多线程详解下(21.CopyOnWriteArrayList22.死锁23.Lock锁24.生产者消费者问题25.管程法26.信号灯法27.线程池28.总结)
//测试JUC安全类型的集合
//CopyOnWriteArrayList这个类是别人写好的,直接用,不用锁,本身是安全的
public class TextJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
22.死锁
-
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有两个以上的对象的锁时,就会发生死锁的问题
-
死锁
//死锁:多个线程相互抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有 一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
-
解决死锁
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有 一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
-
死锁避免方法
-
产生死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用
-
请求与保持条件:一个进程因请求资源而被阻塞时,对已获得的资源保持不放
-
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
-
循环等待条件:若干进程之间形成一种头尾相接的循环等资源关系
-
-
注: 上面列出了四个死锁必要条件,我们只要先办法破其中的任意一个或多个条件就可以避免死锁发生
23.Lock锁
1Lock锁
-
从JDK5.0开始,java提供了更强大的线程同步机制-------通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
java.util.concurrent.locks.ReentrantLock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Loke对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
2测试Lock锁
-
测试前
//测试Lock锁
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums = 10;
-
测试后
//测试Lock锁
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums = 10;
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
24.生产者消费者问题
1线程通信
-
应用场景:生产者和消费者的问题
-
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
-
如果仓库中没有产品,则生产者将产品放入仓库库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
-
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
-
2线程通信-分析
-
这是一个线程同步问题,生产者和消费者共同享受一个资源,并且生产者和消费者之间相互依赖,互为条件
-
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,有需要马上通知消费者消费
-
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
-
在生产者和消费者问题中,仅有synchronized 是不够的
-
synchronized可阻止并发更新同一个共享资源,实现同步
-
synchronized不能用来实现不同线程之间的消息传递(通信)
-
-
3java提供了几个方法解决线程之间的通信问题
-
解决方式一:
-
并发协作模式“生产者/消费者模式”----->管程法
-
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
-
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
-
缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
-
-
生产者将生产好的数据放入缓冲区,消费者从缓冲区中拿出数据
-
解决方法二:
-
并发协作模式“生产者/消费者模式”----->信号灯
-
25.管程法
//测试:生产者消费者模型--->利用缓冲区解决:管程法
//生产者 消费者 产品 缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container= container;
}
//生产