博主分享免费Java教学视频,B站账号:Java刘哥
摘要:
我们已经知道,synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等。Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题。本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念。一. synchronized 的局限性 与 Lock 的优点
如果一个代码块被synchronized关键字修饰,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待直至占有锁的线程释放锁。事实上,占有锁的线程释放锁一般会是以下三种情况之一:- 占有锁的线程执行完了该代码块,然后释放对锁的占有;
- 占有锁线程执行发生异常,此时JVM会让线程自动释放锁;
- 占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等。
二. java.util.concurrent.locks包下常用的类与接口
以下是 java.util.concurrent.locks包下主要常用的类与接口的关系:
1、Lock
通过查看Lock的源码可知,Lock 是一个接口:- public interface Lock {
- void lock();
- void lockInterruptibly() throws InterruptedException; // 可以响应中断
- boolean tryLock();
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 可以响应中断
- void unlock();
- Condition newCondition();
- }
- Lock lock = ...;
- lock.lock();
- try{
- //处理任务
- }catch(Exception ex){
- }finally{
- lock.unlock(); //释放锁
- }
- Lock lock = ...;
- if(lock.tryLock()) {
- try{
- //处理任务
- }catch(Exception ex){
- }finally{
- lock.unlock(); //释放锁
- }
- }else {
- //如果不能获取锁,则直接做其他事情
- }
- public void method() throws InterruptedException {
- lock.lockInterruptibly();
- try {
- //.....
- }
- finally {
- lock.unlock();
- }
- }
2、ReentrantLock
ReentrantLock,即 可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例学习如何使用 ReentrantLock。 例 1 : Lock 的正确使用 (1) 三个线程,每个人不同的锁- package thread.test;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author 言曌
- * @date 2018/3/15 上午9:36
- */
- public class Test {
- static class ThreadA implements Runnable {
- private Lock lock = new ReentrantLock();
- @Override
- public void run() {
- lock.lock();//获得锁
- System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
- // do something
- for (int i = 0; i < 5; i++) {
- System.out.print(Thread.currentThread().getName() + i + " ");
- }
- System.out.println();
- System.out.println("线程" + Thread.currentThread().getName() + "释放了锁\n");
- lock.unlock();//释放锁
- }
- }
- static class ThreadB implements Runnable {
- private Lock lock = new ReentrantLock();
- @Override
- public void run() {
- lock.lock();//获得锁
- System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
- // do something
- for (int i = 0; i < 5; i++) {
- System.out.print(Thread.currentThread().getName() + i + " ");
- }
- System.out.println();
- System.out.println("线程" + Thread.currentThread().getName() + "释放了锁\n");
- lock.unlock();//释放锁
- }
- }
- static class ThreadC implements Runnable {
- private Lock lock = new ReentrantLock();
- @Override
- public void run() {
- lock.lock();//获得锁
- System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
- // do something
- for (int i = 0; i < 5; i++) {
- System.out.print(Thread.currentThread().getName() + i + " ");
- }
- System.out.println();
- System.out.println("线程" + Thread.currentThread().getName() + "释放了锁\n");
- lock.unlock();//释放锁
- }
- }
- public static void main(String args[]) {
- ThreadA threadA = new ThreadA();
- ThreadA threadB = new ThreadA();
- ThreadA threadC = new ThreadA();
- new Thread(new ThreadA(), "A").start();
- new Thread(new ThreadB(), "B").start();
- new Thread(new ThreadC(), "C").start();
- }
- }

- public class Test2 {
- static class MyThread implements Runnable {
- private Lock lock;
- public MyThread(Lock lock) {
- this.lock = lock;
- }
- @Override
- public void run() {
- lock.lock();//获得锁
- System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
- // do something
- for (int i = 0; i < 5; i++) {
- System.out.print(Thread.currentThread().getName() + i + " ");
- }
- System.out.println();
- System.out.println("线程" + Thread.currentThread().getName() + "释放了锁\n");
- lock.unlock();//释放锁
- }
- }
- public static void main(String args[]) {
- Lock lock = new ReentrantLock();
- MyThread myThread = new MyThread(lock);
- new Thread(myThread, "A").start();
- new Thread(myThread, "B").start();
- new Thread(myThread, "C").start();
- }
- }
- public class Test2 {
- static class MyThread implements Runnable {
- private Lock lock;
- public MyThread(Lock lock) {
- this.lock = lock;
- }
- @Override
- public void run() {
- while(true) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (lock.tryLock()) { // 使用 tryLock()
- System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
- // do something
- System.out.println("线程" + Thread.currentThread().getName() + "释放了锁");
- lock.unlock();//释放锁
- } else {
- System.out.println("线程" + Thread.currentThread().getName() + "获取锁失败...");
- }
- }
- }
- }
- public static void main(String args[]) {
- Lock lock = new ReentrantLock();
- MyThread myThread = new MyThread(lock);
- new Thread(myThread, "A").start();
- new Thread(myThread, "B").start();
- new Thread(myThread, "C").start();
- }
- }

