线程安全
指的是,在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。
1.线程和进程
进程: 一个电脑应用程序 操作系统动态执行的基本单元
线程: 一个进程有多个线程 一个进程至少有一个线程 要不然进程没有意义
进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位
2.并发和并行
并发是多个线程对同一资源的争夺
并行是同时做几件事 并行只发生在多核CPU或多CPU系统中
3.wait 和 sleep
功能都是暂停当前线程
wait 是object里面的方法 放开锁去睡 不耽误其他线程
sleep是thread里的 sleep 握着锁去睡 阻塞线程
4.线程创建的两种方式 (一共四种)
new Thread() 对象创建 使用start方法;
实现 Runable接口 具体功能都写在 run方法里面
new Thread(new Runnable() {
@Override
public void run() {
// 调用资源方法,完成业务逻辑
}
}, "your thread name").start();
4.synchronized 8锁问题
当是静态同步方法时,就相当于对这个类加锁 而此时其他线程获取不到这个类锁了 但是非静态同步方法 是可以不同对象获取不同的方法锁. 而静态同步方法(Class对象锁)与非静态同步方法(实例对象锁)之间是不会有竞态条件的。
5.lock锁
juc : java.util.concurrent(并存)
锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。
lock是一个接口 主要有三个实现是ReentrantLock ReentrantReadWirtelock.readLock ReentrantReadWirtelock.wirteLock
1.ReentrantLock可重入锁 排它锁
使用 new ReentrantLock() new 一个对象
ReentrantLock lock=new ReentrantLock();
lock.lock();加锁
lock.unlock();解锁
ReentrantLock 和 Synchronized 都是可重入锁 可以一定程度上避免死锁的情况
ReentrantLock:悲观的独占的排他的互斥的可公平的可不公平的可重入锁
new ReentrantLock();
lock.lock()/unlock()/tryLock(30, TimeUnit.seconds);
和synchronized的区别?悲观的独占的排他的互斥的可重入锁
1.一个是类;一个是关键字
2.都是排他锁,但是玩法麻烦,但是灵活;玩法简单,但是不够灵活
3.都是可重入锁,加解锁次数必须一致;自动加解锁
4.可以公平的;非公平的
5.可以响应中断;不可以中断
2.公平锁
就是谁等待锁的时间最长 这个锁给哪个线程
2.1限时等待
这个是什么意思呢?也就是通过我们的tryLock方法来实现,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以将这种方法用来解决死锁问题。
2.2 ReentrantLock和synchronized区别
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。
6.读写锁
读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。
读写锁的特点:
1.写写不可并发。
2.写读不可并发。
3.读读可以并发。
降级锁:就是当前是写锁的情况下可以获取读锁 然后释放写锁的过程叫做降级锁;
6.1 读写锁总结
-
支持公平/非公平策略
-
支持可重入
-
同一读线程在获取了读锁后还可以获取读锁
-
同一写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁
-
-
支持锁降级,不支持锁升级
-
读写锁如果使用不当,很容易产生“饥饿”问题:
在读线程非常多,写线程很少的情况下,很容易导致写线程“饥饿”,虽然使用“公平”策略可以一定程度上缓解这个问题,但是“公平”策略是以牺牲系统吞吐量为代价的。
-
Condition条件支持
写锁
可以通过newCondition()
方法获取Condition对象。但是读锁是没法获取Condition对象,读锁调用newCondition()
方法会直接抛出UnsupportedOperationException
。
7.线程间通信
线程间通信模型:
-
生产者+消费者
-
通知等待唤醒机制
多线程编程模板中:
-
判断
-
干活
-
通知
多个线程操作一个资源 按照一定规律
虚假唤醒用while
定制通信 : 使用Condition实现线程通信
(synchronized)wait\notify\notifyAll
Lock lock = new ReentrantLock(); // 初始化lock锁
Condition condition = lock.newCondition(); // 初始化condition对象
codition.await();//等待送开锁
condition.signalAll();//通知
哪个condition对象调用signa()方法 ,就通知哪个condition线程
8.并发容器类
并发容器类:
List:
ArrayList:线程不安全的。性能好,扩容机制每次扩容50%
Vector:线程安全的。性能低下,扩容机制每次扩容100%
synchronizedList:线程安全的。迭代器没有加锁,性能问题
CopyOnWriteArrayList:线程安全的。
Set:
HashSet:线程不安全的。
synchronizedSet:线程安全的。迭代器没有加锁
CopyOnWriteArraySet:线程安全的。
Map:
hashMap:线程不安全的。
synchronizedMap:线程安全的
ConcurrentHashMap:线程安全的。推荐使用。
COW容器:Copy On Write容器,写时复制容器。读写分离。适合于读多写少这种场景(黑名单)
CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于 Vector。
优点:读的性能非常高
缺点:
1.写的性能较低
2.占用大量
3.读写不一致
多线程编程模板上:线程 操作 资源类多线程编程模板中:判断 干活 通知多线程编程模板下:为了防止虚假唤醒,判断要使用while
集合不完全例子:java.util.ConcurrentModificationException 并发修改异常
List<String> list=new ArrayList();
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString());
System.out.println(list);
}).start();
}
解决办法:List<String> list= Collections.synchronizedList(new ArrayList<>()); juc提供了线程安全的list set map
9.辅助类
-
CountDownLatch(倒计数器)
-
CycliBarrier(循环栅栏)
-
Semaphore(信号量)
CountDownLatch是一个非常实用的多线程控制工具类,应用非常广泛。
例如:在手机上安装一个应用程序,假如需要5个子进程检查服务授权,那么主进程会维护一个计数器,初始计数就是5。用户每同意一个授权该计数器减1,当计数减为0时,主进程才启动,否则就只有阻塞等待了。
1.CountDownLatch
new CountDownLatch(int count) //实例化一个倒计数器,count指定初始计数countDown() // 每调用一次,计数减一await() //等待,当计数减到0时,阻塞线程(可以是一个,也可以是多个)并行执行
面试:CountDownLatch 与 join 方法的区别
调用一个子线程的 join()方法后,该线程会一直被阻塞直到该线程运行完毕。而 CountDownLatch 则使用计数器允许子线程运行完毕或者运行中时候递减计数,也就是 CountDownLatch 可以在子线程运行任何时候让 await 方法返回而不一定必须等到线程结束;另外使用线程池来管理线程时候一般都是直接添加 Runnable 到线程池这时候就没有办法在调用线程的 join 方法了,countDownLatch 相比 Join 方法让我们对线程同步有更灵活的控制。
2.CycliBarrier
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。该命令只在每个屏障点运行一次。若在所有参与线程之前更新共享状态,此屏障操作很有用.
常用方法:
-
CyclicBarrier(int parties, Runnable barrierAction) 创建一个CyclicBarrier实例,parties指定参与相互等待的线程数,barrierAction一个可选的Runnable命令,该命令只在每个屏障点运行一次,可以在执行后续业务之前共享状态。该操作由最后一个进入屏障点的线程执行。
-
CyclicBarrier(int parties) 创建一个CyclicBarrier实例,parties指定参与相互等待的线程数。
-
await() 该方法被调用时表示当前线程已经到达屏障点,当前线程阻塞进入休眠状态,直到所有线程都到达屏障点,当前线程才会被唤醒。
面试:CyclicBarrier和CountDownLatch的区别?
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
3.Semaphore 共享锁
Semaphore翻译成字面意思为 信号量,Semaphore可以控制同时访问的线程个数。非常适合需求量大,而资源又很紧张的情况。比如给定一个资源数目有限的资源池,假设资源数目为N,每一个线程均可获取一个资源,但是当资源分配完毕时,后来线程需要阻塞等待,直到前面已持有资源的线程释放资源之后才能继续。
常用方法:
public Semaphore(int permits) // 构造方法,permits指资源数目(信号量) public void acquire() throws InterruptedException // 占用资源,当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。 public void release() // (释放)实际上会将信号量的值加1,然后唤醒等待的线程。
信号量
主要用于两个目的:
-
多个共享资源的互斥使用。
-
用于并发线程数的控制。保护一个关键部分不要一次输入超过N个线程。
10.callable接口
Thread类、Runnable接口使得多线程编程简单直接。
但Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。
public void run()方法规范意味着你必须捕获并处理检查型异常。即使你小心捕获异常,也不能保证这个类(Runnable对象)的所有使用者都读取异常信息。
以上两个问题现在都得到了解决。从java5开始,提供了Callable接口,是Runable接口的增强版。用Call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。
-
查看父类下面有哪些子类:Ctrl+h
1.callable使用
FutureTask实现了RunnableFuture接口,runnablefuture接口又继承runnable接口,所以该类的本质是一个runnable接口的实现类。
在futureTask的构造方法里提供了callable的参数 可以通过get方法获取call方法的返回值 是futuretask对象使用get方法
new Thread(new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return null;
}
})).start();
FutureTask:未来的任务,用它就干一件事,异步调用。通常用它解决耗时任务,挂起堵塞问题。
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
FutureTask仅在call方法完成时才能get结果;如果计算尚未完成,则阻塞 get 方法。
一旦计算完成,就不能再重新开始或取消计算。get方法获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
注意:
-
为了防止主线程阻塞,建议get方法放到最后
-
只计算一次,FutureTask会复用之前计算过得结果
-
.Callable 和 Runnable 区别?
他们都是函数式接口,都可以编写多线程程序
1.方法不同:call方法 run方法
2.获取子任务的返回结果集:futureTask.get()
3.捕获子任务的异常信息:捕获get方法的异常
4.future.isDone方法检查子任务是否执行完成
11.阻塞队列 blockingqueue
栈与队列简单回顾:
栈:先进后出,后进先出
队列:先进先出
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
BlockingQueue是为了解决多线程中数据高效安全传输而提出的。从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:
-
当队列满了的时候进行入队列操作
-
当队列空了的时候进行出队列操作
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
抛出异常
add正常执行返回true,element(不删除)和remove返回阻塞队列中的第一个元素 当阻塞队列满时,再往队列里add插入元素会抛IllegalStateException:Queue full 当阻塞队列空时,再往队列里remove移除元素会抛NoSuchElementException 当阻塞队列空时,再调用element检查元素会抛出NoSuchElementException
特定值
插入方法,成功ture失败false
移除方法,成功返回出队列的元素,队列里没有就返回null 检查方法,成功返回队列中的元素,没有返回null
一直阻塞
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。 当阻塞队列满时,再往队列里put元素,队列会一直阻塞生产者线程直到put数据or响应中断退出 当阻塞队列空时,再从队列里take元素,队列会一直阻塞消费者线程直到队列可用
超时退出
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。 返回一个特定值以告知该操作是否成功(典型的是 true / false)
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
//add remove element 超出报错
/* System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove("b"));
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.element());*/
// 第二组:offer poll peek true 或者 false 没有null
/* System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());//拉出去
System.out.println(blockingQueue.poll());//拉出去
System.out.println(blockingQueue.poll());//拉出去
System.out.println(blockingQueue.poll());//拉出去
System.out.println(blockingQueue.peek());//检索要出的元素*/
// 第三组:put take 超出一直等待 没有检查方法
/* try {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d");
blockingQueue.take();
blockingQueue.put("d");
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// 第四组:offer poll
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d",5, TimeUnit.SECONDS));
}
12线程池
重点
线程池的优势:线程复用;控制最大并发数;管理线程。
-
降低资源消耗(线程复用)。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
-
提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
-
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
线程池:线程复用 控制线程数
优势:
1.降低资源消耗
2.提高性能
3.提高了线程的可管理性
类结构:顶层接口Executor --> 子接口ExecutorService --> AbstractExecutorService --> ThreadPoolExecutor
工具类:Executors Arrays Collections
//执行长期任务性能好,创建一个线程池 ,一池有n个固定的线程,有固定线程数的线程
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
//一个任务一任务的执行,一池一线程
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//执行很多短期异步任务,线程池根据需要创建新线程,但是在先前构建的线程可用时将重用它们。可扩容,遇强则强。
ExecutorService threadPool = Executors.newCachedThreadPool();
7个重要参数:
1.corePoolSize:核心线程数
2.maximumPoolSize:最大可扩展线程数
3.keepAliveTime:生存时间(缩容,慢慢到核心线程数)
4.unit:时间单位
5.workQueue:阻塞队列(工作队列)
6.threadFactory:线程工厂
7.handler:拒绝策略
工作原理
1.创建线程池后等待请求。
2.当调用executor()方法添加一个请求任务时,线程池做出判断。
2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程来运行这个任务。
2.2 如果正在运行的线程数量大于等于corePoolSize,则任务进入队列中。
2.3如果这个时候队列满了,且正在运行的线程数量还小于maximumPoolSize,那么创建非核心线程来运行这个任务,如果队列满了,且正在运行的线程数量大于等于maximumPoolSize,那么线程池执行拒绝策略。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做
超过一定的时间(keepAlivetime)时,线程会判断
4.1 如果运行的线程数大于corePoolSize,那么这个线程就会被停掉。
所有线程池的任务完成后,它最终会收缩到corePoolSize的大小。
ThreadPoolExecutor自带的拒绝策略
如下:
-
AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
-
CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
-
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中 尝试再次提交当前任务。
-
DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。 如果允许任务丢失,这是最好的一种策略。
以上内置的策略均实现了RejectedExecutionHandler接口,也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略
生产开发中如何定义线程数量。
分为cpu密集型任务和io密集型任务
如果是cpu密集型任务,意味着cpu是稀缺资源,就是项目计算比较复杂,但是我们通常不能通过增加线程数来提高计算能力,因为线程数量太多,会导致频繁切换上下文,一般这种情况下
建议合理的线程数是 N(CPU)数+1
如果是io密集型任务,说明需要比较多的等待,这个时候可以参考Brain Goetz的推荐方法
参考值可以是N(CPU)核数*2
13.java内存模型(java memory model) 和 volatile
JMM规定了内存主要划分为主内存和工作内存两种。
线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
主内存:保存了所有的变量。共享变量:如果一个变量被多个线程使用,那么这个变量会在每个线程的工作内存中保有一个副本,这种变量就是共享变量。工作内存:每个线程都有自己的工作内存,线程独享,保存了线程用到的变量副本(主内存共享变量的一份拷贝)。工作内存负责与线程交互,也负责与主内存交互。
内存模型的三大特性:
-
原子性:即不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作是原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要使用同步技术(sychronized)或者锁(Lock)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
-
可见性:每个线程都有自己的工作内存,所以当某个线程修改完某个变量之后,在其他的线程中,未必能观察到该变量已经被修改。在 Java 中 volatile、synchronized 和 final 实现可见性。volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。
-
有序性:java的有序性跟线程相关。一个线程内部所有操作都是有序的,如果是多个线程所有操作都是无序的。因为JMM的工作内存和主内存之间存在延迟,而且java会对一些指令进行重新排序。volatile和synchronized可以保证程序的有序性,很多程序员只理解这两个关键字的执行互斥,而没有很好的理解到volatile和synchronized也能保证指令不进行重排序。
当一个变量定义为 volatile 之后,将具备两种特性:
-
保证此变量对所有的线程的可见性。
-
禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障。
-
不保证变量的原子性
volatile 性能:volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
内存屏障: 是一种 屏障指令,它是的cpu或编译器对 屏障指令的前 和后 所发出的内存操作 执行一个排序的约束。 也叫 内存栅栏 或栅栏指令。
volatile 写之前的操作 都禁止重排序到volatile之后
volatile 读之后的操作 都禁止重排序到volatile之前
volatile写之后volatile读 禁止重排序。
14CAS
解决多线程并发问题的一种乐观锁算法。
解决多线程并发问题的一种乐观锁算法。
底层Unsafe类,提供了硬件级别的原子性操作。
缺点:
1.自旋会影响性能
2.ABA问题
3.无法保证代码块的原子性
15.AQS
AbstractQueuedSynchronizer 抽象队列同步器
aqs是一个锁的底层框架,由volatile定义的一个state(0表示无锁,1表示有锁)和一个阻塞队列(fifo)构建.
分为独占排它锁和共享锁
独占排它锁:底层方法实现
tryAcquire tryRelease isHeldExclusively
共享锁: tryAcquireShared tryReleased isHeldExclusively(判断线程是否有锁)
使用:通过内部类继承AQS重写上面方法。
ReentrantLock:
内部有一个抽象类sync
继承AQS
然后有两个实现类:NonfairSync(非公平)和FairSync(公平的)
使用ReentrantLock通过构造方法传参,不传默认Sync是NonfairSync,如果是true的话Sync就是FairSync
sync = fair ? new FairSync() : new NonfairSync();
ReentrantLock里的lock()方法调用sync的lock()的抽象方法。具体实现由NonfairSync或者FairSync实现。
Sync里有几个方法:
lock:抽象方法
nonfairTryAcquire:
1.判断state的值是否为0,如果为0则cas获取锁,并设置当前线程是有锁线程
2.如果state的值不为0,则判断是否当前线程的锁,则重入(state+1)
3.否则获取锁失败
tryRelease:
1.对state的值进行减1,如果减1后的值为0,则把有锁线程置为null,释放锁成功
2.如果不为0,直接把减1后的值设置给state即可。
isHeldExclusively:判断当前线程是否有锁
NonfairSync
:中的lock() 首先通过CAS操作获取锁,设置当前线程为exclusiveOwnerThread(独有的)有锁线程。
如果state不为0,则调用acquire方法,但是AQS中没有具体实现tryAcquire方法,这里调用的是NonfairSync本身重写的tryAcquire方法,但是这个方法(tryAcquire)又调用了父类(sync)的nonfairTryAcquire()方法。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//AQS的方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
FairSync
:也是实现sync的lock()方法
final void lock() {
acquire(1);
}
也是调用AQS的acquire,
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
然后调用自己的tryAcquire方法 刚调用,先判断有没有锁,没有判断队列前面有没有线程 Predecessors(前任)hasQueuedPredecessors()判断, 没有前任 执行&&后面的内容 设置当前线程状态 如果state的值不为0,判断当前线程锁是不是同一个,如果是就是重入锁 否则返回false(将来会放入等待队列)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
总结:
ReentrantLock
加锁:
公平锁:
lock() --> fairSync.lock() --> AQS.acquire() --> FairSync.tryAcquire()
非公平锁:
lock() --> NonfairSync.lock() --> AQS.acquire() --> NonfairSync.tryAcquire() --> Sync.nonfairTryAcquire()
解锁:
unlock() --> AQS.release() --> Sync.tryRelease()