ReentrantLock类
为了保证任何时刻只有一个线程能进入临界区,通常需要给临界区上锁,只有获得锁的线程才能进入临界区。为了达到上锁的目的,我们通常使用synchronized关键字。
在Java SE 5.0之后,java引入了一个ReentrantLock类,也可以实现给代码块上锁和释放锁的效果。
lock方法 和unlock方法
- lock() 申请获得锁
- 如果获得锁,该线程可以继续往下执行
- 如果该锁已被其他线程获取,当前线程停止运行并进入阻塞状态,等待其他线程释放锁
- unlock() 释放锁
- ReentrantLock和synchronized锁一样是可重入锁,
- 一个线程可以重复的获得已经持有的锁,锁保持一个持有计数(holdcount) 来跟踪对 lock 方法的嵌套调用。
- 线程在每一次调用 lock 都要调用 unlock 来释放锁。
- 由于这一特性, 被一个可重入锁保护的代码可以调用另一个使用相同的锁的方法。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9 {
//ReentrantLock类实现了Lock接口
private static final Lock lock = new ReentrantLock();
private int count = 0;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public void add() {
lock.lock();//申请获得锁
try {
//一般为了安全,一般用try把同步代码块的内容包裹起来
//保证即使当前线程运行过程中出现异常,也能正常释放锁
for (int i = 0; i < 10000; i++) {
count++;
}
} finally {
//一般在finally里释放锁
lock.unlock();
}
}
public static void main(String[] args) {
myThread9 m = new myThread9();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
//新建10个线程,加入threads中
Thread thread = new Thread(() -> {
m.add();
}, "thread-" + i);
threads.add(thread);
}
//逐一启动线程
threads.forEach((thread -> thread.start()));
//在主线程中利用join方法,等待所有方法完成
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//所有子线程运行结束,打印count值
System.out.println(m.getCount());
}
}
trylock方法(限时等待)
- trylock():尝试获得锁,如果获得锁,返回true.如果没有获得锁,返回false(当前线程不会进入阻塞状态)
- trylock(long time,TimeUnit unit):在指定时间时间内尝试获得锁,如果成功获取锁,返回true,如果没有锁,返回false(当前线程不会进入阻塞状态)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9_1{
//ReentrantLock类实现了Lock接口
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
myThread9_1 m = new myThread9_1();
//启动第一个线程,执行add方法
new Thread(()->m.add(),"t1").start();
//过1s后启动第二个线程执行testTryLock,确保第一个线程能先启动获得锁
Thread.sleep(1000);
new Thread(()->m.testTryLock(),"t2").start();
}
public void add(){
lock.lock();
try {
//该线程获得锁后需要5s才能运行完毕释放锁
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+":当前i的值为"+i);
Thread.sleep(1000);
}}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void testTryLock(){
boolean flag=false;
try {
//可以尝试不同等待时间对比运行结果
flag = lock.tryLock();//尝试立即获得锁
// flag = lock.tryLock(2,TimeUnit.SECONDS);//等待2s,尝试获得锁
// flag = lock.tryLock(6,TimeUnit.SECONDS);//等待6s,尝试获得锁
if (flag){
//获得锁
System.out.println(Thread.currentThread().getName()+"获得锁,执行同步代码块里内容");
}else {
System.out.println(Thread.currentThread().getName()+"没有在规定时间内获得锁,执行非同步代码块内容");
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (flag){
//如过获得锁,要释放锁
lock.unlock();
}
}
}
}
公平锁
含义:谁等的时间最长,谁就先获取锁。
默认情况下,ReentrantLock是非公平锁,可以通过构造方法参数把ReentrantLock声明为公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9_2 {
//在new ReentrantLock时传入true就可以把lock设置为公平锁(默认时false)
private static final Lock lock = new ReentrantLock(true);
public void test() {
for (int i = 0; i < 3; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
myThread9_2 m = new myThread9_2();
for (int i = 1; i <= 5; i++) {
//m::test等价于()->m.test();
new Thread(m::test, "线程" + i).start();
}
}
}
ReentrantLock和synchronized的对比
- ReentrantLock和synchronized都是独占锁(排他锁),一个时刻都只能有一个线程持有该锁
- ReentrantLock和synchronized都是可重入锁,一个线程可以重复的获得已经持有的锁
- synchronized可以自动获得锁和释放锁,ReentrantLock需要手动获得锁并手动在finally里释放锁
- 在性能上,ReentrantLock是轻量级锁;但jdk 1.5之后,synchronized加入了锁升级的概念(偏向锁,轻量级锁,重量级锁),所以二者性能相当.
- synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断