在文章的开头先明确几个概念:
- 并发:多个线程同时操作同一个对象,并要修改其实例变量
- final 修饰的实例变量线程安全,因为不可变只能初始化一次
- 锁:OS 的调度无法满足同步的需求,需要程序通过调度算法协助调度
- synchronized:JVM 级别锁
- Lock:api 级别
- synchronized:对象的锁,锁的代码
- 通过只允许一个线程执行 sync 内代码,保证了可见性,有序性,原子性
- 并发要求线程交替执行(时间片),而拿了锁会一直将任务执行完再释放(即使n时间片)
- Java 的线程通信实际是共享内存
- synchronized 是悲观锁,独占锁,非公平锁,可重入锁
- 悲观锁 <==> 乐观锁(CAS:注意ABA问题):是否一定要锁
- 独占锁 <==> 共享锁(读锁、写锁):是否可以有多个线程同时拿锁
- 非公平锁 <==> 公平锁:是否按阻塞顺序拿锁
- 可重入锁 <==> 不可重入锁:拿锁线程是否可以多次拿锁
生产者-消费者 demo
要求:
- 容器在一个时刻只能做一件事,要么是在被放入产品,要么是在被取出产品
- 2 个 Producer,在容器未满时一直生产,如果满了就等待
- 10个 Consumer,在容器非空时一直消费,如果为空就等待
public class MyContainer01<T> {
// 容器
LinkedList<T> list = new LinkedList<>();
// 容器最大容量
int MAX_SIZE = 10;
// 保证一个时刻只有一个生产者能生产
synchronized void put(T t) throws Exception{
// while 保证另一个生产者被唤醒后进行二次判断
while (this.list.size() == MAX_SIZE){
// 释放锁,休眠
this.wait();
}
// 生产一个
this.list.add(t);
// 模拟生产过程
TimeUnit.SECONDS.sleep(1);
// 唤醒所有 wait 的线程(包括另一个生产者,和所有消费者)
// 注:这里不用 notify 是因为,只唤醒一个的话,可能唤醒的是另一个生产者
this.notifyAll();
}
// 保证一个时刻只有一个消费者能消费
synchronized T get() throws Exception{
// while 保证唤醒后二次校验
while (this.list.size() == 0){
// 释放锁,休眠
this.wait();
}
// 消费
T t = list.removeFirst();
// 模拟消费过程
TimeUnit.SECONDS.sleep(1);
// 唤醒所有生产者,消费者
// 注:这里不用 notify 是因为,只唤醒一个的话,可能是另一个消费者
this.notifyAll();
return t;
}
// main
public static void main(String[] args) {
// 泛型
MyContainer01<String> container01 = new MyContainer01<>();
// 生产计数
AtomicInteger count = new AtomicInteger(0);
// 启动 2 个生产者
for(int i=1;i<=2;i++){
new Thread(()->{
// 持续生产
while (true){
try {
container01.put(Thread.currentThread().getName()+ "=>" + count.incrementAndGet());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("生产-" + Thread.currentThread().getName()+ "=>" + count.get());
}
},"scThread"+i).start();
}
// 启动 10 个消费者
for(int i=1;i<=10;i++){
new Thread(()-> {
try {
while (true) // 持续消费
System.out.println("消费-"+Thread.currentThread().getName()+"-"+container01.get());
} catch (Exception e) {
e.printStackTrace();
}
},"xfThread"+i).start();
}
}
}