前言:在上面的博客说了 synchronized 的一些用法,下面我们再来看看 lock,这个出现频率也是非常高的一个。
前面说了 synchronized 有锁对象和锁类对象,当某个线程获取锁其他线程必须等待执行完毕才可继续进行,比如线程 A 先获取锁,但是出现异常导致的后果就是线程 B 无法获取锁,会出现死锁的情况(),那么我们一起看看 Lock 是如何解决的。lock 有 4 种方式来获取锁
1:lock.lock() 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁。此种模式和 synchronized 一样但是不会出现死锁
- public class Lock1 {
- static int value = 0;
- static Lock lock = new ReentrantLock();
- static class Task1 implements Runnable {
- public void run() {
- System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
- lock.lock();
- try {
- for (int i = 0; i < 1000000; i++) {
- value++;
- }
- System.out.println(value);
- } finally {
- lock.unlock();
- }
- }
- }
- static class Task2 implements Runnable {
- public void run() {
- System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
- lock.lock();
- try {
- for (int i = 0; i < 1000000; i++) {
- value++;
- }
- System.out.println(value);
- } finally {
- lock.unlock();
- }
- }
- }
- public static void main(String[] args) {
- ExecutorService service= Executors.newCachedThreadPool();
- service.execute(new Task1());
- service.execute(new Task2());
- service.shutdown();
- }
- }
输出结果很明显其中一个 value 是 1000000,一个是 2000000,效果和 synchronized 是一样的。但是如果我们去掉 lock 以后的结果呢,很明显会错,如下图这样
为啥会出现这样情况呢,是由于 cpu 速度极快,每次处理完毕之后并没有立即把数值放入 Java 内存中,而是放在写缓存区,然后由写缓存区同步到 Java 内存中,这样一样,如果线程 1 计算结果是 2,但是还是到内存中,导致线程 2 以为 value 值还是 1 所以会重复计算,还有从结果我们也可以看出 value 值并不是 100000 说明 2 个线程是同步执行的。
2:lock.tryLock();
这个方法和 synchronized 有所不同,synchronized 和 lock 都会等待直到获取锁。如果获取了锁立即返回 true,如果别的线程正持有锁,立即返回 false;当然我们可以利用 while 循环一直等待,直到获取锁然后进行。代码如下
- public class Lock2 {
- public static void main(String[] args) {
- final Lock lock = new ReentrantLock();
- Thread t1 = new Thread(new Runnable() {
- public void run() {
- String tName = Thread.currentThread().getName();
- while (!lock.tryLock()) {
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("等待获取锁");
- }
- try {
- for (int i = 0; i < 5; i++) {
- System.out.println(tName + ":" + i);
- }
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- });
- Thread t2 = new Thread(new Runnable() {
- public void run() {
- String tName = Thread.currentThread().getName();
- while (!lock.tryLock()) {
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("等待获取锁");
- }
- try {
- for (int i = 0; i < 5; i++) {
- System.out.println(tName + ":" + i);
- }
- } catch (Exception e) {
- System.out.println(tName + "出错了!!!");
- } finally {
- System.out.println(tName + "释放锁!!");
- lock.unlock();
- }
- }
- });
- t1.start();
- t2.start();
- }
- }
3:lock.trylock(long time, TimeUnit unit) 如果获取了锁定立即返回 true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回 true,如果等待超时,返回 false;unit 是 time 的时间单位比如 TimeUnit.SECONDS 就是表示秒
4:lock.lockInterruptibly() 如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。也就是说如何线程没有被中断和 lock.lock() 的作用一样。但是如何线程被中断了,那么此时这个线程不会有任何的响应,想象这么一个场景,线程 A 和线程 B 同时执行任务,但是必须等待线程 B 先执行,但是执行过程中突然线程 A 突然被中断,那么这个时候就可能出现死锁,哪怕是在 finally 中加入 unlock,这个时候我们就要采用 lockInterruptibly() 了。代码如下
- public class Lock3 {
- public static void main(String[] args) {
- final Lock lock = new ReentrantLock();
- final Thread thread1 = new Thread(new Runnable() {
- public void run() {
- try {
- TimeUnit.SECONDS.sleep(2);
- System.out.println("等待被中断");
- lock.lockInterruptibly();
- } catch (InterruptedException e) {
- System.out.println("我被中断了");
- } finally {
- lock.unlock();
- }
- }
- });
- Thread thread2 = new Thread(new Runnable() {
- public void run() {
- lock.lock();
- try {
- TimeUnit.SECONDS.sleep(4);
- } catch (InterruptedException e) {
- }
- thread1.interrupt();
- System.out.println("线程1已经被中断");
- }
- });
- thread1.start();
- thread2.start();
- }
- }
在开发中我们最好的愿望就是写的时候加锁,但是读的时候不加锁这样会大大的提升效率,但是采用 synchronized 却无法满足我们的要求,如果在读的方法面前加锁那么所有的读都需要等待,如果不加锁的话那么如果现在 A,B2 个线程读取,C 线程写入可能导致的后果就是 A,B2 个线程取得数据不一致,明明同一种业务场景但是获取值却不同。好了 lock 的读锁和写锁帮助我们实现这种功能。
- public class ReadWriteLockTest {
- private int value = 0;
- ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- public void add(int value) {
- Lock writeLock = readWriteLock.writeLock();
- writeLock.lock();
- try {
- TimeUnit.SECONDS.sleep(3);
- System.out.println("添加开始时间:" + new Date());
- this.value += value;
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- writeLock.unlock();
- }
- }
- public void getValue() {
- Lock readLock = readWriteLock.readLock();
- readLock.lock();
- try {
- TimeUnit.SECONDS.sleep(1);
- System.out.println("获取开始时间:" + new Date());
- System.out.println(value);
- } catch (InterruptedException e) {
- } finally {
- readLock.unlock();
- }
- }
- public static void main(String[] args) {
- final ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
- Runnable task1 = new Runnable() {
- public void run() {
- readWriteLockTest.add(100);
- }
- };
- Runnable task2 = new Runnable() {
- public void run() {
- readWriteLockTest.getValue();
- }
- };
- ExecutorService service = Executors.newCachedThreadPool();
- for (int i=0;i<2;i++){
- service.execute(task1);
- }
- for (int i=0;i<2;i++){
- service.execute(task2);
- }
- for (int i=0;i<2;i++){
- service.execute(task1);
- }
- service.shutdown();
- }
- }
运行结果:
从这个结果我们很明显的可以总结读锁和写锁
第一:如果执行写的时候,读和写必须等待
第二:如果执行读的时候,写必须等待,而读却不用等待
也就是说读和写必须存在先后顺序,不管是先读还是先写。
3:总结
相同点:lock 能实现 synchronized 所有可以实现的
不同点:
1:lock 不容易出现死锁,而 synchronized 如果某个线程出现异常就会产生死锁
2:lock 更加灵活,可以通过 tryLock 来验证是否获取锁,在线程中断也同样可以处理
3:lock 有读写锁在并发量大的时候具有很大的优势,因为读的情况一般会比写多很多
来源: http://www.cnblogs.com/LipeiNet/p/6532842.html