- 同步控制是并发程序必不可少的重要手段,synchronized关键字就是一种简单的控制方式,除此之外,JDK内部并发包中也也提供了Lock接口,该接口中提供了lock()方法和unLock()方法对显式加锁和显式释放锁操作进行支持。
ReentrantLock(重入锁)
重入锁可以完全替代synchronized关键字,在jdk5早期版本中重入锁的性能远远好于synchronized,但从JDK6开始JDK在synchronized中做了大量的优化,是的两者的性能差距不大,
public class Test { public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; public static void increase() { try { lock.lock(); i++; }finally { lock.unlock(); } } public static void main(String[] args) throws Exception { Thread t1 = new TestThread(); Thread t2 = new TestThread(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } } class TestThread extends Thread { @Override public void run() { for (int j = 0; j < 1000; j++) { Test.increase(); } } }
从这段代码可以看到与synchronized相比,重入锁有着显示的操作过程,我们需要手动定义核实加锁,核实释放锁,但也就是因为这样,重入锁对逻辑的控制灵活性要好于synchronized。
公平锁
大多数情况下锁的申请都是非公平的。如一个线程1先请求了锁A,然后线程2页也请求了锁A,那么当锁A可用时,是线程1可以获得锁还是线程2是不一定的,系统只是会从这个锁的等待队列中随机挑选一个。
重入锁允许我们对其公平性进行设置。公平锁的一大特点是:它不会产生饥饿现象。只要排队,最终你就可以获得资源。可以使用如下构造函数创建公平锁:
public ReentrantLock(boolean fair)
当参数fair为true,表示锁的公平的,当然由于公平所需要维护有序队列,因此公平锁的实现成本比较高,性能相对也底下,所以默认都是非公平锁
public class Test { public static ReentrantLock lock = new ReentrantLock(true); public static void main(String[] args) throws Exception { Thread t1 = new TestThread(); Thread t2 = new TestThread(); t1.start(); t2.start(); } } class TestThread extends Thread { @Override public void run() { while(true) try { Test.lock.lock(); System.out.println(Thread.currentThread().getName()+"获得锁"); } finally{ Test.lock.unlock(); } } }
可以看到如上代码制定公平锁之后,两个线程交替获得锁
Thread-1获得锁 Thread-0获得锁 Thread-1获得锁 Thread-0获得锁 Thread-1获得锁 Thread-0获得锁 Thread-1获得锁 Thread-0获得锁 Thread-1获得锁 Thread-0获得锁 Thread-1获得锁 Thread-0获得锁 Thread-1获得锁 ...
ReentrantLock的一些其他方法:
public boolean tryLock(); //使用此方法,当前线程会尝试获得锁,如果锁未被其他线程占用,则申请锁成功,返回true,否则会立即返回false.这种模式不会引起线程等待,因此也不会产生死锁。
public boolean tryLock(long timeout, TimeUnit unit) //在这里tryLock接收两个参数,一个表示等待时长,一个表示计时单位,表示在一段时间范围内如果得到锁就返回true,否则直接返回false,不在继续等待锁。
public class Test implements Runnable { public static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()); System.out.println("get lock success"); Thread.sleep(6000); } else { System.out.println(Thread.currentThread().getName()); System.out.println("get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public static void main(String args[]) { Test timeLock = new Test(); Thread thread1 = new Thread(timeLock); Thread thread2 = new Thread(timeLock); thread1.start(); thread2.start(); } }
输出结果:
Thread-0 get lock success Thread-1 get lock failed
其他:
关于重入锁的具体原理及这部分源码的分析可以看下这篇文章http://www.cnblogs.com/xrq730/p/4979021.html
ReentrantReadWriteLock(读写锁)
ReadWriteLock是JDK5开始提供的读写分离锁。读写分离开有效的帮助减少锁的竞争,以提升系统性能。用锁分离的机制避免多个读操作线程之间的等待。
读写锁的访问约束:
- 读-读不互斥:读读之间不阻塞
- 读-写互斥:读堵塞写,写也阻塞读
- 写-写互斥:写写阻塞
如果在一个系统中读的操作次数远远大于写操作,那么读写锁就可以发挥明显的作用,提升系统性能
public class Test { private static Lock lock = new ReentrantLock(); private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private static Lock readLock = reentrantReadWriteLock.readLock(); private static Lock writeLock = reentrantReadWriteLock.writeLock(); private static int value; public static Object handleRead(Lock lock) throws InterruptedException { try { lock.lock(); Thread.sleep(1000);// 模拟读操作 System.out.println("读操作:" + value); return value; } finally { lock.unlock(); } } public static void handleWrite(Lock lock, int index) throws InterruptedException { try { lock.lock(); Thread.sleep(1000);// 模拟写操作 System.out.println("写操作:" + value); value = index; } finally { lock.unlock(); } } public static void main(String[] args) throws Exception { TestReadThread testReadThread = new TestReadThread(); TestWriteThread testWriteThread = new TestWriteThread(); for (int i = 0; i < 18; i++) { new Thread(testReadThread).start(); } for (int i = 18; i < 20; i++) { new Thread(testWriteThread).start(); } } private static class TestReadThread extends Thread { @Override public void run() { try { //Test.handleRead(lock); Test.handleRead(readLock); } catch (InterruptedException e) { e.printStackTrace(); } } } private static class TestWriteThread extends Thread { @Override public void run() { try { //Test.handleWrite(lock,new Random().nextInt(100)); Test.handleWrite(writeLock,new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
这一段代码可以清楚的表达读写锁的作用,如果不使用读写锁,那么整个程序执行时间大概是20s。换用读写锁后只需要2s多,这里读线程完全并行,节省了大部分时间。