多线程Day 03
本文总结自B站狂神说JAVA
1. 线程同步
在Day01中有一个多个线程操作同一个对象的案例,没有处理好并发,所以出现了错误。解决方案:将需要同时访问该对象的线程进入这个对象的等待池形成队列,等前面的线程完毕,下一个线程才可以继续使用,这种方案叫做线程同步。线程同步必须要有队列和锁。说到底线程同步是用来解决线程的安全性问题。缺陷:会损失一部分性能,一个有锁的线程会导致其它需要此锁的线程挂起;一个优先级高的线程如果等待一个优先级低的线程的锁,会导致优先级倒置。
加锁的方法是:在需要同步的方法上添加synchroniezd
关键字。该关键字默认锁的是添加它的方法。也可以生命一个synchroiezd
代码块,将要同步的内容放到代码块中。
1.1 死锁
多个线程都在等待对方释放资源且一直处于停止的状态,某一个代码块同时拥有两个以上对象的锁,就会发生死锁的问题。
1.2 Lock 锁
每次只有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得lock对象。lock是显式锁,需要自动释放,只有代码块锁,jvm花费较少的时间来调用线程调度,性能更好。推荐使用lock。
定义锁:
private final ReentrantLock lock = new ReentrantLock();
加锁:
在要加锁的代码上面:
lock.lock();
解锁:
lock.unlock();
2. 线程协作
生产者消费者模式:生产者和消费者共享一个资源,并且相互依赖互为条件。生产者在完成生产任务时需要通知消费者去消费,消费者在消费完成之后,需要通知生产者再进行生产。
2.1 管程法
把生产者和消费者都放到缓冲区中。不直接的进行通信协作。
package com.rhdemo.PC;
/**
* @date 2020/7/25 10:37
* 缓冲区解决生产者消费者
*/
public class ProductAndCustomer01 {
public static void main(String[] args) {
SyncContainer syncContainer = new SyncContainer();
new Product(syncContainer).start();
new Customer(syncContainer).start();
}
}
// 生产者
class Product extends Thread{
SyncContainer container;
public Product(SyncContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Goods(i));
System.out.println("生产了"+i);
}
}
}
// 消费者
class Customer extends Thread{
SyncContainer syncContainer;
public Customer(SyncContainer syncContainer) {
this.syncContainer = syncContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了"+syncContainer.pop().id);
}
}
}
// 产品
class Goods{
// 编号
int id;
public Goods(int id) {
this.id = id;
}
}
// 缓冲区
class SyncContainer{
// 容器大小
Goods [] goods = new Goods[10];
// 容器计数器
int count = 0;
// 生产者放入产品
public synchronized void push(Goods good){
// 容器满了就等待
if (count == goods.length){
// 通知消费者,等待消费之后再次生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
goods[count] = good;
count++;
// 通知消费者
this.notify();
}
// 消费者进行消费
public synchronized Goods pop(){
if (count == 0){
// 等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 进行消费
count--;
Goods goodObj = goods[count];
// 通知生产者
this.notify();
return goodObj;
}
}
2.2 信号灯法
package com.rhdemo.PC;
/**
* @date 2020/7/25 10:55
* 信号灯法
*/
public class PC2 {
public static void main(String[] args) {
Tv tv = new Tv();
new Pro(tv).start();
new Cus(tv).start();
}
}
// 生产者->表演
class Pro extends Thread{
Tv tv;
public Pro(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
if (i%2 == 0){
this.tv.play("快乐大本营");
}else {
this.tv.play("EDG VS RNG ");
}
}
}
}
// 消费者->观众
class Cus extends Thread{
Tv tv;
public Cus(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
tv.watch();
}
}
}
// 产品
class Tv{
String voice;
boolean flag = true;
// 表演
public synchronized void play(String voice){
System.out.println("表演了"+voice);
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 通知观众
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
// 观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了"+voice);
// 通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
3.线程池
将创建好的线程放到线程池中,使用时获取,用完放回。提高响应速度,降低资源消耗。
/**
* @date 2020/7/25 11:18
* 线程池
*/
public class ThreadPool {
public static void main(String[] args) {
// 创建线程池并且指定大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 开启线程
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 关闭
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}