java基础: synchronized与Lock的区别
Posted myseries
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java基础: synchronized与Lock的区别相关的知识,希望对你有一定的参考价值。
主要区别
1. 锁机制不一样:synchronized是java内置关键字,是在JVM层面实现的,系统会监控锁的释放与否,lock是JDK代码实现的,需要手动释放,在finally块中释放。可以采用非阻塞的方式获取锁;
2. 性能不一样:资源竞争激励的情况下,lock性能会比synchronize好,竞争不激励的情况下,synchronize比lock性能好,synchronize会根据锁的竞争情况,从偏向锁-->轻量级锁-->重量级锁升级,而且编程更简单
3. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
4. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
5. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
6. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
7. 用法不一样:synchronize可以用在代码块上,方法上。lock只能写在代码里,不能直接修改方法。
8. Lock可以绑定多个condition。例如,ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个,要么唤醒全部线程。
分别讨论synchronized、Lock
一:synchronized
加同步格式:
synchronized(需要一个任意的对象(锁)){
代码块中放操作共享数据的代码;
}
synchromized缺陷
synchronized是java中的一个关键字,也就是说是java语言的内置的特性。
如果一个代码块被synchronized修饰,当一个线程获取了对应的锁,并执行代码块时,其他线程只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
(1)获取锁的线程执行完了改代码块,然后线程释放对锁的占有
(2)线程执行发生异常,此时JVM会让线程自动释放锁。
例子1:
如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
例子2:
当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
总的来说,也就是说Lock提供了比synchronized更多的功能。
二:LOCK
首先要说明的就是,通过查看LOCK 的源码可知道,lock是一个接口
lock接口中每个方法的使用:lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的。 unLock()方法是用来释放锁的。 四个获取锁方法的区别:
(1)lock()方法时平常使用的最多的一个方法,就是用来获取锁的,如果锁已经被其他线程获取,则进行等待。
由于在前面讲到如果采用lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放掉,防止死锁的发生。
(2)tryLock()方法是有返回值的,他表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已经被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在哪里等待
(3)tryLock(long time, TimeUnit unit)方法和tryLock()方法时类似的,只不过区别在于这个方法在拿不到锁时会等待一定时间,在时间限制之内如果还是拿不到锁,就返回false。如果一开始就拿到锁或者在等待期间内拿到了锁,则就返回true。
(4)lockinterruptibly()方法比较特殊,当通过这个方法区获取锁时,如果线程正在等待获取锁,则这个线程能够相应中断,即中断线程的等待状态。也就是说,当连个线程同时通过lock.lockinterruputibly()向获取某个锁时,假如此时线程A获取到了锁,而线程B只有等待,那么对线程调用threadB.interrupt()方法能够中断线程B的等待过程。
注意:当一个线程获取了锁之后,是不会被interrupt()方法中断的
因此当通过lockinterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以相应中断的
而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
ReentrantLock
直接使用lock接口的话,我们需要实现很多方法,不太方便,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入锁”。
ReadWriteLock也是一个接口,在它里面只定义了两个方法:
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。ReentrantReadWriteLock实现了ReadWriteLock接口。
ReentrantReadWriteLock
ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的两个方法:readlock()和writelock用来获取读锁和写锁
注意:
不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
LOCK和SYNCHRONIZED的选择
(1)lock是一个接口,而synchronized是java的关键字,synchronized是内置的语言实现;
(2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock()时需要在finally块中释放锁;
(3)lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不讷讷狗狗响应中断
(4)通过lock可以知道有没有成功获取锁,而synchronized却无法办到
(5)lock可以提高多个线程进行读操作的效率
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而竞争资源非常激烈是(既有大量线程同时竞争),此时lock的性能要远远优于synchronized。所以说,在具体使用时适当情况选择。
一些小例子:
package com.cn.test.thread.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { private Lock lock = new ReentrantLock(); /* * 使用完毕释放后其他线程才能获取锁 */ public void lockTest(Thread thread) { lock.lock();//获取锁 try { System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称 Thread.sleep(2000);//为看出执行效果,是线程此处休眠2秒 } catch (Exception e) { System.out.println("线程"+thread.getName() + "发生了异常释放锁"); }finally { System.out.println("线程"+thread.getName() + "执行完毕释放锁"); lock.unlock(); //释放锁 } } public static void main(String[] args) { LockTest lockTest = new LockTest(); //声明一个线程 “线程一” Thread thread1 = new Thread(new Runnable() { @Override public void run() { lockTest.lockTest(Thread.currentThread()); } }, "thread1"); //声明一个线程 “线程二” Thread thread2 = new Thread(new Runnable() { @Override public void run() { lockTest.lockTest(Thread.currentThread()); } }, "thread2"); // 启动2个线程 thread2.start(); thread1.start(); } }
执行结果:
package com.cn.test.thread.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { private Lock lock = new ReentrantLock(); /* * 尝试获取锁 tryLock() 它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false */ public void tryLockTest(Thread thread) { if(lock.tryLock()) { //尝试获取锁 try { System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称 Thread.sleep(2000);//为看出执行效果,是线程此处休眠2秒 } catch (Exception e) { System.out.println("线程"+thread.getName() + "发生了异常释放锁"); }finally { System.out.println("线程"+thread.getName() + "执行完毕释放锁"); lock.unlock(); //释放锁 } }else{ System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,我无法获取"); } } public static void main(String[] args) { LockTest lockTest = new LockTest(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { lockTest.tryLockTest(Thread.currentThread()); } }, "thread1"); //声明一个线程 “线程二” Thread thread2 = new Thread(new Runnable() { @Override public void run() { lockTest.tryLockTest(Thread.currentThread()); } }, "thread2"); // 启动2个线程 thread2.start(); thread1.start(); } }
执行结果:
package com.cn.test.thread.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { private Lock lock = new ReentrantLock(); public void tryLockParamTest(Thread thread) throws InterruptedException { if(lock.tryLock(3000, TimeUnit.MILLISECONDS)) { //尝试获取锁 获取不到锁,就等3秒,如果3秒后还是获取不到就返回false try { System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称 Thread.sleep(4000);//为看出执行效果,是线程此处休眠2秒 } catch (Exception e) { System.out.println("线程"+thread.getName() + "发生了异常释放锁"); }finally { System.out.println("线程"+thread.getName() + "执行完毕释放锁"); lock.unlock(); //释放锁 } }else{ System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,等待3s后仍无法获取,放弃"); } } public static void main(String[] args) { LockTest lockTest = new LockTest(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { lockTest.tryLockParamTest(Thread.currentThread()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }, "thread1"); //声明一个线程 “线程二” Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { lockTest.tryLockParamTest(Thread.currentThread()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }, "thread2"); // 启动2个线程 thread2.start(); thread1.start(); } }
执行结果:
因为此时线程1休眠了4秒,线程2等待了3秒还没有获取到就放弃获取锁了,执行结束
将方法中的 Thread.sleep(4000)改为Thread.sleep(2000)执行结果如下:
因为此时线程1休眠了2秒,线程2等待了3秒的期间线程1释放了锁,此时线程2获取到锁,线程2就可以执行了
题目:多线程之间按找顺序调用,实现A->B->C三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次,重复上述过程10次.
class ShareResource{ private int number = 1; // A:1, B:2, C:3 private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); private Condition conditionC = lock.newCondition(); public void print5(){ try { lock.lock(); while (number != 1){ conditionA.await(); } for (int i = 1; i <= 5; i++){ System.out.print("A"); } System.out.println(); number++; conditionB.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print10(){ try { lock.lock(); while (number != 2){ conditionB.await(); } for (int i = 1; i <= 10; i++){ System.out.print("B"); } System.out.println(); number++; conditionC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15(){ try { lock.lock(); while (number != 3){ conditionC.await(); } for (int i = 1; i <= 15; i++){ System.out.print("C"); } System.out.println(); number = 1; conditionA.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class SynchronizedLockDifference { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(()->{ for (int i = 1; i <= 10; i++){ shareResource.print5(); } }, "A").start(); new Thread(()->{ for (int i = 1; i <= 10; i++){ shareResource.print10(); } }, "B").start(); new Thread(()->{ for (int i = 1; i <= 10; i++){ shareResource.print15(); } }, "C").start(); } }
输出结果:
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
AAAAA
BBBBBBBBBB
CCCCCCCCCCCCCCC
Process finished with exit code 0
以上是关于java基础: synchronized与Lock的区别的主要内容,如果未能解决你的问题,请参考以下文章
Java同步锁——lock与synchronized 的区别转
Java高并发编程实战4,synchronized与Lock底层原理