线程应用:(七)Lock & Condition同步通信
一、线程锁:Lock 代替synchronized
Lock的作用类似于传统线程模型中的synchronized,更体现面向对象的思想,两个线程执行的代码片段要实现同步互斥的效果,必须要用同一个Lock对象。
1)ReentrantLock
//用Lock替换synchronized互斥 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadTest1 { public static void main(String[] args) { new ThreadTest1().init(); } //起了两个线程,一个一直输出"AAAAAA",一个一直输出"BBBBBB",但调用方法的对象是同一个 public void init(){ final Outputer outputer = new Outputer(); new Thread(new Runnable() { @Override public void run() { while(true){ outputer.output("AAAAAA"); } } }).start(); new Thread(new Runnable() { @Override public void run() { while(true){ outputer.output("BBBBBB"); } } }).start(); } } class Outputer { Lock lock = new ReentrantLock(); //这里两个线程调用方法的是同一个对象,所以是用的同一把锁 public void output(String name){ //synchronized (this) { lock.lock(); try{ int len = name.length(); for(int i=0;i<len;i++){ System.out.print(name.charAt(i)); } System.out.println(""); }finally{ lock.unlock(); //无论如何都要释放锁 } //} } }
锁中的代码出现异常可能会出现死锁,最好finally释放锁。
2)读写锁 ReentrantReadWriteLock
分为读锁和写锁,使用读写锁可以提高效率,还能实现互斥。ReentrantLock是互斥锁,一个线程获得后,别的线程无论读写都不允许。
多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。
规则:
读中可以有读,
读中不能有写,
写中不能有读,写中不能有写。
只是读的时候不用互斥,用读锁即可,不用synchronized。
/** * 起3个线程不停写,3个线程不停读 */ public class ReadWriteLockTest { public static void main(String[] args) { final Queue q = new Queue(); for(int i=0;i<3;i++){ new Thread(){ @Override public void run() { while(true){ q.get(); } } }.start(); new Thread(){ @Override public void run() { while(true){ q.put(new Random().nextInt(10000));; } } }.start(); } } } /** *读时可以有读,读时不能有写 *写时不能有读,也不能有写。 */ class Queue { private Object data = null; //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据 private ReadWriteLock rw1 = new ReentrantReadWriteLock(); //创建一个读写锁 public void get() { rw1.readLock().lock(); //从读写锁中获得读锁 try { System.out.println(Thread.currentThread().getName() + "开始读数据"); Thread.sleep((long)(Math.random()*1000)); System.out.println(Thread.currentThread().getName() + "已读完数据" + data); } catch (InterruptedException e) { e.printStackTrace(); } finally { rw1.readLock().unlock(); //释放读锁 } } public void put(Object data) { rw1.writeLock().lock(); //获得写锁 try { System.out.println(Thread.currentThread().getName() + "开始写数据"); Thread.sleep((long)(Math.random()*1000)); this.data = data; System.out.println(Thread.currentThread().getName() + "已写数据" + data); } catch (InterruptedException e) { e.printStackTrace(); } finally { rw1.writeLock().unlock(); //释放写锁 } } }
利用读写锁来模拟缓存案例。(结合到单例模式下)
/** * 模拟缓存 * 读和写间、写和写间互斥,又可以并发的读,性能高 */ public class CacheDemo { private Map<String, Object> cache = new HashMap<String, Object>(); private ReadWriteLock rw1 = new ReentrantReadWriteLock(); //读写锁 public Object getData(String key){ rw1.readLock().lock(); //读1,多个读时应该要可以并发,不会造成数据的破坏 Object value = null; try { value = cache.get(key); if(value == null){ //如果缓存中没数据,要重新赋值,要先写锁 rw1.readLock().unlock(); //先释放读1 rw1.writeLock().lock(); //写2,如果有多个线程到这一步,也只有一个能获得写锁 try{ if(value==null){ //赋值前再判断一次 value = "aaa"; //模拟取数据库取数据 } } finally { rw1.writeLock().unlock(); //写2 } rw1.readLock().lock(); //读3 } } finally { rw1.readLock().unlock(); //读3 } return value; } }
二、Condition代替wait/notify实现同步通信
wait、notify必须和synchronized一起用,并且synchronized和wait、notify要用同一个锁对象。Condition可以用来代替wait、notify实现同步通信,Condition是在Lock对象的基础上使用的。
使用Lock、Condition代替wait、notify、synchronized,案例代码如下。
public class ConditionTest { public static void main(String[] args) throws InterruptedException { final Business business = new Business(); new Thread(new Runnable() { @Override public void run() { for(int i=1;i<=50;i++){ //循环50轮 try { business.forSub(i); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); for(int i=1;i<=50;i++){ //循环50轮 business.forMain(i); } } } class Business { Lock lock = new ReentrantLock(); //用Lock代替synchronized private boolean subGo = true; Condition condition = lock.newCondition(); //Condition基于锁之上 public void forSub(int i) throws InterruptedException{ lock.lock(); try{ while(!subGo){ //防止虚假唤醒 //this.wait(); condition.await(); //别写成condition.wait();,是有区别的 } for(int j=1;j<=10;j++){ System.out.println("subThread of, 第"+i+"轮, 序列为:"+j); } subGo = false; condition.signal(); //this.notify(); }finally{ lock.unlock(); } } public void forMain(int i) throws InterruptedException{ lock.lock(); try{ while(subGo){ condition.await(); //this.wait(); } for(int j=1;j<=100;j++){ System.out.println("mainThread of, 第"+i+"轮, 序列为:"+j); } subGo = true; condition.signal(); //this.notify(); }finally{ lock.unlock(); } } }
一个锁内部可以有多个Condition,可实现多路等待和唤醒。
只用一个Condition的不足:假如有5个线程用来存馒头,1个线程用来取馒头,当笼屉馒头满的时候,5个存的线程等待,当1个取的线程取了一个馒头后,唤醒了5个存线程中的一个继续存,存完后,本来应该要唤醒取的线程,但如果这里只有1个Condition,就不能进行区分,可能会唤醒另外4个存线程中的一个。
当有三个线程要按顺序交替进行,代码如下。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreeConditionTest { public static void main(String[] args) throws InterruptedException { final ConditionBusiness business = new ConditionBusiness(); new Thread(new Runnable() { @Override public void run() { for(int i=1;i<=50;i++){ //循环50轮 try { business.sub2(i); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { for(int i=1;i<=50;i++){ //循环50轮 try { business.sub3(i); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); for(int i=1;i<=50;i++){ //循环50轮 business.forMain(i); } } } //用于三个线程的同步通信,主线程-sub2-sub3的顺序交替进行 class ConditionBusiness { Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); private int flag = 1; //设定1先走 public void forMain(int i) throws InterruptedException{ lock.lock(); try{ while(flag != 1){ condition1.await(); } for(int j=1;j<=10;j++){ System.out.println("main, 第"+i+"轮, 序列为:"+j); } flag = 2; condition2.signal(); //线程1走完唤醒线程2 }finally{ lock.unlock(); } } public void sub2(int i) throws InterruptedException{ lock.lock(); try{ while(flag != 2){ condition2.await(); } for(int j=1;j<=10;j++){ System.out.println("sub2, 第"+i+"轮, 序列为:"+j); } flag = 3; condition3.signal(); //线程2走完唤醒线程3 }finally{ lock.unlock(); } } public void sub3(int i) throws InterruptedException{ lock.lock(); try{ while(flag != 3){ condition3.await(); } for(int j=1;j<=10;j++){ System.out.println("sub3, 第"+i+"轮, 序列为:"+j); } flag = 1; condition1.signal(); //线程3走完唤醒线程1 }finally{ lock.unlock(); } } }