java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
本篇我们将讨论以下知识点:
1.线程同步问题的产生
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午2:55:42
- * @decrition 模拟卖票线程
- */
- public class Ticket implements Runnable
- {
- //当前拥有的票数
- private int num = 100;
- public void run()
- {
- while(true)
- {
- if(num>0)
- {
- try{Thread.sleep(10);}catch (InterruptedException e){}
- //输出卖票信息
- System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
- }
- }
- }
- }
上面是卖票线程类,下来再来看看执行类:
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午2:54:18
- * @decrition 模拟卖票系统,该案例只考虑单方面卖票,其他情况暂时不考虑
- */
- public class TicketDemo {
- public static void main(String[] args)
- {
- Ticket t = new Ticket();//创建一个线程任务对象。
- //创建4个线程同时卖票
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- Thread t3 = new Thread(t);
- Thread t4 = new Thread(t);
- //启动线程
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
运行程序结果如下(仅截取部分数据):
SE5.0之后并发包中新增了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。接下来我们就来介绍Lock接口的主要API方便我们学习
方法 | 相关描述内容 |
void lock() | 获取锁,调用该方法当前线程会获取锁,当获取锁后。从该方法返回 |
void lockInterruptibly() throws InterruptedException |
可中断获取锁和lock()方法不同的是该方法会响应中断,即在获取锁 中可以中断当前线程。例如某个线程在等待一个锁的控制权的这段时 间需要中断。 |
boolean tryLock() | 尝试非阻塞获取锁,调用该方法后立即返回,如果能够获取锁则返回 true,否则返回false。 |
boolean tryLock(long time,TimeUnit unit) throws InterruptedException |
超时获取锁,当前线程在以下3种情况返回: 1.当前线程在超时时间内获取了锁 2.当前线程在超时时间被中断 3.当前线程超时时间结束,返回false |
void unlock() | 释放锁 |
Condition newCondition() | 条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有 获取了锁,才能调用该组件的await()方法,而调用后,当前线程将缩放 锁。 |
- ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
- ReentrantLock lock = new ReentrantLock(true); //公平锁
- lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
- try {
- //操作
- } finally {
- lock.unlock(); //释放锁
- }
2.防止重复执行代码:
- ReentrantLock lock = new ReentrantLock();
- if (lock.tryLock()) { //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
- try {
- //操作
- } finally {
- lock.unlock();
- }
- }
3.尝试等待执行的代码:
- ReentrantLock lock = new ReentrantLock(true); //公平锁
- try {
- if (lock.tryLock(5, TimeUnit.SECONDS)) {
- //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
- try {
- //操作
- } finally {
- lock.unlock();
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
- }
这里有点需要特别注意的,把解锁操作放在finally代码块内这个十分重要。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。好了,ReentrantLock我们就简单介绍到这里,接下来我们通过ReentrantLock来解决前面卖票线程的线程同步(安全)问题,代码如下:
- package com.zejian.test;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author zejian
- * @time 2016年3月12日 下午2:55:42
- * @decrition 模拟卖票线程
- */
- public class Ticket implements Runnable
- {
- //创建锁对象
- private Lock ticketLock = new ReentrantLock();
- //当前拥有的票数
- private int num = 100;
- public void run()
- {
- while(true)
- {
- ticketLock.lock();//获取锁
- if(num>0)
- {
- try{
- Thread.sleep(10);
- //输出卖票信息
- System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
- }catch (InterruptedException e){
- Thread.currentThread().interrupt();//出现异常就中断
- }finally{
- ticketLock.unlock();//释放锁
- }
- }
- }
- }
- }
- public synchronized void method{
- //method body
- }
等价于
- private Lock ticketLock = new ReentrantLock();
- public void method{
- ticketLock.lock();
- try{
- //.......
- }finally{
- ticketLock.unlock();
- }
- }
从这里可以看出使用synchronized关键字来编写代码要简洁得多了。当然,要理解这一代码,我们必须知道每个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入synchronized方法的线程,由条件来管那些调用wait的线程(wait()/notifyAll/notify())。同时我们必须明白一旦有一个线程通过synchronied方法获取到内部锁,该类的所有synchronied方法或者代码块都无法被其他线程访问直到当前线程释放了内部锁。刚才上面说的是同步方法,synchronized还有一种同步代码块的实现方式:
- Object obj = new Object();
- synchronized(obj){
- //需要同步的代码
- }
其中obj是对象锁,可以是任意对象。那么我们就通过其中的一个方法来解决售票系统的线程同步问题:
- class Ticket implements Runnable
- {
- private int num = 100;
- Object obj = new Object();
- public void run()
- {
- while(true)
- {
- synchronized(obj)
- {
- if(num>0)
- {
- try{Thread.sleep(10);}catch (InterruptedException e){}
- System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
- }
- }
- }
- }
- }
- //创建条件对象
- Condition conditionObj=ticketLock.newCondition();
方法 | 函数方法对应的描述 |
void await() | 将该线程放到条件等待池中(对应wait()方法) |
void signalAll() | 解除该条件等待池中所有线程的阻塞状态(对应notifyAll()方法) |
void signal() | 从该条件的等待池中随机地选择一个线程,解除其阻塞状态(对应notify()方法) |
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:44:25
- * @decrition 烤鸭资源
- */
- public class KaoYaResource {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- /**
- * 生产烤鸭
- */
- public synchronized void product(String name){
- if(flag){
- //此时有烤鸭,等待
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace()
- ;
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- notifyAll();//通知消费线程可以消费了
- }
- /**
- * 消费烤鸭
- */
- public synchronized void consume(){
- if(!flag){//如果没有烤鸭就等待
- try{this.wait();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- notifyAll();//通知生产者生产烤鸭
- }
- }
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:29:12
- * @decrition 单生产者单消费者模式
- */
- public class Single_Producer_Consumer {
- public static void main(String[] args)
- {
- KaoYaResource r = new KaoYaResource();
- Producer pro = new Producer(r);
- Consumer con = new Consumer(r);
- //生产者线程
- Thread t0 = new Thread(pro);
- //消费者线程
- Thread t2 = new Thread(con);
- //启动线程
- t0.start();
- t2.start();
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:22
- * @decrition 生产者线程
- */
- class Producer implements Runnable
- {
- private KaoYaResource r;
- Producer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.product("北京烤鸭");
- }
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:05
- * @decrition 消费者线程
- */
- class Consumer implements Runnable
- {
- private KaoYaResource r;
- Consumer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.consume();
- }
- }
- }
很显然的情况就是生产一只烤鸭然后就消费一只烤鸭。运行情况完全正常,嗯,这就是单生产者单消费者模式。上面使用的是synchronized关键字的方式实现的,那么接下来我们使用对象锁的方式实现:KaoYaResourceByLock.java
- package com.zejian.test;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author zejian
- * @time 2016年3月13日 上午9:55:35
- * @decrition 通过对象锁的方式来实现等待/通知机制
- */
- public class KaoyaResourceByLock {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- //创建一个锁对象
- private Lock resourceLock=new ReentrantLock();
- //创建条件对象
- private Condition condition= resourceLock.newCondition();
- /**
- * 生产烤鸭
- */
- public void product(String name){
- resourceLock.lock();//先获取锁
- try{
- if(flag){
- try {
- condition.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- condition.signalAll();//通知消费线程可以消费了
- }finally{
- resourceLock.unlock();
- }
- }
- /**
- * 消费烤鸭
- */
- public void consume(){
- resourceLock.lock();
- try{
- if(!flag){//如果没有烤鸭就等待
- try{condition.await();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- condition.signalAll();//通知生产者生产烤鸭
- }finally{
- resourceLock.unlock();
- }
- }
- }
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月13日 上午10:35:05
- * @decrition 多生产者多消费者模式
- */
- public class Mutil_Producer_Consumer {
- public static void main(String[] args)
- {
- KaoYaResource r = new KaoYaResource();
- Mutil_Producer pro = new Mutil_Producer(r);
- Mutil_Consumer con = new Mutil_Consumer(r);
- //生产者线程
- Thread t0 = new Thread(pro);
- Thread t1 = new Thread(pro);
- //消费者线程
- Thread t2 = new Thread(con);
- Thread t3 = new Thread(con);
- //启动线程
- t0.start();
- t1.start();
- t2.start();
- t3.start();
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:22
- * @decrition 生产者线程
- */
- class Mutil_Producer implements Runnable
- {
- private KaoYaResource r;
- Mutil_Producer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.product("北京烤鸭");
- }
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:05
- * @decrition 消费者线程
- */
- class Mutil_Consumer implements Runnable
- {
- private KaoYaResource r;
- Mutil_Consumer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.consume();
- }
- }
- }
就多了两条线程,我们运行代码看看,结果如下:
不对呀,我们才生产一只烤鸭,怎么就被消费了3次啊,有的烤鸭生产了也没有被消费啊?难道共享数据源没有进行线程同步?我们再看看之前的KaoYaResource.java
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:44:25
- * @decrition 烤鸭资源
- */
- public class KaoYaResource {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- /**
- * 生产烤鸭
- */
- public synchronized void product(String name){
- if(flag){
- //此时有烤鸭,等待
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- notifyAll();//通知消费线程可以消费了
- }
- /**
- * 消费烤鸭
- */
- public synchronized void consume(){
- if(!flag){//如果没有烤鸭就等待
- try{this.wait();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- notifyAll();//通知生产者生产烤鸭
- }
- }
解决后的资源代码如下只将if改为了while:
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:44:25
- * @decrition 烤鸭资源
- */
- public class KaoYaResource {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- /**
- * 生产烤鸭
- */
- public synchronized void product(String name){
- while(flag){
- //此时有烤鸭,等待
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- notifyAll();//通知消费线程可以消费了
- }
- /**
- * 消费烤鸭
- */
- public synchronized void consume(){
- while(!flag){//如果没有烤鸭就等待
- try{this.wait();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- notifyAll();//通知生产者生产烤鸭
- }
- }
运行代码,结果如下:
到此,多消费者多生产者模式也完成,不过上面用的是synchronied关键字实现的,而锁对象的解决方法也一样将之前单消费者单生产者的资源类中的if判断改为while判断即可代码就不贴了哈。不过下面我们将介绍一种更有效的锁对象解决方法,我们准备使用两组条件对象(Condition也称为监视器)来实现等待/通知机制,也就是说通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。有了前面的分析这里我们直接上代码:
- package com.zejian.test;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author zejian
- * @time 2016年3月13日 下午12:03:27
- * @decrition 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
- */
- public class ResourceBy2Condition {
- private String name;
- private int count = 1;
- private boolean flag = false;
- //创建一个锁对象。
- Lock lock = new ReentrantLock();
- //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
- Condition producer_con = lock.newCondition();
- Condition consumer_con = lock.newCondition();
- /**
- * 生产
- * @param name
- */
- public void product(String name)
- {
- lock.lock();
- try
- {
- while(flag){
- try{producer_con.await();}catch(InterruptedException e){}
- }
- this.name = name + count;
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);
- flag = true;
- // notifyAll();
- // con.signalAll();
- consumer_con.signal();//直接唤醒消费线程
- }
- finally
- {
- lock.unlock();
- }
- }
- /**
- * 消费
- */
- public void consume()
- {
- lock.lock();
- try
- {
- while(!flag){
- try{consumer_con.await();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);//消费烤鸭1
- flag = false;
- // notifyAll();
- // con.signalAll();
- producer_con.signal();//直接唤醒生产线程
- }
- finally
- {
- lock.unlock();
- }
- }
- }
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月13日 下午2:45:52
- * @decrition 死锁示例
- */
- public class DeadLockDemo {
- private static String A="A";
- private static String B="B";
- public static void main(String[] args) {
- DeadLockDemo deadLock=new DeadLockDemo();
- while(true){
- deadLock.deadLock();
- }
- }
- private void deadLock(){
- Thread t1=new Thread(new Runnable(){
- @SuppressWarnings("static-access")
- @Override
- public void run() {
- synchronized (A) {
- try {
- Thread.currentThread().sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- synchronized(B){
- System.out.println("1");
- }
- }
- });
- Thread t2 =new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (B) {
- synchronized (A) {
- System.out.println("2");
- }
- }
- }
- });
- //启动线程
- t1.start();
- t2.start();
- }
- }
millis)和join(long millis,int
nanos)两个具备超时特性的方法。这两个超时的方法表示,如果线程在给定的超时时间里没有终止,那么将会从该超时方法中返回。下面给出一个例子,创建10个线程,编号0~9,每个线程调用钱一个线程的join()方法,也就是线程0结束了,线程1才能从join()方法中返回,而0需要等待main线程结束。
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月13日 下午4:10:03
- * @decrition join案例
- */
- public class JoinDemo {
- public static void main(String[] args) {
- Thread previous = Thread.currentThread();
- for(int i=0;i<10;i++){
- //每个线程拥有前一个线程的引用。需要等待前一个线程终止,才能从等待中返回
- Thread thread=new Thread(new Domino(previous),String.valueOf(i));
- thread.start();
- previous=thread;
- }
- System.out.println(Thread.currentThread().getName()+" 线程结束");
- }
- }
- class Domino implements Runnable{
- private Thread thread;
- public Domino(Thread thread){
- this.thread=thread;
- }
- @Override
- public void run() {
- try {
- thread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" 线程结束");
- }
- }
好了,到此本篇结束。
java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)的更多相关文章
- Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)
一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会 ...
- Java 里如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点:thread.join(), object.w ...
- Java 中如何实现线程间通信
世界以痛吻我,要我报之以歌 -- 泰戈尔<飞鸟集> 虽然通常每个子线程只需要完成自己的任务,但是有时我们希望多个线程一起工作来完成一个任务,这就涉及到线程间通信. 关于线程间通信本文涉及到 ...
- Java并发——使用Condition线程间通信
线程间通信 线程之间除了同步互斥,还要考虑通信.在Java5之前我们的通信方式为:wait 和 notify.Condition的优势是支持多路等待,即可以定义多个Condition,每个condit ...
- Java 里如何实现线程间通信(转载)
出处:http://www.importnew.com/26850.html 正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程 ...
- 【转】Java里如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点:thread.join(), object.w ...
- React 精要面试题讲解(二) 组件间通信详解
单向数据流与组件间通信 上文我们已经讲述过,react 单向数据流的原理和简单模拟实现.结合上文中的代码,我们来进行这节面试题的讲解: react中的组件间通信. 那么,首先我们把看上文中的原生js代 ...
- Java精通并发-多线程同步关系实例剖析与详解
在上一次https://www.cnblogs.com/webor2006/p/11422587.html中通过实践来解了一个案例,先来回顾一下习题: 编写一个多线程程序,实现这样的一个目标: 1.存 ...
- java 利用管道实现线程间通信
package com.lb; import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutpu ...
随机推荐
- 3d Max 2012安装失败怎样卸载3dsmax?错误提示某些产品无法安装
AUTODESK系列软件着实令人头疼,安装失败之后不能完全卸载!!!(比如maya,cad,3dsmax等).有时手动删除注册表重装之后还是会出现各种问题,每个版本的C++Runtime和.NET f ...
- maya 安装失败/出错/卸载 2018/2017/2016/2015/2013/2012
AUTO Uninstaller 更新下载地址 1.选择软件 2.选择版本 3.点击[开始卸载]
- 性能测试工具LoadRunner32-LR之windows性能监控Perfmon
Perfmon是啥? Perfmon提供了图表化的系统性能实时监视器.性能日志和警报管理,可以用于监视CPU使用率.内存使用率.硬盘读写速度.网络速度等 性能分析方法 内存分析方法 内存分析用于判断系 ...
- Powershell(3)
Powershell 可以使用powershell管理的服务 share point, exchange, lync, windows azure, window server, system cen ...
- 自定义Qt组件-通讯模块(P2)
1. 抽象协议AbstractProtocol 抽象协议AbstractProtocol定义CommManager与协议之间的接口.AbstractProtocol中的一些属性(如enabled)用 ...
- java题目练手
大数阶乘 题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=28 代码如下: import java.io.*; import java.ma ...
- ADO.NET的主要对象
ADO.NET主要分为五个对象: 1)Connection对象:用来连接程序与数据库.没有利用连接对象将数据库打开,是无法从数据库中取得数据的.Close和Dispose的区别,Close之后还可以用 ...
- 秒懂JSON.parse()与JSON.stringify()的区别
在网站开发中,Json是最为常见的一种数据交互手段.在使用过程中,常会遇到Json字段串和对象之间进行转换.很多朋友对于JSON.parse() 和JSON.stringify() 的区别,下面为大家 ...
- 树checkbox选择jquery实例
<!DOCTYPE html> <html> <head> <title></title> <script src="htt ...
- Razor 语法糖常规用法
1.隐匿代码表达式 例: @model.name 会将表达式的值计算并写入到响应中,输入时采用html编码方式 2.显示表达式 例:@(model.name)会将输入@model.name字符串 3. ...