简单使用 Lock 锁
Java 5 中引入了新的锁机制——java.util.concurrent.locks 中的显式的互斥锁:Lock 接口,它提供了比synchronized 更加广泛的锁定操作。Lock 接口有 3 个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。
采用 lock 加锁和释放锁的一般形式如下:
Lock lock = new ReentrantLock();//默认使用非公平锁,如果要使用公平锁,需要传入参数true ........ lock.lock(); try { //更新对象的状态 //捕获异常,必要时恢复到原来的不变约束 //如果有return语句,放在这里 finally { lock.unlock(); //锁必须在finally块中释放
可中断锁
ReetrantLock 有两种锁:忽略中断锁和响应中断锁。忽略中断锁与 synchronized 实现的互斥锁一样,不能响应中断,而响应中断锁可以响应中断。
获得响应中断锁的一般形式如下:
ReentrantLock lock = new ReentrantLock(); ........... lock.lockInterruptibly();//获取响应中断锁 try { //更新对象的状态 //捕获异常,必要时恢复到原来的不变约束 //如果有return语句,放在这里 }finally{ lock.unlock(); //锁必须在finally块中释放 }
当用 synchronized 中断对互斥锁的等待时,并不起作用,该线程依然会一直等待,如下面的实例:
public class Buffer { private Object lock; public Buffer() { lock = this; } public void write() { synchronized (lock) { long startTime = System.currentTimeMillis(); System.out.println("开始往这个buff写入数据…"); for (; ; ) {// 模拟要处理很长时间 if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println("终于写完了"); } } public void read() { synchronized (lock) { System.out.println("从这个buff读数据"); } } public static void main(String[] args) { Buffer buff = new Buffer(); final Writer writer = new Writer(buff); final Reader reader = new Reader(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (; ; ) { //等5秒钟去中断读 if (System.currentTimeMillis() - start > 5000) { System.out.println("不等了,尝试中断"); reader.interrupt(); //尝试中断读线程 break; } } } }).start(); // 我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁, // 就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它, // 它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断, // 让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。 } } class Writer extends Thread { private Buffer buff; public Writer(Buffer buff) { this.buff = buff; } @Override public void run() { buff.write(); } } class Reader extends Thread { private Buffer buff; public Reader(Buffer buff) { this.buff = buff; } @Override public void run() { buff.read();//这里估计会一直阻塞 System.out.println("读结束"); } }
将上面代码中 synchronized 的互斥锁改为 ReentrantLock 的响应中断锁,即改为如下代码:
public class BufferInterruptibly { private ReentrantLock lock = new ReentrantLock(); public void write() { lock.lock(); try { long startTime = System.currentTimeMillis(); System.out.println("开始往这个buff写入数据…"); for (; ; ) {// 模拟要处理很长时间 if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println("终于写完了"); } finally { lock.unlock(); } } public void read() throws InterruptedException { lock.lockInterruptibly();// 注意这里,可以响应中断 try { System.out.println("从这个buff读数据"); } finally { lock.unlock(); } } public static void main(String args[]) { BufferInterruptibly buff = new BufferInterruptibly(); final Writer2 writer = new Writer2(buff); final Reader2 reader = new Reader2(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (; ; ) { if (System.currentTimeMillis() - start > 5000) { System.out.println("不等了,尝试中断"); reader.interrupt(); //此处中断读操作 break; } } } }).start(); } } class Reader2 extends Thread { private BufferInterruptibly buff; public Reader2(BufferInterruptibly buff) { this.buff = buff; } @Override public void run() { try { buff.read();//可以收到中断的异常,从而有效退出 } catch (InterruptedException e) { System.out.println("我不读了"); } System.out.println("读结束"); } } class Writer2 extends Thread { private BufferInterruptibly buff; public Writer2(BufferInterruptibly buff) { this.buff = buff; } @Override public void run() { buff.write(); } }
条件变量实现线程间协作
下面将生产者——消费者模型一文中的代码改为用条件变量实现,如下:
class Info { // 定义信息类 private String name = "name";//定义name属性,为了与下面set的name属性区别开 private String content = "content";// 定义content属性,为了与下面set的content属性区别开 private boolean flag = true; // 设置标志位,初始时先生产 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //产生一个Condition对象 public void set(String name, String content) { lock.lock(); try { while (!flag) { condition.await(); } this.setName(name); // 设置名称 Thread.sleep(300); this.setContent(content); // 设置内容 flag = false; // 改变标志位,表示可以取走 condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void get() { lock.lock(); try { while (flag) { condition.await(); } Thread.sleep(300); System.out.println(this.getName() + " --> " + this.getContent()); flag = true; // 改变标志位,表示可以生产 condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void setName(String name) { this.name = name; } public void setContent(String content) { this.content = content; } public String getName() { return this.name; } public String getContent() { return this.content; } } class Producer implements Runnable { // 通过Runnable实现多线程 private Info info = null; // 保存Info引用 public Producer(Info info) { this.info = info; } public void run() { boolean flag = true; // 定义标记位 for (int i = 0; i < 10; i++) { if (flag) { this.info.set("姓名--1", "内容--1"); // 设置名称 flag = false; } else { this.info.set("姓名--2", "内容--2"); // 设置名称 flag = true; } } } } class Consumer implements Runnable { private Info info = null; public Consumer(Info info) { this.info = info; } public void run() { for (int i = 0; i < 10; i++) { this.info.get(); } } } class ThreadCaseDemo { public static void main(String args[]) { Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info); // 生产者 Consumer con = new Consumer(info); // 消费者 new Thread(pro).start(); //启动了生产者线程后,再启动消费者线程 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(con).start(); } }
从以上并不能看出用条件变量的 await()、signal()、signalAll()方法比用 Object 对象的 wait()、notify()、notifyAll()方法实现线程间协作有多少优点,但它在处理更复杂的多线程问题时,会有明显的优势。所以,Lock 和 Condition 对象只有在更加困难的多线程问题中才是必须的。
读写锁
另外,synchronized 获取的互斥锁不仅互斥读写操作、写写操作,还互斥读读操作,而读读操作时不会带来数据竞争的,因此对对读读操作也互斥的话,会降低性能。Java 5 中提供了读写锁,它将读锁和写锁分离,使得读读操作不互斥,获取读锁和写锁的一般形式如下:
ReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.writeLock().lock() //获取写锁
rwl.readLock().lock() //获取读锁
用读锁来锁定读操作,用写锁来锁定写操作,这样写操作和写操作之间会互斥,读操作和写操作之间会互斥,但读操作和读操作就不会互斥。
《Java 并发编程实践》一书给出了使用 ReentrantLock 的最佳时机:
当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用 synchronized。
参考来源:jike.并发新特性—Lock 锁与条件变量