本文是笔者看了《实战java高并发程序设计》之后加上自己的理解所写的笔记。
之所以直接从并发工具开始,是因为多线程的基础知识,例如多线程创建,常用的方法,以及synchronized,volatile关键字等知识之前学习的时候已经学习过许多遍了,但是java并发包却鲜有接触,这次决定写成博客,系列的总结一下。(之后有时间的话再把前面的知识补充上来,让这个系列更完整一些)
Reentrantlock(重入锁)
简单看一下它的用法:
重入锁基本代码演示:
package thread.thread_util; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 展示重入锁的用法 */ public class Lesson16_ReetrantLock { public static void main(String[] args) { Runnable target = new Runnable() { private Lock lock = new ReentrantLock(); @Override public void run() { lock.lock(); // lock.lock(); try { for (int i = 0; i < 5 ; i++) { System.out.println(Thread.currentThread().getId() + ": " + i); } } finally { lock.unlock(); // lock.unlock(); } } }; Thread t1 = new Thread(target); Thread t2 = new Thread(target); t1.start(); t2.start(); } }
重入到底是什么意思?
相比synchronized关键字,重入锁显示的调用了加锁和解锁的时机,但是为什么要叫重入锁呢?重入二字是什么意思呢?
重入的意思就是同一个线程可以反复进入同一个锁,上面的代码中就算把注释给去掉代码也是可以执行的。
当然这个代码例子仍然不够贴切,谁会没事加两把锁上去呢?但是你可以想象一下递归,我们都知道递归的每次操作都把相关的变量压入了一个栈之中,执行完成就弹出栈,然后再执行上一层函数,这里如果我们进行了加锁操作的话,那么每一层函数在递推的时候就都会加上一层锁,而在回归的时候函数结束,则会释放锁。重入锁的内部有一个计数器,每加一个锁,计数器就加1,释放一个锁,计数器就减1,只有当计数器为0时,锁才能被释放。
我们以经典的斐波那契数作为例子来展示一下。
深入理解“重入”代码演示:
package thread.thread_util; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 递归中的重入锁 */ public class Lesson16_ReentrantLock02 { public static void main(String[] args) { Runnable target = new Runnable() { private Lock lock = new ReentrantLock(); @Override public void run() { System.out.println(Thread.currentThread().getId()); System.out.println("最终结果为: " + fibonacci(3)); } public int fibonacci (int n) { try { lock.lock(); System.out.println(Thread.currentThread().getId() + "获得了锁"); if( n == 1 ) return 1; if( n == 2 ) return 1; return fibonacci(n-1) + fibonacci(n-2); } finally { lock.unlock(); System.out.println(Thread.currentThread().getId() + "释放了锁"); } } }; Thread thread = new Thread(target); thread.start(); } }
结果:
11
11获得了锁
11获得了锁
11释放了锁
11获得了锁
11释放了锁
11释放了锁
最终结果为: 2
我们可以看到这里总共获得了3次锁,释放了3次锁,整个锁才会被最终释放。
你可以再开一个线程进行验证,结果会类似下面这样:
11 11获得了锁 11获得了锁 11释放了锁 11获得了锁 11释放了锁 11释放了锁 最终结果为: 2 12 12获得了锁 12获得了锁 12释放了锁 12获得了锁 12释放了锁 12释放了锁 最终结果为: 2
重入锁的好基友:Condition对象
Condition的功能和Object.wait()与Object.notify()比较相似,它让我们可以控制某个线程什么时候开始等待,什么时候被唤醒,而不是由jvm来决定。
条件对象代码演示:
package thread.thread_util; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 展示条件对象的基本用法 * await * signal * signalAll */ public class Lesson17_Condition01 implements Runnable{ public static Lock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); @Override public void run() { try { lock.lock(); condition.await(); System.out.println("Thread is going on"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws Exception{ Runnable target = new Lesson17_Condition01(); Thread thread = new Thread(target); thread.start(); Thread.sleep(2000); lock.lock(); condition.signal(); lock.unlock(); } }
这里在Runnable对象中,将对象锁住并让线程进入了休眠状态
主线程中进行了2秒钟的休眠,这2秒钟内子线程也无法执行,因为被“await()”了,直到2秒钟后,主线程再次开始执行,对线程进行了唤醒(signal)操作,唤醒操作也要进行加锁操作。
关于重入锁还有几点需要说明:
- 中断响应:对于synchronized来说,线程只有两种状态,要么获得锁开始执行,要么在队列中等待。
而对于重入锁来说,还存在另外一种状态,那就是中断线程,也就是把等待队列中的线程中断,那么这个中断有什么用呢?
比如说如果出现了死锁的情况,两个线程都在等待对方释放资源,那么这个时候如果我主动的中断一个线程,那么这个线程所持有的资源就被释放啦。我们来看看代码
重入锁中断响应代码演示:
package thread.thread_util; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 展示重入锁的中断响应功能 * 首先构造一个死锁 * 然后中断某个线程,释放资源 * 最后另外一个线程就可以获得释放的资源,完成线程的任务 */ public class Lesson16_ReentrantLock03 implements Runnable{ private static ReentrantLock lock1 = new ReentrantLock(); private static ReentrantLock lock2 = new ReentrantLock(); private int flag = 0; public void setFlag(int val) { this.flag = val; } @Override public void run() { try { if(flag == 1) { lock1.lockInterruptibly(); Thread.sleep(1000); lock2.lockInterruptibly(); System.out.println("Thread1 is going on "); } else { lock2.lockInterruptibly(); Thread.sleep(1000); lock1.lockInterruptibly(); System.out.println("Thread2 is going on "); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } } public static void main(String[] args) { Lesson16_ReentrantLock03 target1 = new Lesson16_ReentrantLock03(); Lesson16_ReentrantLock03 target2 = new Lesson16_ReentrantLock03(); target1.setFlag(1); target2.setFlag(2); Thread thread1 = new Thread(target1); Thread thread2 = new Thread(target2); thread1.start(); thread2.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } }
- lockInterruptibly : 虽然锁住了对象,但是可以响应中断,发现中断之后就释放锁
Acquires the lock unless the current thread is interrupted. (文档说明)
最后线程2进行了中断,代码中的“else”里面的代码中的lockInterruptibly()方法就会使线程2 释放lock2并放弃对lock1的请求。
运行结果:
Thread1 is going on java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at thread.thread_util.Lesson16_ReentrantLock03.run(Lesson16_ReentrantLock03.java:33) at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0
锁申请等待时限(tryLock)
这是主动的中断,还有另外一种更为简便的方法,就是给锁的申请添加时限,如果规定时间内我还没得到锁,那我就不要这把锁了
trylock() 代码演示:
package thread.thread_util; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class Lesson16_ReentrantLock04 implements Runnable{ private static ReentrantLock lock = new ReentrantLock(); @Override public void run () { try { if(lock.tryLock(3,TimeUnit.SECONDS)){ System.out.println(Thread.currentThread().getName() + " get lock successful"); Thread.sleep(4000); } else { System.out.println(Thread.currentThread().getName() + " get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Lesson16_ReentrantLock04 target = new Lesson16_ReentrantLock04(); Thread t1 = new Thread(target); Thread t2 = new Thread(target); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
这里线程1先获得了锁,然后睡眠6秒钟(sleep并不会释放锁),所以线程2在3秒钟内都无法获得所最后进入else代码段。
最后结果:
线程1 get lock successful
线程2 get lock failed
关于重入锁和条件对象暂时就写到这里。