知识点
一、wait-notify 正确使用姿势
- 两个方法必须在 synchronized 代码块中调用
- 正确使用方式:
thread1:
synchronized (lock){
while (条件不成立){
lock.wait();
}
//条件成立执行代码
}
thread2:
synchronized (lock){
lock.notifyAll();
}
二、ReentrantLock正确使用姿势
reentrantLock.lock();
try {
//业务代码
}finally {
reentrantLock.unlock();
}
三 、synchronized -> ReentrantLock 代码转换
转换过程:
1、synchronized 锁对象继承 ReentrantLock
2、synchronized 代码替换为 ReentrantLock 代码
3、wait-notify 替换为 await-signal
synchronized :
package LockSync;
import lombok.extern.slf4j.Slf4j;
public class Test {
public static void main(String[] args) {
Message message = new Message();
new Thread(() -> {
message.getName();
}, "t1").start();
new Thread(() -> {
message.setName("张三");
}, "t2").start();
}
}
@Slf4j
class Message {
MyLock lock = new MyLock();
String name;
//获取姓名
String getName() {
synchronized (lock) {
while (name == null) {
try {
log.info("获取姓名阻塞");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("获取姓名成功:{}",name);
return name;
}
}
//设置姓名
void setName(String name) {
synchronized (lock) {
log.info("设置姓名:{}",name);
this.name = name;
lock.notifyAll();
}
}
}
class MyLock {
}
ReentrantLock:
package LockSync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
Message message = new Message();
new Thread(() -> {
message.getName();
}, "t1").start();
new Thread(() -> {
message.setName("张三");
}, "t2").start();
}
}
@Slf4j
class Message {
MyLock lock = new MyLock();
Condition condition = lock.newCondition();
String name;
//获取姓名
String getName() {
lock.lock();
try {
while (name == null) {
try {
log.info("获取姓名阻塞");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("获取姓名成功:{}", name);
return name;
} finally {
lock.unlock();
}
}
//设置姓名
void setName(String name) {
lock.lock();
try {
log.info("设置姓名:{}", name);
this.name = name;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
class MyLock extends ReentrantLock {
}
四、证明 synchronized 可见性
代码
package Thread7;
public class Test {
public static void main(String[] args) {
Message message = new Message();
new Thread(() -> {
message.start();
}, "t1").start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
message.setFlag();
}, "t2").start();
}
}
class Message {
private Boolean flag = false;
private Object lock = new Object();
public void start() {
while (!flag) {
synchronized (lock) {
}
}
}
public void setFlag() {
flag = true;
}
}
结论
synchronized 锁 this、class对象、某一个变量,都会导致该类所有共享变量可见,每次获取锁都重新从主内存读取最新的共享变量。
六、synchronized 有序性
synchronized的有序性实际上是指线程间操作synchronized块的有序,对于synchronized块中的代码,其并不能保证不会发生指令重排现象。(DCL问题)
五、volatile知识点
1、读写屏障
- 写屏障:对volatile变量写指令后加入写屏障。
- 读屏障:对volatile变量读指令前加入读屏障。
2、可见性
- 写屏障保证在该屏障之前,对共享变量(包括非volatile变量)的改动都同步到主存。
- 读屏障保证在该屏障之后,对共享变量(包括非volatile变量)的读取,加载的都是主存最新数据。
3、有序性
- 写屏障保证指令重排,保证写屏障之前的代码(字节码指令)不会排到写屏障之后。
- 读屏障保证指令重排,保证读屏障之后的代码(字节码指令)不会排到读屏障之前。
- 注意:有序性保证指令重排序,创建对象的指令包括:1、分配内存空间 2、初始化对象 3、将内存空间的地址赋值给对应的引用 (DCL问题)
4、总结
volatile变量在所有变量中尽量最后写、最先读。
七、CAS
特征:
无锁并发,没有阻塞,不会引起上下文切换,不会早成CPU用户态和内核态之间的转换。
注意:
CAS必须借助volatile才能实现交换。
使用:
一般借助while(true)循环重试
八、不可变类
思路:
可变类 -> 线程安全问题 -> 不可变类 -> 使用保护性拷贝 -> 拷贝对象太多 -> 享元模式
应用:
Boolean、Integer、Byte、Short、Long、Character
String
BigDecimal、BigInteger
注意:
单个方法是线程安全的,多个方法之间的组合不是线程安全的。
九、线程池笔记
1、线程池里有两个集合,一个是线程集合HashSet,另一个是阻塞队列BlockingQueue
2、阻塞队列使用 ReentrantLock 锁,使用两个Condition 分别存储队列空和队列满时的等待线程
3、线程池执行任务时,若线程数低于核心线程数,创建Worker对象,传入任务,调用start方法,并将对象存入works组合;否则存入阻塞队列。
4、Worker继承Thread后重写run方法,采用 while (task != null || (task = getTask()) != null) 循环调用传入的或阻塞队列里的任务。
5、allowCoreThreadTimeOut == false,表示保存核心线程数不被销毁,实现方式:
getTask()方法中当前线程数小于等于核心线程数时,调用workQueue.take()方法,该方法会调用 ReentrantLock.await() 进行阻塞,所以 getTask() 不会返回null,当前Worker不会销毁。
实现方式
package thread8;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 自定义线程池
*/
@Slf4j
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPool pool = new ThreadPool(2, 4, 6);
for (int i = 0; i < 20; i++) {
int finalI = i;
pool.excute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("任务" + finalI);
});
}
}
}
/**
* 线程池
*/
@Slf4j
class ThreadPool {
//工作线程集合
private HashSet<Worker> workers;
//阻塞队列
private TaskPool taskPool;
//核心线程数
private int coreSize;
//最大线程数
private int maxCoreSize;
private int capcity;
ThreadPool(int coreSize, int maxCoreSize, int capcity) {
this.coreSize = coreSize;
this.maxCoreSize = maxCoreSize;
this.workers = new HashSet<>();
this.capcity = capcity;
this.taskPool = new TaskPool(capcity);
}
//执行线程
public void excute(Runnable runable) {
//线程集合未满
synchronized (workers) {
if (workers.size() < coreSize || (taskPool.getSize() == capcity && workers.size() < maxCoreSize)) {
Worker worker = new Worker(runable);
worker.start();
log.info("创建线程");
workers.add(worker);
} else {
taskPool.add(runable);
}
}
}
/**
* 工作线程
*/
class Worker extends Thread {
private Runnable runnable;
Worker(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void run() {
//执行传入进来的Runable和阻塞队列中的Runable
while (runnable != null || (runnable = taskPool.get()) != null) {
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
runnable = null;
}
}
//任务执行完之后删除线程(可以根据核心线程池决定是否保存线程)
workers.remove(this);
}
}
}
/**
* 阻塞队列
*/
@Slf4j
class TaskPool {
private LinkedList<Runnable> tasks = new LinkedList();
ReentrantLock lock = new ReentrantLock();
Condition emptyWait = lock.newCondition();
Condition fullWait = lock.newCondition();
private int capcity;
TaskPool(int capcity) {
this.capcity = capcity;
}
public Integer getSize() {
int size = 0;
size = tasks.size();
return size;
}
//获取线程
Runnable get() {
lock.lock();
try {
while (tasks.isEmpty()) { //阻塞队列为空,进入阻塞等待
log.info("阻塞队列为空");
try {
emptyWait.await(); //此处可以添加拒绝策略
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//阻塞队列不为空,返回数据,唤醒阻塞队列
Runnable runnable = tasks.removeFirst();
log.info("出队列");
fullWait.signalAll();
return runnable;
} finally {
lock.unlock();
}
}
//添加线程
void add(Runnable runnable) {
lock.lock();
try {
while (tasks.size() == capcity) { //队列已满,插入等待
log.info("队列已满");
fullWait.await();
}
//队列未满,插入数据,唤醒阻塞队列
tasks.add(runnable);
log.info("进队列");
emptyWait.signalAll();
} catch (InterruptedException e) {
} finally {
lock.unlock();
}
}
}
十、线程池大小设置
1、CPU密集型
线程数 = 核数+1
2、IO密集型
线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间+等待时间)/ CPU计算时间
例如:4核CPU计算时间50%,等待时间50%,期望CPU利用率 100%:
4 * 100% * 100% / 50% = 8
多线程设计模式
一、保护性暂停
- 作用 :一个线程等待另一个线程的执行结果
- 应用:JDK的 join、future 实现原理
1、生产消费不解耦
package thread;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class GuardedObjectTest {
public static void main(String[] args) {
final GuardedObject guardedObject = new GuardedObject();
new Thread(() -> {
log.info("等待结果");
guardedObject.get(5000);
}, "thread1").start();
new Thread(() -> {
//执行业务代码
log.info("获取结果");
Object response = new Object();
guardedObject.complete(response);
}, "thread1").start();
}
}
class GuardedObject {
//结果
private Object response;
//获取结果
//timeout 等待时间
public Object get(long timeout) {
//开始等待时间
long begin = System.currentTimeMillis();
//已等待时间
long passedTime = 0;
synchronized (this) {
while (response == null) {
//开始等待前判断是否需要继续等待
if(passedTime >= timeout){
break;
}
try {
this.wait(timeout - passedTime); //被虚假唤醒后继续等待
} catch (InterruptedException e) {
e.printStackTrace();
}
//结束等待后计算等待时间
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}
//产生结果
public void complete(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
2、生产消费解耦
第一种方式只有一个目标对象,需要生产一个消费一个。
给目标对象增加ID编码,采用享元模式缓存目标对象,解耦生产者与消费者
与生产-消费模式有区别,这里要求生产与消费一一对应。
package thread2;
import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Consumer().start();
}
Thread.sleep(2000);
for (Integer id : Future.getIds()) {
new Producer(id, "内容" + id).start();
}
}
}
/**
* 消费者
*/
@Slf4j
class Consumer extends Thread {
@Override
public void run() {
GuardedObject guardedObject = Future.creatureFuture();
log.info("希望获取记录{}", guardedObject.getId());
Object response = guardedObject.get(5000);
log.info("获取记录{}", response);
}
}
/**
* 生产者
*/
@Slf4j
class Producer extends Thread {
private int id;
private Object response;
Producer(int id, Object response) {
this.id = id;
this.response = response;
}
@Override
public void run() {
GuardedObject guardedObject = Future.getGuardedObject(id);
log.info("设置返回值{}", response);
guardedObject.complete(response);
}
}
/**
* 工具类
*/
class Future {
private static Map<Integer, GuardedObject> map = new Hashtable<>();
private static int id = 0;
//生成主键ID
private static synchronized int generateId() {
return id++;
}
//获取当前存在的所有的ID
public static Set<Integer> getIds() {
return map.keySet();
}
//创建目标对象
public static GuardedObject creatureFuture() {
GuardedObject myObject = new GuardedObject(generateId());
map.put(myObject.getId(), myObject);
return myObject;
}
//获取目标对象
public static GuardedObject getGuardedObject(Integer id) {
return map.remove(id);
}
}
/**
* 目标类
*/
class GuardedObject {
//结果
private Object response;
//增加ID
private Integer id;
GuardedObject(Integer id) {
this.id = id;
}
//获取结果
//timeout 等待时间
public Object get(long timeout) {
//开始等待时间
long begin = System.currentTimeMillis();
//已等待时间
long passedTime = 0;
synchronized (this) {
while (response == null) {
//开始等待前判断是否需要继续等待
if (passedTime >= timeout) {
break;
}
try {
this.wait(timeout - passedTime); //被虚假唤醒后继续等待
} catch (InterruptedException e) {
e.printStackTrace();
}
//结束等待后计算等待时间
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}
//产生结果
public void complete(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
public Integer getId() {
return id;
}
}
返回结果:
二、生产消费者模式
- 作用:平衡生产者消费者线程资源,不需要线程一一对应
- 应用:JDK阻塞队列
- 注意:调用wait、notifyAll方法的对象必须是 synchronized 锁的对象
1、标准实现
package thread3;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(2);
new Thread(()->{
while (true){
try {
Thread.sleep(1000);
queue.getMessage();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者").start();
for (int i = 0;i< 3;i++){
int finalI = i;
new Thread(()->{
queue.putMessage(new Message(finalI, "消息体"+finalI));
},"生产者"+finalI).start();
}
}
}
/**
* 消息队列
*/
@Slf4j
class MessageQueue {
private static LinkedList<Message> list = new LinkedList();
private static Integer capcity;
MessageQueue(Integer capcity) {
this.capcity = capcity;
}
/**
* 获取消息
*/
public Message getMessage() {
synchronized (list) {
while (list.isEmpty()) {
try {
log.info("队列为空");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = list.removeFirst();
log.info("获取消息{}",message);
list.notifyAll();
return message;
}
}
/**
* 插入消息
*/
public void putMessage(Message message){
synchronized (list){
while (list.size() >= capcity){
try {
log.info("队列已满");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("插入消息{}",message);
list.add(message);
list.notifyAll();
}
}
}
/**
* 消息组件
*/
final class Message {
private Integer id;
private String name;
Message(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "id:"+id+",name:"+name;
}
}
三、固定运行顺序
1、wait、notify方式
package Thread4;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test {
private static final Object lock = new Object();
private static Boolean flsg = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
while (!flsg) {
try {
log.info("t2未执行,等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("t1执行");
}
}, "t1").start();
new Thread(() -> {
synchronized (lock) {
log.info("t2执行");
flsg = true;
lock.notify();
}
}, "t2").start();
}
}
2、park、unpark方式:
package Thread4;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j
public class Test2 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
LockSupport.park();
log.info("t1执行");
}, "t1");
thread.start();
new Thread(() -> {
log.info("t2执行");
LockSupport.unpark(thread);
}, "t2").start();
}
}
四、交替输出
1、wait-notify
package Thread5;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test {
private static Integer flag = Integer.valueOf(1);
private static final Object LOCK = new Object();
public static void main(String[] args) {
Message message = new Message(5);
new Thread(() -> {
message.print(1,2,"a");
}, "t1").start();
new Thread(() -> {
message.print(2,3,"b");
}, "t2").start();
new Thread(() -> {
message.print(3,1,"c");
}, "t3").start();
}
}
@Slf4j
class Message {
private Integer flag = 1;
private Integer loopNumber;
Message(Integer loopNumber) {
this.loopNumber = loopNumber;
}
public void print(Integer waitFlag,Integer nextFlag,String str) {
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
while (flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info(str);
flag = nextFlag;
this.notifyAll();
}
}
}
}
2、await-signal(一个Condition)
package Thread5;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 交替输出
*/
@Slf4j
public class Test2 {
public static void main(String[] args) {
Message2 message2= new Message2(5,new ReentrantLock());
new Thread(() -> {
message2.print(1, 2, "a");
}, "t1").start();
new Thread(() -> {
message2.print(2, 3, "b");
}, "t2").start();
new Thread(() -> {
message2.print(3, 1, "c");
}, "t3").start();
}
}
@Slf4j
class Message2 {
private Integer flag = 1;
private Integer loopNumber;
private Condition condition;
private ReentrantLock reentrantLock;
Message2(Integer loopNumber, ReentrantLock reentrantLock) {
this.loopNumber = loopNumber;
this.reentrantLock = reentrantLock;
this.condition = reentrantLock.newCondition();
}
public void print(Integer waitFlag, Integer nextFlag, String str) {
for (int i = 0; i < loopNumber; i++) {
reentrantLock.lock();
try {
while (flag != waitFlag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info(str);
flag = nextFlag;
condition.signalAll();
}finally {
reentrantLock.unlock();
}
}
}
}
3、await-signal(多个Condition)
package Thread5;
import lombok.extern.slf4j.Slf4j;
import org.omg.PortableServer.THREAD_POLICY_ID;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 交替输出
*/
@Slf4j
public class Test3 {
public static void main(String[] args) {
Message3 message = new Message3(5);
Condition a = message.newCondition();
Condition b = message.newCondition();
Condition c = message.newCondition();
new Thread(() -> {
message.print(a, b, "a");
}, "t1").start();
new Thread(() -> {
message.print(b, c, "b");
}, "t2").start();
new Thread(() -> {
message.print(c, a, "c");
}, "t3").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
message.lock();
try{
a.signal();
}finally {
message.unlock();
}
}
}
@Slf4j
class Message3 extends ReentrantLock {
private Integer loopNumber;
Message3(Integer loopNumber) {
this.loopNumber = loopNumber;
}
public void print(Condition thisCondition, Condition nextCondition, String str) {
for (int i = 0; i < loopNumber; i++) {
lock();
try {
thisCondition.await();
log.info(str);
nextCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
4、park-unpark:
package Thread5;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
/**
* 交替输出
*/
@Slf4j
public class Test4 {
static Thread a;
static Thread b;
static Thread c;
public static void main(String[] args) {
Message4 message = new Message4(5);
a = new Thread(() -> {
message.print(b, "a");
}, "t1");
b = new Thread(() -> {
message.print(c, "b");
}, "t2");
c = new Thread(() -> {
message.print(a, "c");
}, "t3");
a.start();
b.start();
c.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(a);
}
}
@Slf4j
class Message4 {
private Integer loopNumber;
Message4(Integer loopNumber) {
this.loopNumber = loopNumber;
}
public void print(Thread nextThread, String str) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
log.info(str);
LockSupport.unpark(nextThread);
}
}
}
五、两阶段终止
1、interrupt打断
package thread6;
import lombok.extern.slf4j.Slf4j;
/**
* 两阶段终止
*/
@Slf4j
public class Test {
public static void main(String[] args) {
Message message = new Message();
new Thread(() -> {
log.info("t1开始线程");
message.start();
}, "t1").start();
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
log.info("t2打断线程");
message.stop();
}, "t2").start();
}
}
@Slf4j
class Message {
private Thread thread;
public void start() {
thread = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.info("循环被打断,退出循环");
break;
} else {
try {
Thread.sleep(2000);
log.info("正常执行");
} catch (InterruptedException e) {
log.info("睡眠被打断");
Thread.currentThread().interrupt();
}
}
}
}, "monitor");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
2、volatile版本
也需要调用 interrupt 打断 sleep
package thread6;
import lombok.extern.slf4j.Slf4j;
/**
* 两阶段终止
*/
@Slf4j
public class Test {
public static void main(String[] args) {
Message message = new Message();
new Thread(() -> {
log.info("t1开始线程");
message.start();
}, "t1").start();
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
log.info("t2打断线程");
message.stop();
}, "t2").start();
}
}
@Slf4j
class Message {
private Thread thread;
private volatile Boolean stop = false;
public void start() {
thread = new Thread(() -> {
while (true) {
if (stop) {
log.info("循环被打断,退出循环");
break;
} else {
try {
Thread.sleep(2000);
log.info("正常执行");
} catch (InterruptedException e) {
log.info("睡眠被打断");
}
}
}
}, "monitor");
thread.start();
}
public void stop() {
stop = true;
thread.interrupt();
}
}
六、犹豫模式
1、定义
一个线程发现另一个线程已经做过某件事,所以本线程不再重复操作
2、代码(基于两阶段终止)
private volatile Boolean starting = false;
public void start() {
synchronized (this) {
if (starting) {
return;
}
starting = true;
}
//创建新线程
}