【Java】ReentrantLock重入锁与synchronized同步锁区别详解


ReentrantLock实现类(Lock接口)详解: 【Java】Lock锁接口和实现类详解
synchronized关键字线程同步详解: 【Java】线程的基本同步方式和常用方法

1. 两者优劣特点对比(详细)

比较点 synchronized关键字 ReentrantLock实现类(Lock接口)
构成 它是java语言的关键字,是原生语法层面的互斥,需要jvm实现 它是JDK 1.5之后提供的API层面的互斥锁类
实现 通过JVM获取锁/释放锁 api层面的获取锁释放锁,释放锁需要手动
代码 采用synchronized不需要手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,更安全 ReentrantLock则必须要手动释放锁,如果没有主动释放锁,就有可能导致出现死锁,需要lock()和unlock()方法配合try-finally语句块完成
灵活 锁的范围是整个方法或synchronized代码块部分 使用Lock接口的方法调用,可以跨方法,灵活性更强大
中断 不可中断,除非抛出异常(释放锁方式:
①代码执行完,正常释放锁;
②抛出异常,由JVM退出等待)
可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待(方法:
①设置超时方法 tryLock(long timeout, TimeUnit unit)时间过了就放弃等待;
②lockInterruptibly()放代码块中,调用interrupt()方法可中断)
公平 非公平锁,不考虑排队问题直接尝试获取锁 公平锁和非公平锁两者都可以,默认非公平锁,检查是否有排队等待的线程,先来者先得锁,构造器可以传入boolean值,true为公平锁,false为非公平锁
条件 通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能
高级 提供很多方法用来监听当前锁的信息,如:
getHoldCount()
getQueueLength()
isFair()
isHeldByCurrentThread()
isLocked()
便利 方便简洁,由编译器去保证锁的加锁和释放 需要手工声明来加锁和释放锁
场景 资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized,另外可读性非常好 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步等
性能 低并发优先,性能好,可读性好 高并发优先,性能最佳

2. ReentrantLock 的3个高级功能

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了3个高级功能(表格中也有锁体现)。

功能①:等待可中断

持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。

三个API说明:
1)lock(), 拿不到lock就不罢休,不然线程就一直block,死等。
2)tryLock(),马上返回,拿到lock就返回true,不然返回false,不等。带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。
3)lockInterruptibly():

  1. 线程在sleep或wait,join此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException;
  2. 此线程在运行中,则不会收到提醒。但是此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并作出处理。线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。并且如果线程已经被interrupt,再使用lockInterruptibly的时候,此线程也会被要求处理interruptedException。

代码示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestInterrupt {
	public static void main(String[] args) throws Exception {
		new TestInterrupt().test();
	}

	public void test() throws Exception {
		final Lock lock = new ReentrantLock();
		lock.lock();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					int i = 5;
					while (i-- > 0) {
						System.out.println("I'm running " + i);
						Thread.sleep(1000);
					}
					// 相当于一个可被中断的设置,会抛出中断异常(受查异常)
					lock.lockInterruptibly();
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName() + " interrupted(我被中断了).");
					System.out.println("被打断后所做的事情...");
				}
			}
		}, "子线程-1");

		t1.start();
		Thread.sleep(3000); // 2秒后打断t1线程,t1线程会中断执行
		t1.interrupt(); // 打断:触发t1线程任务run中的中断异常(抛出),进入执行异常代码块
	}
}
释义:线程中断(interrupt)

中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)

  1. 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
  2. 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出InterruptedException,从而使线程提前结束 TIMED-WATING 状态
  3. 许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
  4. 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程。

功能②:公平锁机制

多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得;
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。

公平锁、非公平锁的创建方式:

//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
 
//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);

功能③:锁绑定多个条件

一个ReentrantLock对象可以同时绑定对个对象。ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
代码示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestCondition {
	public static void main(String[] args) throws InterruptedException {
		MyService service = new MyService();
		Thread t1 = new Thread(new MyServiceThread1(service), "a");
		Thread t2 = new Thread(new MyServiceThread2(service), "b");
		
		t1.start();
		t2.start();

		// 线程sleep2秒钟
		Thread.sleep(2000);
		// 唤醒所有持有conditionA的线程
		service.signallA();

		Thread.sleep(3000);
		// 唤醒所有持有conditionB的线程
		service.signallB();
	}
}

// MyServiceThread1 使用了awaitA()方法,持有的是conditionA!
class MyServiceThread1 implements Runnable {
	private MyService service;
	public MyServiceThread1(MyService service) {
		this.service = service;
	}
	@Override
	public void run() {
		service.awaitA();
	}
}

// MyServiceThread2 使用了awaitB()方法,持有的是conditionB!
class MyServiceThread2 implements Runnable {
	private MyService service;
	public MyServiceThread2(MyService service) {
		this.service = service;
	}
	@Override
	public void run() {
		service.awaitB();
	}
}

// 主要的功能代码
class MyService {
	// 实例化一个ReentrantLock对象
	private ReentrantLock lock = new ReentrantLock();
	// 为线程A注册一个Condition
	public Condition conditionA = lock.newCondition();
	// 为线程B注册一个Condition
	public Condition conditionB = lock.newCondition();

	public void awaitA() {
		try {
			lock.lock();
			
			System.out.println(Thread.currentThread().getName() + "进入了awaitA方法");
			long timeBefore = System.currentTimeMillis();
			conditionA.await(); // 执行conditionA等待
			long timeAfter = System.currentTimeMillis();
			
			System.out.println(Thread.currentThread().getName() + "被唤醒");
			System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore) / 1000 + "s");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void awaitB() {
		try {
			lock.lock();
			
			System.out.println(Thread.currentThread().getName() + "进入了awaitB方法");
			long timeBefore = System.currentTimeMillis();
			conditionB.await(); // 执行conditionB等待
			long timeAfter = System.currentTimeMillis();
			
			System.out.println(Thread.currentThread().getName() + "被唤醒");
			System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore) / 1000 + "s");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void signallA() {
		try {
			lock.lock();
			System.out.println("启动唤醒程序");
			// 唤醒所有注册conditionA的线程
			conditionA.signalAll();
		} finally {
			lock.unlock();
		}
	}

	public void signallB() {
		try {
			lock.lock();
			System.out.println("启动唤醒程序");
			// 唤醒所有注册conditionB的线程
			conditionB.signalAll();
		} finally {
			lock.unlock();
		}
	}
}

Java-ReentrantLock重入锁

补充:Condition 类和 Object 类锁方法区别
  1. Condition 类的 awiat 方法和 Object 类的 wait 方法等效;
  2. Condition 类的 signal 方法和 Object 类的 notify 方法等效;
  3. Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效;
  4. ReentrantLock 类可以唤醒指定条件的线程,而 Object 的唤醒是随机的
补充:trylock 和 lock 和 lockInterruptibly 方法区别
  1. tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false
  2. lock 能获得锁就返回 true,不能的话一直等待获得锁;
  3. lock 和 lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,lock 不会抛出异常,而 lockInterruptibly 会抛出异常

3. 什么时候使用ReentrantLock?

答案是:

  1. 原子操作的颗粒度更小的加锁操作或跨方法释放锁时(灵活);
  2. 如果你需要使用ReentrantLock的三个高级功能时。
发布了320 篇原创文章 · 获赞 311 · 访问量 66万+

猜你喜欢

转载自blog.csdn.net/sinat_36184075/article/details/104875120