转载地址:https://www.cnblogs.com/whatadiors/p/8013086.html
- 同步控制是并发程序必不可少的重要手段,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 {@Overridepublic 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 {@Overridepublic 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();@Overridepublic 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 {@Overridepublic void run() {try {//Test.handleRead(lock);Test.handleRead(readLock);} catch (InterruptedException e) {e.printStackTrace();}}}private static class TestWriteThread extends Thread {@Overridepublic void run() {try {//Test.handleWrite(lock,new Random().nextInt(100));Test.handleWrite(writeLock,new Random().nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}}} }
这一段代码可以清楚的表达读写锁的作用,如果不使用读写锁,那么整个程序执行时间大概是20s。换用读写锁后只需要2s多,这里读线程完全并行,节省了大部分时间。