线程间的通信
- wait()/wait(long):释放当前对象持有的对象锁,并进入到阻塞态,直到被**notify()/notifyAll()**通知到或者时间到,来竞争对象锁
- notify():选中通知一个**wait()**进入等待队列的线程来竞争对象锁;
- notifyAll():通知所有等待的线程来竞争对锁;**
CAS:Compare And Swap (比较并交换):我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。 1. 比较 A 与 V 是否相等。(比较) 2. 如果
比较相等,将 B 写入 V。(交换) 3. 返回操作是否成功。
-CAS的实现:
针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:
-
java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
-
unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
-
Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。
简而言之,是因为硬件予以了支持,软件层面才能做到。
- -CAS自旋尝试设置值操作:
自旋实现:
- 循环死等
- 可中断的方式----->interrupt
- 判断循环次数,到达阈值退出
- 判断循环总耗时,到达阈值退出
缺点:
- 前提(很快运行)满足不了,线程一直处于运行态,循环执行CAS性能消耗大
- 线程数量多,导致前提满足不了,或者CPU在很多线程间切换---->性能消耗大
乐观锁和悲观锁:设计上解决线程安全的一中思想
- 乐观锁:乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是
否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。 - 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样
别人想拿这个数据就会阻塞直到它拿到锁。
Callable创建线程:
import java.util.concurrent.*;
/**
* @author liye
* @create 2020-06-30-19:52
* Callable创建线程
* Future/FutureTask
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c= new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("call");
return 1233;
}
};
FutureTask<Integer> task=new FutureTask<Integer>(c);
//FutureTask实现了Runnable接口,然后传Callable参数到它的构造方法中
new Thread(task).start();
System.out.println("main");
Integer r=task.get();//当前线程阻塞等待,直到线程执行完毕,获取到返回值
System.out.println(r);
//线程池使用Callable
ExecutorService pool= Executors.newFixedThreadPool(4);
Future future=pool.submit(c);
System.out.println("main");
System.out.println(future.get());
}
}
**synchronized: **
- 实现原理:通过对象头加锁,monitor锁机制:编译为字节码时,生成monitorenter,monitorexit;
- 分类: 只能升级,不能降级
1.无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。
2.偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。
偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;如果线程处于活动状态,升级为轻量级锁的状态。
3.轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
4.重量级锁指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
- 优化方案:
1.锁粗化:一个线程对一个对象锁反复获取和释放的操作,中间没有其他线程影响,可以合并为一个锁;
2.锁消除:临界区代码中,如果没有对象逃逸出当前线程,可以说本身是线程安全的操作,可以直接不使用锁;
死锁
- 前提条件:同步的本质在于,一个线程等待另一个线程执行完毕之后才可以继续执行,但是如果现在相关的几个线程之间都在互相等待,那么就会造成死锁;
- 产生的必要条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
- 后果:线程阻塞等待,无法向下执行
- 解决方案
① 资源一次性分配(破坏请求与保持条件)
② 可剥夺资源:在线程满足条件时,释放掉已占有的资源
③ 资源有序分配:系统为每类资源赋予一个编号,每个线程按照编号递 请求资源,释放则相反
- 检测手段:使用JDK提供的监控工具,如jconsole,jstack查看线程状态;
lock体系:JDK提供的除了synchronized之外的加锁方式,定义了锁对象来进行锁操作;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author liye
* @create 2020-06-30-19:52
*/
public class CallableTest {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
lock.lock();//设置当前线程的同步状态,并在队列中保存线程及线程的同步状态
try {
}finally {
lock.unlock();//线程出队列
}
}
}
- 实现原理:AQS队列式的同步器,当中保存了队列的头尾节点;
1.实现原理:双端队列保存线程及线程同步状态,并通过CAS提供设置同步状态的方法
2.AQS提供的模板方法:1.独占式获取与释放同步状态;2.共享式获取与释放同步状态;3.查询同步队列中线程等待情况;
- LOCK锁的特点
1.提供公平锁与非公平锁:是否按照入队顺序设置线程同步状态,如果多个线程同时申请加锁操作时是否按照时间顺序操作
2.AQS提供的独占式共享式设置同步状态:1.独占式:只允许一个线程获取锁;2.共享式:允许一定数量的线程共享式获取锁
3.带Reentrant关键字的lock包下的api:可重入锁,代码如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author liye
* @create 2020-06-30-19:52
*/
public class CallableTest {
public static Lock lock=new ReentrantLock();
public static void t1() {
try {
lock.lock();
System.out.println("t1");
} finally {
lock.unlock();
}
}
public static void t2() {
try {
lock.lock();
t1();//t2的锁还没释放,就调用t1()方法,这就叫做重入锁
System.out.println("t2");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
t2();
}
}
ReentrantReadWriteLock提供的读写api
- 使用场景:多个线程执行某个操作时只允许读读并发/并行操作
- 特点:1.公平锁,2.重入锁,3.只允许降级(写—>读)
- 优势:针对读读并发执行,提高效率
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author liye
* @create 2020-06-30-19:52
*/
public class CallableTest {
private static ReadWriteLock LOCK=new ReentrantReadWriteLock();
private static Lock readLock=LOCK.readLock();
private static Lock writeLock=LOCK.writeLock();
public static void readFile() {
try {
readLock.lock();
//io读文件
} finally {
readLock.unlock();
}
}
public static void writeFile() {
try {
writeLock.lock();
//IO写文件
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
//20个线程读文件
for(int i=0;i<20;i++) {
}
//20个线程写文件
for(int i=0;i<20;i++) {
}
}
}
Condition
- 作用:线程间通信
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author liye
* @create 2020-06-30-19:52
*/
public class CallableTest {
private static volatile int SUM=0;
private static Lock LOCK=new ReentrantLock();
private static Condition CONDITION =LOCK.newCondition();
public static void t1() {
try {
LOCK.lock();//相当于synchronized ()
while (SUM>97) {
CONDITION.await();//相当于Thread.wait()
}
SUM+=3;
System.out.println("面包师傅生产了3个面包"+SUM);
CONDITION.signalAll();//相当于notifyAll()/notify()
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
public static void t2() {
try {
LOCK.lock();//相当于synchronized ()
while (SUM<=0) {
CONDITION.wait();//相当于Thread.wait()
}
SUM--;
System.out.println("有客人买走一个面包"+SUM);
CONDITION.signalAll();//相当于notifyAll()/notify()
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
t1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
t2();
}
}
}).start();
}
}
AQS的应用
- CountDownLatch
1.使用场景:在某个线程A某个地方阻塞等待,直到一组线程执行完毕之后,再执行A后续代码;
2.注意:只提供计数器减的操作
@Test
public void t3() throws Exception {
CountDownLatch cdl=new CountDownLatch(20);
for(int i=0;i<20;i++){
Thread t=new Thread(()->{
System.out.println(Thread.currentThread().getName());
cdl.countDown();//计数器-1
});
t.start();
}
//等待所有子线程执行完毕
cdl.await();//当前线程阻塞等待,直到计数器为零
System.out.println(Thread.currentThread().getName());
}
- Semaphore
- 使用场景:
1.:在某个线程A某个地方阻塞等待,直到一组线程执行完毕之后,再执行A后续代码;
@Test
public void t5() throws Exception {
Semaphore s=new Semaphore(0);
for(int i=0;i<20;i++){
Thread t=new Thread(()->{
System.out.println(Thread.currentThread().getName());
s.release();//增加一个资源量
});
t.start();
}
//等到资源总量为给定数值,如果相等继续执行,否则阻塞等待
s.acquire(20);
System.out.println(Thread.currentThread().getName());
}
2.多线程有限资源的访问
@Test
public void t4() throws Exception {
Semaphore s=new Semaphore(1000);
for(;;){
Thread t=new Thread(()->{
//模拟每个线程处理Http请求
try {
//获取一个资源量,总资源量超过1000,阻塞等待
s.acquire();
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();//释放资源
}
});
t.start();
}
}
ThreadLocal
- 使用场景:隔离线程间的变量,保证每个线程是使用自己的变量
import org.junit.Test;
/**
* @author liye
* @create 2020-06-29-14:36
* ThreadLocal和线程绑定
*/
public class ThreadLocalTest {
private static ThreadLocal<String> HOLDER=new ThreadLocal<>();
@Test
public void t1() {
try {
// HOLDER.get();//获取当前线程中,某个ThreadLocal对象的值
// HOLDER.remove();//删除当前线程中,某个ThreadLocal对象的值
// HOLDER.set("abc");//设置当前线程中,某个ThreadLocal对象的值
HOLDER.set("abc");
for(int i=0;i<20;i++) {
final int j=i;
new Thread(()->{
HOLDER.set(String.valueOf(j));
try {
if(j==5) {
Thread.sleep(500);
System.out.println(HOLDER.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
HOLDER.remove();
}
}).start();
}
while (Thread.activeCount()>1) {
Thread.yield();
}
System.out.println(HOLDER.get());
} finally {
HOLDER.remove();
}
}
}