- public class Test2 {
- static class MyThread implements Runnable {
- private Lock lock;
- public MyThread(Lock lock) {
- this.lock = lock;
- }
- @Override
- public void run() {
- while(true) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- try {
- if (lock.tryLock(4, TimeUnit.SECONDS)) { // 使用 tryLock(),4s内得到锁则返回true
- System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
- // do something
- System.out.println("线程" + Thread.currentThread().getName() + "释放了锁");
- lock.unlock();//释放锁
- } else {
- System.out.println("线程" + Thread.currentThread().getName() + "获取锁失败...");
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String args[]) {
- Lock lock = new ReentrantLock();
- MyThread myThread = new MyThread(lock);
- new Thread(myThread, "A").start();
- new Thread(myThread, "B").start();
- new Thread(myThread, "C").start();
- }
- }

3、ReadWriteLock
ReadWriteLock也是一个接口,在它里面只定义了两个方法:- public interface ReadWriteLock {
- /**
- * Returns the lock used for reading.
- *
- * @return the lock used for reading.
- */
- Lock readLock();
- /**
- * Returns the lock used for writing.
- *
- * @return the lock used for writing.
- */
- Lock writeLock();
- }
4、ReentrantReadWriteLock
ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。下面通过几个例子来看一下ReentrantReadWriteLock具体用法。假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:- import java.util.Random;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
- /**
- * @author 言曌
- * @date 2018/3/15 上午11:07
- */
- public class ReadWriteLockTest {
- static class Queue {
- private Object data = null;//共享数据,只能有一个线程写该数据,但可以有多个线程同时读数据
- private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
- public void get() {
- rwl.readLock().lock();//上读锁,其他线程只能读不能写
- System.out.println("线程" + Thread.currentThread().getName() + "开始读取数据");
- try {
- Thread.sleep((long) (Math.random() * 1000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("线程" + Thread.currentThread().getName() + "读取数据完毕,数据为:"+data);
- rwl.readLock().unlock();//释放读锁
- }
- public void put(Object data) {
- rwl.writeLock().lock();//上写锁,不允许其他线程读也不允许写
- System.out.println("线程" + Thread.currentThread().getName() + "开始写入数据");
- try {
- Thread.sleep((long) (Math.random() * 1000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.data = data;
- System.out.println("线程" + Thread.currentThread().getName() + "写入数据完毕,数据为:" + data);
- rwl.writeLock().unlock();//释放写锁
- }
- }
- public static void main(String args[]) {
- Queue queue = new Queue();
- for(int i=0;i<3;i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- while(true) {
- queue.get();
- queue.put(new Random().nextInt(1000));
- }
- }
- }).start();
- }
- }
- }

5、Lock和synchronized的选择
总的来说,Lock和synchronized有以下几点不同:- (1) Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;
- (2) synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- (3) Lock 可以让等待锁的线程响应中断,而使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- (4) 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
- (5) Lock可以提高多个线程进行读操作的效率。
三. 锁的相关概念介绍
1、可重入锁
如果锁具备可重入性,则称作为 可重入锁 。像 synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了 锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。- class MyClass {
- public synchronized void method1() {
- method2();
- }
- public synchronized void method2() {
- }
- }
2、可中断锁
顾名思义,可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。 如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法时已经体现了Lock的可中断性。3、公平锁
公平锁即 尽量 以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁则无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。 在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。 看下面两个例子: Case : 非公平锁(默认不传参数或者传 false)- public class LockTest2 {
- static class MyThread implements Runnable {
- private ReentrantLock lock = new ReentrantLock(false);//默认是非公平锁,false
- @Override
- public void run() {
- while(true) {
- lock.lock();//获得锁
- System.out.println(Thread.currentThread().getName());
- lock.unlock();//释放锁
- }
- }
- }
- public static void main(String args[]) {
- MyThread myThread = new MyThread();
- new Thread(myThread,"A").start();
- new Thread(myThread,"B").start();
- new Thread(myThread,"C").start();
- new Thread(myThread,"D").start();
- new Thread(myThread,"E").start();
- }
- }

- private ReentrantLock lock = new ReentrantLock(true);

- isFair() //判断锁是否是公平锁
- isLocked() //判断锁是否被任何线程获取了
- isHeldByCurrentThread() //判断锁是否被当前线程获取了
- hasQueuedThreads() //判断是否有线程在等待该锁
- getHoldCount() //查询当前线程占有lock锁的次数
- getQueueLength() // 获取正在等待此锁的线程数
- getWaitQueueLength(Condition condition) // 获取正在等待此锁相关条件condition的线程数在ReentrantReadWriteLock中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过要记住,ReentrantReadWriteLock并未实现Lock接口,它实现的是ReadWriteLock接口。
4.读写锁
读写锁将对临界资源的访问分成了两个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。上一节已经演示过了读写锁的使用方法,在此不再赘述。 参考:https://www.cnblogs.com/aishangJava/p/6555291.html- 微信
- 交流学习,有偿服务
-
- 博客/Java交流群
- 资源分享,问题解决,技术交流。群号:590480292
-
2018年03月28日 15:29:56
synchronized中无法查看锁是否获取?那偏向锁进行升级的时候就能判断是否是本线程id的~这怎么说呢?