黑马程序员Java学习笔记之多线程(并发)
来源:互联网 发布:vs2010 编译php扩展 编辑:程序博客网 时间:2024/05/17 01:09
-------android培训、java培训、期待与您交流! ----------
现在的操作系统普遍是多任务操作系统,即同时可以执行多个任务(一般的表现是开启运行多个软件,或者在一个软件中同时同时使用多个CPU核心进行并行运算),多核CPU普及之后,实现了真正意义上的并行运算。多任务的优势自然是不言而喻的,而在程序设计时,通过多线程的方式实现了多任务。
一、多线程的理解
在单核CPU上,多线程其实是伪多线程。CPU会分配给每个任务一些时间片,某个任务执行几步,切换到另外的任务再执行几步,这样不停切换。由于CPU运算是以毫秒甚至纳秒为单位的,所以切换的频率非常快,以至于看上去多个任务在并行运行。你可以边听歌边浏览网页,同时后台也会运行一些软件。而在多核CPU上,则是物理上就有多个运算核心同时工作,系统会按照优化的方式分配不同的任务给某一个或几个核心。但是光靠系统的分配是不足够的,如果程序开发人员想要一些大型程序高效运行,就需要善用多线程编程。
二、Java中多线程的基本实现方法
在Java中,我们类里的main()方法,又称为主线程,如果我们不去创建其他的线程,就会一直执行主线程,但其实比如GC(垃圾回收装置),也是一个线程,所以也就是说,Java程序本身就是多线程的。而我们常说的多线程编程,主要是指的自定线程。
在Java中,实现自定多线程由两种方法,一是继承Thread类,二是实现Runnable接口。我们通常都会采用第二种方式即实现Runnable接口的方式来实现多线程。因为Java采用单继承,所以如果我们的类直接继承Thread类,就无法再继承其他的类了,而接口实现则可以同时实现多个,所以更加灵活
1、继承Thread类的多线程实现
Thread类(java.lang.Thread)为一个基本的多线程类,通过继承Thread类实现多线程,主要是重写其中的run()方法。在run()方法中写线程主体,而在主线程main()合适的位置上,调用类的实例.start()方法,即可开启该线程。
案例1:使用Thread类创建线程
package JavaMultithreading;/** * Created by za on 2015/7/11. */public class RabAndTor { public static void main(String[] args) { Rabbit rab = new Rabbit(); Tortoise tor = new Tortoise(); rab.start(); tor.start(); }}class Rabbit extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("兔子在跑第 " + i + " 步!"); } }}class Tortoise extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("乌龟在跑第 " + i + " 步!"); } }}运行结果:
...
2、实现Runnable接口的多线程实现
实现Runnable接口的好处之前已经说了,而方法也是重写接口中的抽象方法run(),创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。
开启线程的方法和Thread类对象创建法类似,也是在main()中合适的位置使用实例.start()。
案例2:使用实现Runnable接口创建线程
package JavaMultithreading;/** * Created by za on 2015/7/11. */public class Rabbit2 implements Runnable { @Override public void run() { for (int i = 0; i < 1000; i++) { System.err.println("兔子在跑第 " + i + " 步..."); } }}
三、Java中线程的状态
一个线程有自己的生命周期,有五种状态:新生(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和终止(Dead)。
如图所示,一个线程从最初创建后,执行start(),就进入就绪状态,这时相当于告诉CPU,该线程已经可以开始运行了,由CPU分配时间片给该线程。当该线程真正开始执行的时候,就进入了运行状态,run()方法的方法体开始工作。在这个过程中,线程有可能被暂停,比如执行sleep()方法,这就进入了阻塞状态,进入阻塞状态的线程一直处于停滞状态,直到阻塞解除,线程重新回到就绪状态(注意,这里是回到就绪状态重新排队等待运行而不是回到运行状态)。当线程执行完毕或者被强制终止,就进入终止状态,也就是死掉了,和该线程有关的资源将会被释放。
四、线程的终止
线程终止有两种情况,自然终止和外部干预。
1、自然终止,线程运行完毕后终止。
2、外部干预,有三种方法
1)使用终止标识,使线程正常退出
a、线程类中定义线程体使用的标识
b、线程体中使用该标识
c、对外提供方法改变该标识
案例3:使用终止标识终止线程
package JavaMultithreading;/** * Created by za on 2015/7/12. */public class TestThreadStop { public static void main(String[] args) { Study std = new Study(); new Thread(std).start(); for (int i = 0; i < 100; i++) { if(50 == i) { std.stop(); } System.out.println("Online Chatting..." + i); } }}class Study implements Runnable { public boolean flag = true; @Override public void run() { while (flag) { System.out.println("Studying Thread..."); } } public void stop() { this.flag = false; }}运行结果:
2)使用stop()方法强行终止线程
一般不使用,因为stop和suspend、resume一样,也可能发生不可预料的结果。
3)使用interrupt()方法中断线程
五、线程的阻塞
1、join()方法:合并线程
案例5:join()方法合并线程
package JavaMultithreading;/** * 合并线程 * Created by za on 2015/7/12. */public class TestJoin extends Thread { public static void main(String[] args) throws InterruptedException { TestJoin t1 = new TestJoin(); //进程新生 Thread thread = new Thread(t1); //进程就绪 thread.start(); for (int i = 0; i < 200; i++) { if(i == 50) { //使用join方法,将main线程阻塞,要等待join执行完,再执行main thread.join(); } System.out.println("main..." + i); } } public void run() { for (int i = 0; i < 100; i++) { System.out.println("join..." + i); } }}运行结果:
2、yield()方法:暂停当前执行的线程,让出CPU调度,先执行其他线程
yield()方法是一个static静态方法,暂停当前线程并不意味这当前线程一定不会被执行,只是现在暂停了,回到了就绪模式,还是有可能在未来的某个时间被CPU调到
案例6:yield()方法暂停线程
package JavaMultithreading;/** * Created by za on 2015/7/12. */public class TestYield implements Runnable { public static void main(String[] args) { TestYield t2 = new TestYield(); new Thread(t2).start(); for (int i = 0; i < 100; i++) { if (i%2 == 0) { Thread.yield();//暂停所在线程main } System.out.println("main..." + i); } } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Yield..." + i); } }}运行结果:
3、sleep()方法:当前线程在指定的毫秒数内休眠,不释放锁
应用:1)与时间相关的,倒计时,计时记数
2)模拟网络延时
案例7:倒计时
package JavaMultithreading;/** * 使用进程sleep模拟倒计时 * Created by za on 2015/7/12. */public class TestSleep01 implements Runnable { public static void main(String[] args) { TestSleep01 ts1 = new TestSleep01(); new Thread(ts1).start(); } @Override public void run() { int counter = 10; while(true) { System.out.println(counter--); if(counter < 0) break; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}运行结果:
案例8:模拟网络延时售票
package JavaMultithreading;/** * Created by za on 2015/7/11. */public class TestWebTicketSellingSystem implements Runnable { public static int numLeft = 50; public static int numCounter = 1; public static void main(String[] args) { TestWebTicketSellingSystem sell = new TestWebTicketSellingSystem(); Thread t1 = new Thread(sell, "张三"); Thread t2 = new Thread(sell, "李四"); Thread t3 = new Thread(sell, "王五"); Thread t4 = new Thread(sell, "赵六"); Thread t5 = new Thread(sell, "刘七"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } @Override public void run() { while(true) { if (numLeft <= 0) { System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" ); break; } else { System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 " + numLeft-- + " 张。" ); try { //模拟网络延时 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }}运行结果:
略
六、线程的优先级
1、线程状态信息的获取方法
1)Thread.currentThread()方法,是一个静态static方法,返回当前线程的对象的引用
2)setName() 设置名称
3)getName() 获取名称
4)isAlive() 判断状态
案例9:获取线程状态信息
package JavaMultithreading;/** * Created by za on 2015/7/13. */public class ThreadTest1 implements Runnable { private boolean flag = true; private int num = 0; @Override public void run() { while(flag) { //获取线程状态信息 System.out.println(Thread.currentThread().getName() + "---->" + num++); } } public void setFlag(boolean flag) { this.flag = flag; } public void stop() { setFlag(false); }}
2、线程的优先级
线程优先级有三种
1)最大优先MAX_PRIORITY = 10
2)一般优先NORM_PRIORITY = 5(默认优先级)
3)最小优先MIN_PRIORITY = 1
案例10:线程优先级
package JavaMultithreading;/** * Thread.currentThread() 当前线程 * setName() 设置名称 * getName() 获取名称 * isAlive() 判断状态 * * Created by za on 2015/7/13. */public class TestPriority { public static void main(String[] args) throws InterruptedException { ThreadTest1 it1 = new ThreadTest1(); Thread p1 = new Thread(it1, "进程1"); ThreadTest1 it2 = new ThreadTest1(); Thread p2 = new Thread(it2, "进程2"); //p1.setPriority(Thread.MAX_PRIORITY); //p2.setPriority(Thread.MIN_PRIORITY); p1.start(); p2.start(); Thread.sleep(2); it1.stop(); it2.stop(); }}运行结果:
七、线程的同步(并发)
线程的同步也成为并发,因为有多线程访问同一个资源,我们就需要确保这个资源安全。因此我们就要对资源加入同步,使资源变为线程安全的。
使用同步关键字synchronized,修饰代码块或者方法。也叫做锁,要实现线程同步,首先要获得该线程的锁。
只要是线程安全的,效率就没有线程不安全的高。这个要按需求进行处理。
1、同步块
synchronized(引用类型|this|类.class) {
}
2、同步方法
使用synchronized修饰的方法。
案例11:线程锁,同步块和同步方法的应用
package JavaMultithreading;/** * 线程锁|线程安全 * Created by za on 2015/7/13. */public class TestSyn { public static void main(String[] args) { Test12306 sell = new Test12306(); Thread t1 = new Thread(sell, "张三"); Thread t2 = new Thread(sell, "李四"); Thread t3 = new Thread(sell, "王五"); Thread t4 = new Thread(sell, "赵六"); Thread t5 = new Thread(sell, "刘七"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }}class Test12306 implements Runnable { public static int numLeft = 49; public static int numCounter = 1; public boolean flag = true; @Override public void run() { while(flag) { TicketSystem3(); } } //线程安全的 局部synchronized public void TicketSystem3() { synchronized(this) { if (numLeft == 0) { System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" ); flag = false; return; } System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 " + numLeft-- + " 张。" ); /*try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }*/ } } //线程安全的 public synchronized void TicketSystem2() { if (numLeft <= 0) { System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" ); flag = false; return; } else { System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 " + numLeft-- + " 张。" ); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } //线程不安全 public void TicketSystem1() { if (numLeft <= 1) { System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" ); flag = false; return; } else { System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 " + numLeft-- + " 张。" ); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }}运行结果:
图略
八、单例模式的线程锁
单例模式是指一个类只有一个实例的设计模式,一般采用私有构造器的方式去避免创建实例,同时对是否已经存在实例进行判断,如果不存在才创建实例。但是如果涉及到多线程,就有可能存在并发的问题,即如果几个线程同时请求创建实例,如果线程不安全,就有可能创建出大于一个的实例。这时要采用线程安全的synchronized方法检测。同时应用双层检测机制double checking。
案例12:单例模式的线程锁
package JavaMultithreading;/** * 懒汉式 * Created by za on 2015/7/13. */public class MyJvm { private static MyJvm instance; private MyJvm() { } public static void main(String[] args) { } public static MyJvm getInstance() { if (null == instance) { //提供效率 synchronized(MyJvm.class) { if (null == instance) { //安全 instance = new MyJvm(); } } } return instance; }}/* * 饿汉式 * 1、构造器私有化 * 2、声明私有的静态属性,并创建该对象 * 3、对外提供访问属性的静态方法 */class MyJvm2 { private static MyJvm2 instance = new MyJvm2(); private MyJvm2() { } public static void main(String[] args) { } public static MyJvm2 getInstance() { if (null == instance) { //提供效率 synchronized(MyJvm2.class) { if (null == instance) { //安全 instance = new MyJvm2(); } } } return instance; }}
九、死锁
1、Java死锁
Java线程死锁是指,多个资源都在等待无法被释放的锁,使得无法继续进行。过多的同步容易造成死锁,死锁一定建立在同步之上,没有同步也就不存在死锁了。
案例13:一手交钱一手交货的死锁
package JavaMultithreading;/** * 一手交钱一手交货的死锁示例 * Created by za on 2015/8/12. */public class TestSyn02 { public static void main(String[] args) { Object goods = new Object(); Object money = new Object(); //Test1和Test2都要访问goods和money这一份资源,这样就容易产生死锁 Client t1 = new Client(goods, money); Sellsman t2 = new Sellsman(goods, money); Thread proxy1 = new Thread(t1); Thread proxy2 = new Thread(t2); //执行结果可能是死锁,谁都没有办法执行了 proxy1.start(); proxy2.start(); }}class Client implements Runnable { //建立一个客户类,要求拿到货才给钱 //定义钱和货物两个Field Object goods; Object money; //创建构造器,由外部传入钱和货物两个对象 public Client(Object goods, Object money) { this.goods = goods; this.money = money; } @Override public void run() { while(true) { test(); } } //写一个test方法 public void test() { //第一道锁,对货物的,要先拿到货物的锁 //过多的线程同步会造成死锁 synchronized (goods) { try { //添加延时为了增大死锁的概率 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //第二道锁,对钱的 synchronized (money) { } } System.out.println("一手交钱"); }}class Sellsman implements Runnable { //建立一个商人类,要求拿到钱才给货 //定义钱和货物两个Field Object goods; Object money; //创建构造器,由外部传入钱和货物两个对象 public Sellsman(Object goods, Object money) { this.goods = goods; this.money = money; } @Override public void run() { while(true) { test(); } } //写一个test方法 public void test() { //第一道锁,对钱的,要先拿到钱的锁 //过多的线程同步会造成死锁 synchronized (money) { try { //添加延时为了增大死锁的概率 Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //第二道锁,对货物的的 synchronized (goods) { } } System.out.println("一手交货"); }}
2、解决方案
解决死锁问题的一个办法,采用生产者消费者模式。
生产者消费者问题(Producer-consumer problem)也成为有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程----即所谓的“生产者”和“消费者”----在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复这一过程。与此同时,消费者也在缓冲区中消耗这些数据。该问题的关键就是要保证生产者不会再缓冲区蛮时加入数据,消费者也不会在缓冲区空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满师休眠(要么干脆放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区中添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,在唤醒消费者。
常用办法:信号灯法,管程等。
存在问题:如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。
关键方法:设置boolean类型信号灯flag
java.lang.Object类方法notify()唤醒,notifyAll()唤醒所有,wait()等待
wait()等待,释放锁|sleep()不释放锁
案例14:生产者消费者模式(信号灯法)
package JavaMultithreading;/** * * Created by za on 2015/8/12. */public class TestPcp { public static void main(String[] args) { //共同的资源是 Movie m = new Movie(); //多线程 Player p = new Player(m); Watcher w = new Watcher(m); new Thread(p).start(); new Thread(w).start(); }}class Movie { //共同的movie资源 //使用生产者消费者模式(信号灯法) //与synchronized一起使用 private String pic; /** * 创建信号灯flag * flag == true,生产者生产,消费者等待,生产完成后通知消费者 * flag == false,消费者消费,生产者等待,消费完成后通知生产 */ private boolean flag = true; /** * 播放 */ public synchronized void play(String pic) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //开始生产,用sleep方法模拟生产耗费时间 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("生产了:" + pic); //生产完毕 this.pic = pic; //通知消费者 this.notify(); //生产停止 this.flag = false; } public synchronized void watch() { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //开始消费 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费了:" + pic); //消费完毕 //通知生产者 this.notifyAll(); //消费停止 this.flag = true; }}class Player implements Runnable { /** * 生产者 */ private Movie m; public Player(Movie m) { super(); this.m = m; } @Override public void run() { for (int i = 0; i < 20; i++) { if (0 == i%2) { m.play("case1"); } else { m.play("case2"); } } }}class Watcher implements Runnable { /** * 消费者 */ private Movie m; public Watcher(Movie m) { super(); this.m = m; } @Override public void run() { for (int i = 0; i < 20; i++) { m.watch(); } }}
十、任务调度
1、常用类:Timer定时器类
java.util.Timer是线程用其安排以后在后台线程中执行的任务。可安排任务执行一次或者定期重复执行。
2、常用方法:schedule()日程安排
参数
public void schedule(TimerTask task, Date time)
public void schedule(TimerTask task, long delay, long period)
案例15:任务调度
package JavaMultithreading;import java.util.Date;import java.util.Timer;import java.util.TimerTask;/** * Created by za on 2015/8/12. */public class TestSchedule {}class Timer01 { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { //TimerTask是java.util包下的一个抽象类,实现了Runnable接口,就是一个线程 //我们要重写其中的run()方法,这里使用了匿名 @Override public void run() { System.out.println("Test timer"); } }, /*开始时间*/new Date(System.currentTimeMillis() + 1000), /*间隔时间,没有就是运行一次*/200); }}
0 0
- 黑马程序员Java学习笔记之多线程(并发)
- 黑马程序员------java学习笔记之多线程
- 黑马程序员---java学习笔记之多线程
- 黑马程序员-Java学习笔记之多线程
- 黑马程序员---java学习笔记之多线程2
- 黑马程序员—Java基础学习笔记之多线程
- 黑马程序员 java笔记之多线程
- 黑马程序员-java学习之多线程技术
- 黑马程序员-java学习之多线程
- 黑马程序员--Java学习日记之多线程
- 黑马程序员--Java学习笔记之多线程(自定义线程的两种方式对比、线程状态、线程安全)
- 黑马程序员----java之多线程
- 黑马程序员---JAVA之多线程
- 黑马程序员---Java之多线程
- 黑马程序员java之多线程
- 黑马程序员——JAVA笔记之多线程
- 黑马程序员 Java学习总结之多线程基础
- 黑马程序员——Java学习之多线程分析
- 君と彼女の恋
- day20
- house robber
- iOS-数据库sqlite的使用
- Hadoop中的HDFS学习
- 黑马程序员Java学习笔记之多线程(并发)
- 曝刘亦菲奢华豪宅 面积堪比4个足球场,有钱就是这么任性!
- bzoj 1503: [NOI2004]郁闷的出纳员 (伸展树)
- FZU 1848 – ZeroZeroZeros (找规律+二分)
- 如何将DrawerLayout显示在ActionBar/Toolbar和status bar之间
- 三目运算 小tip
- OpenWRT上安装FreeSWITCH
- wcf使用X509证书加密传输
- Uva 11624 - Fire!