黑马程序员——Java基础—多线程(二)
2015-04-05 13:14
369 查看
———Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ———
一线程间通信
二jdk5新特性
三停止线程
四Thread类其他方法
五多线程开发应用
多线程(二)
线程间通信就是多个线程操作同一资源,但是操作的动作不同。
2.等待唤醒机制
等待唤醒机制,是由
以下代码,通过等待唤醒机制,实现了生产一个披萨,消费一个披萨:
程序运行部分结果如下:
3.Object类中的wait等方法
首先,在等待唤醒机制中,无论是等待操作,还是唤醒操作,都必须标识出等待的这个线程和被唤醒的这个线程锁持有的锁;表现为代码是:
4、生产者消费者模型
在实际生产时,会有多个线程负责生产,多个线程负责消费;那么在上述代码中启动新线程,来模拟多线程生产消费的情况。
示例代码:
用这样的方式,运行会出现如下结果:
生产了两个披萨,但只消费了一个。现在0,1线程负责生产,2,3线程负责消费,原因推断:
1)当0线程生产完一个披萨,进入冻结;
2)1线程判断有披萨,进入冻结;
3)2线程消费一个披萨,唤醒0线程,进入冻结;
4)3线程判断没披萨,进入冻结;
5)现在出于运行状态的只有0线程,0线程生产一个披萨,唤醒1线程(1线程是线程池中第一个线程),进入冻结;
6)1线程又生产了一个披萨
这导致了生产两个,只消费一个的问题。这个问题的发生是因为,第
示例代码:
每次被唤醒,都要判断flag的值。代码运行结果如下:
程序出现了无响应,因为使用
示例代码:
程序运行部分结果:
jdk5开始,提供了多线程同步的升级解决方案。将
2.Lock接口和Condition接口
1)Lock接口已知实现类中,有ReentrantLock类。这个子类可以用来实例化,创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock();
2)Condition接口的实例可以通过newCondition()方法获得
Condition conditon = Lock.newCondition();
3)一个Lock对象可以对应多个Condition对象
Condition condition1 = Lock.newCondition();
Condition condition2 = Lock.newCondition();
3.新特性应用
将此新特性应用在消费者生产者模型中,实现只唤醒对方线程。
修改之后的Pizza类代码如下:
分别创建的
注意:
当线程处于冻结状态,无法读取控制循环的标记,线程就不会结束。
2.interrupt()方法
将处于冻结状态的线程,强制恢复到运行状态。
示例代码:
如果不调用
上述程序运行结果:
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。守护线程可以理解为后台线程。后台线程开启后,会和前台线程(一般线程)一起抢夺CPU资源;当所有前台线程结束运行后,后台线程自动结束。可以理解为,后台线程依赖前台线程的运行。
示例代码:
在启动两个线程前,将两个线程设置为守护线程,其他代码不变;那么这两个线程依赖主线程运行;虽然这两个线程都处于冻结状态,但是当主线程运行完毕,这两个守护进程随之结束。
2.join()方法
调用
示例代码:
程序在启动
3.yield()方法
调用
示例代码:
程序运行部分结果:
三个线程均衡执行。
示例代码:
让主线程,匿名线程和
———Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ———
一线程间通信
二jdk5新特性
三停止线程
四Thread类其他方法
五多线程开发应用
多线程(二)
一、线程间通信
1.定义线程间通信就是多个线程操作同一资源,但是操作的动作不同。
2.等待唤醒机制
等待唤醒机制,是由
wait(),
notify()或
notifyAll()等方法组成。对于有些资源的操作,需要一个线程完成一步,进入等待状态,将CPU执行权交由另一个线程,让它完成下一步的操作,如此交替进行。这个过程中,一个线程需要在完成一步操作后,先通知(
notify())另一个线程运行,再等待(
wait()),进入冻结状态,以此类推。等待中的线程,都储存在系统线程池中,等待这被
notify()唤醒。
以下代码,通过等待唤醒机制,实现了生产一个披萨,消费一个披萨:
package com.heisejiuhuche; public class ProductionConsumptionModel { public static void main(String[] args) { Pizza pizza = new Pizza(); new Thread(new Producer(pizza)).start(); new Thread(new Consumer(pizza)).start(); } } class Pizza { private String pizza; private int count = 1; //包子存在与否的旗标,false代表没有pizza,true代表有 private boolean flag = false; public synchronized void producePizza(String pizza) { if (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.pizza = pizza + "---" + count++; System.out.println(Thread.currentThread().getName() + "-生产-----" + this.pizza); flag = true; notify(); } public synchronized void consumePizza() { //如果没有pizza,则执行生产包子的代码 if (!flag) { try { //如果有pizza,则线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "-消费--------------" + this.pizza); //如果没有pizza,生产完之后,将flag设为true flag = false; //线程进入冻结状态之前,通知另一线程开始启动,消费pizza notify(); } } class Producer implements Runnable { private Pizza pizza; Producer(Pizza pizza) { this.pizza = pizza; } public void run() { while (true) { pizza.producePizza("pizza"); } } } class Consumer implements Runnable { private Pizza pizza; Consumer(Pizza pizza) { this.pizza = pizza; } public void run() { while (true) { pizza.consumePizza(); } } }
程序运行部分结果如下:
Thread-1-消费--------------pizza---20609 Thread-0-生产-----pizza---20610 Thread-1-消费--------------pizza---20610 Thread-0-生产-----pizza---20611 Thread-1-消费--------------pizza---20611
3.Object类中的wait等方法
wait()等多线程同步等待唤醒机制中的方法,被定义在
Object类中是因为:
首先,在等待唤醒机制中,无论是等待操作,还是唤醒操作,都必须标识出等待的这个线程和被唤醒的这个线程锁持有的锁;表现为代码是:
锁.wait();
锁.notify();而这个锁,由
synchronized关键字格式可知,可以是任意对象;那么,可以被任意对象调用的方法,一定是定义在了
Object类当中。
wait(),
notify(),
notifyAll()这些方法都被定义在了
Object类中,因为这些方法是要使用在多线程同步的等待唤醒机制当中,必须具备能被任意对象调用的特性。所以,这些方法要被定义在
Object类中。
4、生产者消费者模型
在实际生产时,会有多个线程负责生产,多个线程负责消费;那么在上述代码中启动新线程,来模拟多线程生产消费的情况。
示例代码:
package com.heisejiuhuche; public class ProductionConsumptionModel { public static void main(String[] args) { Pizza pizza = new Pizza(); //两个线程负责生产,两个线程负责消费 new Thread(new Producer(pizza)).start(); new Thread(new Producer(pizza)).start(); new Thread(new Consumer(pizza)).start(); new Thread(new Consumer(pizza)).start(); } }
用这样的方式,运行会出现如下结果:
Thread-0-生产-----pizza---198 Thread-1-生产-----pizza---199 Thread-2-消费--------------pizza---199
生产了两个披萨,但只消费了一个。现在0,1线程负责生产,2,3线程负责消费,原因推断:
1)当0线程生产完一个披萨,进入冻结;
2)1线程判断有披萨,进入冻结;
3)2线程消费一个披萨,唤醒0线程,进入冻结;
4)3线程判断没披萨,进入冻结;
5)现在出于运行状态的只有0线程,0线程生产一个披萨,唤醒1线程(1线程是线程池中第一个线程),进入冻结;
6)1线程又生产了一个披萨
这导致了生产两个,只消费一个的问题。这个问题的发生是因为,第
5步
0线程唤醒
1线程的时候,由于
1线程的等待代码在
if语句中,
1线程醒了之后,不需要再判断
flag的值所导致。如果
1线程被唤醒,还要继续判断flag的值,就不会产生这个情况。因此,要将if判断,改为
while循环,让线程被唤醒之后,再次判断
flag的值。
示例代码:
while (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
每次被唤醒,都要判断flag的值。代码运行结果如下:
Thread-0-生产-----pizza---1 Thread-2-消费--------------pizza---1 Thread-0-生产-----pizza---2 Thread-3-消费--------------pizza---2
程序出现了无响应,因为使用
while循环,可能会出现所有线程全部进入冻结状态的情况。要解决这个问题,必须用到另一个方法
notifyAll();唤醒所有线程。由于用了
while循环,所有线程被唤醒之后第一件事是判断
flag的值,所以不会再出现多生产或多消费问题。至此,程序运行正常。
示例代码:
public synchronized void consumePizza() { while(!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "-消费--------------" + this.pizza); //如果没有pizza,生产完之后,将flag设为true flag = false; //线程进入冻结状态之前,唤醒所有其他线程 notifyAll(); } }
程序运行部分结果:
Thread-2-消费--------------pizza---198 Thread-0-生产-----pizza---199 Thread-3-消费--------------pizza---199 Thread-1-生产-----pizza---200 Thread-3-消费--------------pizza---200
二、jdk5新特性
1.概述jdk5开始,提供了多线程同步的升级解决方案。将
synchronized关键字,替换成
Lock接口;将
Object对象,替换为
Condition对象;将
wai(),
notify(),
notifyAll()方法,替换为
await(),
signal(),
signalAll()方法。一个锁,可以对应多个
Condition对象。这个特性的出现,可以让多线程在唤醒其他线程时,不必唤醒本方的线程,只唤醒对方线程。例如在生产者消费者模型中,使用
Lock和
Condition类,可以实现只唤醒消费者线程,或只唤醒生产者线程。
2.Lock接口和Condition接口
1)Lock接口已知实现类中,有ReentrantLock类。这个子类可以用来实例化,创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock();
2)Condition接口的实例可以通过newCondition()方法获得
Condition conditon = Lock.newCondition();
3)一个Lock对象可以对应多个Condition对象
Condition condition1 = Lock.newCondition();
Condition condition2 = Lock.newCondition();
3.新特性应用
将此新特性应用在消费者生产者模型中,实现只唤醒对方线程。
修改之后的Pizza类代码如下:
class Pizza { private String pizza; private int count = 1; private boolean flag = false; //获取Lock和Condition对象 private final ReentrantLock lock = new ReentrantLock(); //分别指定生产者和消费者的Condition对象 private final Condition conditionPro = lock.newCondition(); private final Condition conditionCon = lock.newCondition(); public void producePizza(String pizza) { //上锁 lock.lock(); try { while (flag) { //如果有披萨,线程冻结 conditionPro.await(); } this.pizza = pizza + "---" + count++; System.out.println(Thread.currentThread().getName() + "-生产-----" + this.pizza); flag = true; //只唤醒消费者线程中的一个 conditionCon.signal(); } catch(InterruptedException e) { e.printStackTrace(); } finally { //这是一定要执行的代码,解锁 lock.unlock(); } } public void consumePizza() { lock.lock(); try { while(!flag) { conditionCon.await(); } System.out.println(Thread.currentThread().getName() + "-消费--------------" + this.pizza); flag = false; conditionPro.signal(); } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
分别创建的
conditionPro和
conditionCon对象,用于实现只唤醒对方线程,代码更优。
三、停止线程
1.线程停止原理stop()方法已经过时,停止的唯一标准就是
run()方法结束。开启多线程运行,运行代码通常都是循环结构,只要控制住循环,就可以让
run()方法结束,就可以让线程结束。
注意:
当线程处于冻结状态,无法读取控制循环的标记,线程就不会结束。
2.interrupt()方法
将处于冻结状态的线程,强制恢复到运行状态。
interrupt()方法是在清除线程的冻结状态。
示例代码:
package com.heisejiuhuche; public class InterruptTest { public static void main(String[] args) { int x = 0; Interrupt inter = new Interrupt(); Thread t1 = new Thread(inter); Thread t2 = new Thread(inter); t1.start(); t2.start(); while(true) { System.out.println(Thread.currentThread().getName() + "run...."); if(x++ == 60) { //强制t1 t2恢复运行状态,抛出异常 t1.interrupt(); t2.interrupt(); break; } } System.out.println("over"); } } class Interrupt implements Runnable { //循环控制变量 private boolean flag = true; public synchronized void run() { while(flag) { try { //让t1 t2进入冻结状态 this.wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "Interrupt Exception....."); //处理完异常,改变flag的值,下次判断时,结束循环 changeFlag(); } System.out.println(Thread.currentThread().getName() + " Interrupt run....."); } } public void changeFlag() { flag = false; } }
如果不调用
t1和
t2线程的
interrupt()方法,程序会无响应,因为两个线程都处于冻结状态,无法继续运行。
上述程序运行结果:
mainrun.... over Thread-1Interrupt Exception..... Thread-1 Interrupt run..... Thread-0Interrupt Exception..... Thread-0 Interrupt run.....
四、Thread类其他方法
1.setDaemon()方法将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。守护线程可以理解为后台线程。后台线程开启后,会和前台线程(一般线程)一起抢夺CPU资源;当所有前台线程结束运行后,后台线程自动结束。可以理解为,后台线程依赖前台线程的运行。
示例代码:
package com.heisejiuhuche; public class InterruptTest { public static void main(String[] args) { int x = 0; Interrupt inter = new Interrupt(); Thread t1 = new Thread(inter); Thread t2 = new Thread(inter); t1.setDaemon(true); t2.setDaemon(true); t1.start(); t2.start(); }
在启动两个线程前,将两个线程设置为守护线程,其他代码不变;那么这两个线程依赖主线程运行;虽然这两个线程都处于冻结状态,但是当主线程运行完毕,这两个守护进程随之结束。
2.join()方法
调用
join()方法的线程,在申请CPU执行权。之前拥有CPU执行权的线程,将转入冻结状态,等调用
join()方法的线程执行完毕,再转回运行状态。
示例代码:
package com.heisejiuhuche; public class JoinTest { public static void main(String[] args) { Join j = new Join(); Thread t1 = new Thread(j); Thread t2 = new Thread(j); t1.start(); try { //主线程将CPU执行权交给t1线程,自己转入冻结 //等待t1线程执行完毕,主线程再运行 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); for(int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + "***" + x); } } } class Join implements Runnable { public void run() { for(int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + "---" + x); } } }
程序在启动
t1线程之后,主线程先等待
t1线程打印完
100个数;主线程再继续和
t2线程交替打印
100个数。
3.yield()方法
调用
yield()方法的线程,会临时释放执行权,可以达到线程均衡运行的效果。
示例代码:
package com.heisejiuhuche; public class YieldTest { public static void main(String[] args) { Yield j = new Yield(); Thread t1 = new Thread(j); Thread t2 = new Thread(j); t1.start(); t2.start(); for(int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + "***" + x); } } } class Yield implements Runnable { public void run() { for(int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + "---" + x); Thread.yield(); } } }
程序运行部分结果:
Thread-1---51 main***79 Thread-0---46 main***80 Thread-1---52 main***81 Thread-0---47
三个线程均衡执行。
五、多线程开发应用
多线程应用在程序中的运算需要同时进行的时候,可以提高程序运行的效率。例如,main()方法中有三个循环需要执行,如果是单线程,第二个循环要等待第一个循环执行完才能执行,第三个循环要等第二个循环执行完,如此一来,程序运行效率低下。此时,就可以运用多线程,让三个循环同时运行。
示例代码:
package com.heisejiuhuche; public class ThreadApplycation { public static void main(String[] args) { //主线程执行 for(int x = 0; x < 100; x ++) { System.out.println("Main thread running ..."); } //匿名线程执行 new Thread() { public void run() { for(int x = 0; x < 100; x++) { System.out.println("Anonymous thread running ..."); } } }.start(); //线程r执行 Runnable r = new Runnable() { public void run() { for(int x = 0; x < 100; x ++) { System.out.println("r thread running ..."); } } }; new Thread(r).start(); } }
让主线程,匿名线程和
r线程,同时开始运算。
———Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ———
相关文章推荐
- 黑马程序员_java基础加强8_多线程加强
- 黑马程序员---Java基础--11天(多线程)
- 黑马程序员---java基础之多线程
- 黑马程序员__JAVA基础__多线程
- 黑马程序员-Java基础之多线程总结
- 黑马程序员_java基础加强9_多线程加强
- [黑马程序员]--Java语言基础-多线程
- 黑马程序员-JAVA基础-多线程(上)
- 黑马程序员_Java多线程通信基础
- 黑马程序员---java基础多线程
- 黑马程序员—7、JAVA基础&多线程
- 黑马程序员-----java基础十二(java之多线程)
- 黑马程序员-Java语言基础– 多线程 第11天
- 黑马程序员-JAVA基础-多线程(下)
- 黑马程序员_java基础加强6_多线程加强
- 黑马程序员---------笔记整理(java基础八-----多线程)
- 黑马程序员_Java基础_多线程1
- 黑马程序员_Java基础(4)--多线程
- 黑马程序员 java基础之多线程
- 黑马程序员 Java基础 --->多线程