【线程锁 Synchronized与ReentrantLock 示例】
前言:
之前一直对线程锁不理解,去网上找了很多讲解Synchronized关键字和ReentrantLock的帖子,大致了解了用法,写个小程序记录一下。有不足和错误欢迎留言评论,大家一起学习进步。
一、为什么要用线程锁
Java多线程同时处理任务会显著提高工作效率。但是,如果多个线程同时操作某一资源,可能会导致数据的不同步,引发错误。因此对于某些资源我们要对线程进行控制,同一时间只允许单个线程进行操作。这时我们就需要使用线程锁。
场景:
书店生产书,直到书总量达到10本,顾客买书,总共要买10本
书店:书店只要书不足10本就一直生产,直到书店内书个数达到10,停止生产,如果此时有书被顾客买走就继续生产,直至存货10本...
顾客:只要书店有书就从书店购买,没有书就停止购买,等有书产出就继续,直到购买10本
二、Synchronized关键字实现线程锁
书店:
public class SynchronizedProviderThread extends Thread { public static List<String> books=new ArrayList<String>(); public static int MAX_BOOK_NUM=10; public void run(){ try{ while(!Thread.interrupted()){ synchronized (books){ if(books.size()>=10){ System.out.println("书已达最大总量:"+books.size()); books.wait(); System.out.println("已经有书被买走了,可以继续生产了"); }else{ books.add("BOOK"); System.out.println("生产书一本。。。。当前书总量为:"+books.size()); books.notifyAll(); } } } }catch(Exception e){ e.printStackTrace(); } } }
顾客:
public class SynchronizedConsumerThread extends Thread{ public int sellCount=0; public void run(){ try{ while(sellCount<10){ synchronized (SynchronizedProviderThread.books){ if(SynchronizedProviderThread.books.size()>0){ sellCount++; SynchronizedProviderThread.books.remove(SynchronizedProviderThread.books.get(0)); System.out.println("购买书一本。。。当前书总量为:"+ SynchronizedProviderThread.books.size()+" 已购买"+sellCount+"本书"); SynchronizedProviderThread.books.notifyAll(); }else{ System.out.println("书已售空,无法继续购买"); SynchronizedProviderThread.books.wait(); System.out.println("已经有书了,可以开始购买了"+" 当前书量:"+ SynchronizedProviderThread.books.size()); } } } }catch (Exception e){ e.printStackTrace(); } } }
总线程:
public class SychronizedTestMain { public static void main(String args[]){ SynchronizedProviderThread provider=new SynchronizedProviderThread(); SynchronizedConsumerThread consumer1=new SynchronizedConsumerThread(); provider.start(); consumer1.start(); } }
运行结果:
书已售空,无法继续购买 生产书一本。。。。当前书总量为:1 已经有书了,可以开始购买了 当前书量:1 购买书一本。。。当前书总量为:0 已购买1本书 书已售空,无法继续购买 生产书一本。。。。当前书总量为:1 生产书一本。。。。当前书总量为:2 生产书一本。。。。当前书总量为:3 生产书一本。。。。当前书总量为:4 生产书一本。。。。当前书总量为:5 生产书一本。。。。当前书总量为:6 生产书一本。。。。当前书总量为:7 生产书一本。。。。当前书总量为:8 生产书一本。。。。当前书总量为:9 生产书一本。。。。当前书总量为:10 书已达最大总量:10 已经有书了,可以开始购买了 当前书量:10 购买书一本。。。当前书总量为:9 已购买2本书 购买书一本。。。当前书总量为:8 已购买3本书 购买书一本。。。当前书总量为:7 已购买4本书 购买书一本。。。当前书总量为:6 已购买5本书 购买书一本。。。当前书总量为:5 已购买6本书 购买书一本。。。当前书总量为:4 已购买7本书 购买书一本。。。当前书总量为:3 已购买8本书 购买书一本。。。当前书总量为:2 已购买9本书 购买书一本。。。当前书总量为:1 已购买10本书 已经有书被买走了,可以继续生产了 生产书一本。。。。当前书总量为:2 生产书一本。。。。当前书总量为:3 生产书一本。。。。当前书总量为:4 生产书一本。。。。当前书总量为:5 生产书一本。。。。当前书总量为:6 生产书一本。。。。当前书总量为:7 生产书一本。。。。当前书总量为:8 生产书一本。。。。当前书总量为:9 生产书一本。。。。当前书总量为:10 书已达最大总量:10
分析:
书店、顾客中被synchronized标注的方法同步运行,为books添加了线程锁监控,当多个线程同时要调用books时,要先争夺books的线程锁,由JVM按照一定的选举规则从中选出一个线程获得线程锁,执行synchronized()中的部分,执行完毕后会释放线程锁,其余线程继续争锁......这样一来同一时间只有一个线程调用books。
books.wait()方法:暂停当前线程,开始等待,并释放线程锁,直到其他线程调用books.notifyAll()或books.notify()时,重新争夺线程锁,如果争到线程锁,就从刚刚books.wait()暂停处继续运行。
books.notifyAll():释放线程锁,并告知所有正在等待books锁的线程不再等待,开始争夺锁。
books.notify():释放当前线程锁,并告知一个正在等待books锁的线程不再等待,获取锁,其余等待的线程继续等待,直到再有notify()或notifyAll()被调用。
不足:当有书生产出时,会告知正在等待的线程不再等待,开始争夺锁,这时候可能其他书店线程在争夺books的锁,因此有可能下一个获取到锁的线程又是书店,而不是顾客,所以会导致书店已经连续生产好几本书了,而有的顾客还在等待。同理,顾客买了一本书之后,同样也会唤醒其他等待顾客的线程,而不是第一时间告知书店可以继续生产书了。
可改善:当有书生产时,只告知正在等待的顾客不再等待。当顾客购买书后只告知正在等待的书店不再等待。
ReentrantLock就可以实现这样的效果
三、ReentrantLock实现线程锁
Lock:
public class LockForSell { public static ReentrantLock reentrantLock=new ReentrantLock(true); public static Condition providerCondition=reentrantLock.newCondition(); public static Condition consumerCondition=reentrantLock.newCondition(); }
书店:
public class ReentrantLockProviderThread extends Thread{ public static List<String> books=new ArrayList<String>(); public static int MAX_BOOK_SIZE=10; public void run(){ while(!Thread.interrupted()){ LockForSell.reentrantLock.lock(); try{ if(books.size()>=MAX_BOOK_SIZE){ System.out.println("书已达最大总量:"+MAX_BOOK_SIZE+" 无法继续生产"); LockForSell.providerCondition.await();//生产线程释放锁,开始等待 System.out.println("已经有顾客买书了,可以继续生产"); }else{ books.add("BOOK"); System.out.println("生产书一本,当前书总量:"+books.size()); LockForSell.consumerCondition.signal(); } }catch(Exception e){ e.printStackTrace(); }finally{ LockForSell.reentrantLock.unlock(); } } } }
顾客:
public class ReentrantLockConsumerThread extends Thread{ public int sellCount=0; public void run(){ while(sellCount<10){ LockForSell.reentrantLock.lock(); try{ if(ReentrantLockProviderThread.books.size()<=0){ System.out.println("书已售空 无法继续购买"); LockForSell.consumerCondition.await(); System.out.println("有新书了,可以继续购买"); }else{ sellCount++; ReentrantLockProviderThread.books.remove(ReentrantLockProviderThread.books.get(0)); System.out.println("购买书一本,当前书量:"+ReentrantLockProviderThread.books.size()+" 已购买"+sellCount+"本书"); LockForSell.providerCondition.signal(); } }catch(Exception e){ e.printStackTrace(); }finally{ LockForSell.reentrantLock.unlock(); } } } }
总线程:
public class ReentrantLockMainTest { public static void main(String args[]){ LockForSell lockForSell=new LockForSell(); ReentrantLockProviderThread providerThread=new ReentrantLockProviderThread(); ReentrantLockConsumerThread consumerThread=new ReentrantLockConsumerThread(); providerThread.start(); consumerThread.start(); } }
运行结果:
书已售空 无法继续购买 生产书一本,当前书总量:1 有新书了,可以继续购买 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买1本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买2本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买3本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买4本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买5本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买6本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买7本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买8本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买9本书 生产书一本,当前书总量:2 购买书一本,当前书量:1 已购买10本书 生产书一本,当前书总量:2 生产书一本,当前书总量:3 生产书一本,当前书总量:4 生产书一本,当前书总量:5 生产书一本,当前书总量:6 生产书一本,当前书总量:7 生产书一本,当前书总量:8 生产书一本,当前书总量:9 生产书一本,当前书总量:10 书已达最大总量:10 无法继续生产
参考内容:
https://www.cnblogs.com/cisol/p/6673190.html
https://www.jianshu.com/p/96c89e6e7e90
https://www.cnblogs.com/csuwater/p/5411693.html