synchronized的局限性
synchronized是java内置的关键字,synchronized对同步锁的获取和释放锁由jvm实现,开发无法控制,可以认为是越底层的东西越高级越无法控制。
所以使用synchronized同步的情景
例如:
1、当线程尝试获取锁的时候,如果获取不到锁会一直阻塞,这个阻塞的过程,用户无法控制
2、如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待
ReentrantLock也可以同步方法,加锁
一般ReentrantLock的使用过程:
创建锁:ReentrantLock lock = new ReentrantLock();
获取锁:lock.lock()
释放锁:lock.unlock();
在同步方法的代码加锁,结束后释放锁
package com.example.demo.demo.reentrantLockDemo;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* java中synchronize获取锁和ReentrantLock
*/
public class ReentrantLockTest {
static int num =0;
static ReentrantLock lock = new ReentrantLock();
public static void addNum(){
try {
lock.lock();
num++;
}
finally {
lock.unlock();
}
}
public static class Mythread extends Thread{
private String name;
public Mythread(String name){
this.name= name;
}
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
ReentrantLockTest.addNum();
}
}
}
public static void main(String[] args) throws InterruptedException {
Mythread t1 = new Mythread("t1");
t1.start();
Mythread t2 = new Mythread("t2");
t2.start();
Mythread t3 = new Mythread("t3");
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(num);
}
}
通过lock(),和unlock()可以灵活控制锁的获取与释放,注意lock.unlock()一定要放在finally中,否则,若程序出现了异常,锁没有释放(会一直同步占用),那么其他线程就再也没有机会获取这个锁了
ReentrantLock是可重入锁
顾名思义可以加多把锁
try {
lock.lock();
lock.lock();
num++;
}
finally {
lock.unlock();
lock.unlock();
}
需要注意获取锁和释放锁是成对出现的
公平锁和非公平锁的概念
当获取锁是系统只是会从这个锁的等待队列中随机挑选一个,不管队列中的顺序,是非公平锁。反之先来先获取为公平锁,synchronized默认有jvm内部实现控制的,是非公平锁
ReentrantLock公平锁与非公平锁
jdk中ReentrantLock的源码,2个构造方法:
public ReentrantLock () {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
存在无参和有参数
默认构造方法创建的是非公平锁。
第2个构造方法,有个fair参数,当fair为true的时候创建的是公平锁,公平锁看起来很不错,不过要实现公平锁,系统内部肯定需要维护一个有序队列,因此公平锁的实现成本比较高,性能相对于非公平锁来说相对低一些。因此,在默认情况下,锁是非公平的,如果没有特别要求,则不建议使用公平锁。
private static ReentrantLock fairLock = new ReentrantLock(true);
fairLock.lock();//公平锁
fairLock.unlock();
ReentrantLock其他加锁的方法
lockInterruptibly()
就是在等的获取锁的过程中(发起获取锁请求到还未获取到锁这段时间内)是可以被中断的,也就是说在等待锁的过程中,程序可以根据需要取消获取锁的请求
private static ReentrantLock Lock = new ReentrantLock(true);
Lock.lockInterruptibly();//加锁获取锁时,在线程调用interrupt()方法之后,才会引发 InterruptedException异常
tryLock无参方法
public boolean tryLock()
返回boolean类型的值,此方法会立即返回,结果表示获取锁是否成功
public class Demo8 {
private static ReentrantLock =new ReentrantLock(false);
public static class T extends Thread {
public T(String name) {
super(name);
}
@Override
public void run() {
try {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "开始获取锁!");//获取锁超时时间设置为3秒,3秒内是否能否获取锁都会返回
if (lock1.tryLock())
{
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "获取到了锁!");
//获取到锁之后,休眠5秒
TimeUnit.SECONDS.sleep(5);
}
else
{
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "未能获取到锁!");
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T("t1");
T t2 = new T("t2");
t1.start();
t2.start();
}
}
1563356291081:t2开始获取锁!
1563356291081:t2获取到了锁!
1563356291081:t1开始获取锁!
1563356291081:t1未能获取到锁!
可以看到t2获取成功,t1获取失败了,tryLock()是立即响应的,中间不会有阻塞。
tryLock有参方法
可以明确设置获取锁的超时时间,该方法签名:
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
将上面代码中的lock1.tryLock()替换
lock1.tryLock(3,TimeUnit.SECONDS)
程序中调用了ReentrantLock的实例方法 tryLock(3,TimeUnit.SECONDS),表示获取锁的超时时间是3秒,3秒后不管是否能否获取锁,该方法都会有返回值,获取到锁之后,内部休眠了5秒
会导致会导致另外一个线程获取锁失败。
1563355512901:t2 开始获取锁!
1563355512901:t1 开始获取锁!
1563355512902:t2 获取到了锁!
1563355515904:t1 未能获取到锁!
总结
1. ReentrantLock可以实现公平锁和非公平锁
2. ReentrantLock默认实现的是非公平锁
3. ReentrantLock的获取锁和释放锁必须成对出现,锁了几次,也要释放几次
4. 释放锁的操作必须放在finally中执行
5. lockInterruptibly()实例方法可以相应线程的中断方法,调用线程的interrupt()方法时,
6. lockInterruptibly()方法会触发 InterruptedException异常
7. 关于 InterruptedException异常说一下,看到方法声明上带有 throwsInterruptedException,表示该方法可以相应线程中断,调用线程的interrupt()方法时,这些方法会触发 InterruptedException异常,触发InterruptedException时,线程的中断中断状态会被清除。所以如果程序由于调用 interrupt()方法而触发 InterruptedException异常,线程的标志由默认的false变为ture,然后又变为false
8. 实例方法tryLock()获会尝试获取锁,会立即返回,返回值表示是否获取成功
9. 实例方法tryLock(long timeout, TimeUnit unit)会在指定的时间内尝试获取锁,指定的时间内是否能够获取锁,都会返回,返回值表示是否获取锁成功,该方法会响应线程的中断