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 ...
随机推荐
- python单元测试框架-unittest(二)之断言
断言内容是自动化脚本的重要内容,正确设置断言以后才能帮助我们判断测试用例执行结果. 断言方法 assertEqual(a, b) 判断a==b assertNotEqual(a, b) 判断a!=b ...
- 牛客网Java刷题知识点之什么是单例模式?解决了什么问题?饿汉式单例(开发时常用)、懒汉式单例(面试时常用)、单例设计模式的内存图解
不多说,直接上干货! 什么是单例设计模式? 解决的问题:可以保证一个类在内存中的对象唯一性,必须对于多个程序使用同一个配置信息对象时,就需要保证该对象的唯一性. 如何保证? 1.不允许其他程序用new ...
- Hash表的原理
哈希的概念:Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩 ...
- Linux禁ping
A.临时允许PING操作的命令为:# >/proc/sys/net/ipv4/icmp_echo_ignore_all B.永久允许PING配置方法. /etc/sysctl.conf 中增加一 ...
- Qmake 配置自定义编译过程
Qmake 配置自定义编译过程 需求:动态更换资源文件 在 Windows10 下编写 Qt 项目时,有这样的需求: 程序用到的资源文件可以动态更换而不需要重新编译整个项目 解决方案 0.1 将所有的 ...
- dos命令执行mysql的sql文件
1.cmd进入dos窗口 2.输入cd+ mysql的bin目录后进入bin文件下 3.进入控制台:mysql -uroot -proot 回车 4.选择数据库:use mydata go 回车 5 ...
- 零基础逆向工程36_Win32_10_互斥体_互斥体与临界区的区别
1 引言 讲了第二个内核对象,互斥体.前面已经学过一个内核对象,线程.这节讲两个函数,WaitForSingleObject()和WaitForMultipleObjects().因此这两个函数是根据 ...
- <Android 基础(十七)> ViewPager介绍
介绍 Layout manager that allows the user to flip left and right through pages of data. You supply an i ...
- [UnityShader]点染队列、ZWrite和ZTest
转载自:http://www.myexception.cn/mobile/1902628.html [UnityShader]渲染队列.ZWrite和ZTest 参考链接:http://blog.cs ...
- PHP switch分支语句中省略break后还会执行其他case的原因分析
请分析以下PHP代码的输出结果: $a= 'dog'; switch($a) { case 'cat': echo "\$a is cat"; case 'dog': echo & ...