Java多线程学习总结(四)
Java多线程学习总结(四)
——2020.02.24学习总结
1.线程间的通信
wait()和notifyAll()的应用实例:
package com.Zhongger.Day05;
/**
* @Author Zhongger
* @Description 有两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1,一个对该变量减1,实现交替,来10轮
* @Date 2020.2.24 生产者消费者模式
*/
public class TheadWaitNotifyTest {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class AirConditioner
{
private int number=0;
public synchronized void increment() throws InterruptedException {
//1.判断
if (number!=0){
this.wait();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3.通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();
}
}
运行结果为:
在这其中,用到了生产者消费者模式
生产者消费者模式说明:
- 生产者只在仓库未满时进行生产,仓库满时生产者进程被阻塞;
- 消费者只在仓库非空时进行消费,仓库为空时消费者进程被阻塞;
- 当生产者发现仓库满时会通知消费者消费;
A相当于一个生产者,B相当于一个消费者。
现在,如果多加入两个线程,看看程序的运行结果会怎么样?
可以发现,A,C作为生产者,生产的物品多于了1。如果在真正的生产环境中出现这样的问题,会造成难以挽回的后果。所以一定要避免这种情况。
即在多线程交互中,必须要防止多线程的虚假唤醒。解决方案是:判断的地方使用whlie循环
whlie(number!=0){
this.wait();
}
另外,使用ReentrantLock的方式也是可以的,在前两天的博客中有写到。
2.多线程间按顺序调用
package com.Zhongger.Day05;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author Zhongger
* @Description 多线程间按顺序调用,实现A->B->C,A打印5次,B打印10次,C打印15次
* @Date 2020.2.24
*/
public class ThreadOrderAccess {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 0; i < 3; i++) {
shareResource.print5();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
shareResource.print10();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
shareResource.print15();
}
},"C").start();
}
}
//资源类
class ShareResource{
private int number=1;//1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition condition1=lock.newCondition();
private Condition condition2=lock.newCondition();
private Condition condition3=lock.newCondition();
public void print5(){
lock.lock();
try {
//判断
while (number!=1){
condition1.await();
}
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number=2;
condition2.signal();//唤醒了B线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
//判断
while (number!=2){
condition2.await();
}
//干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number=3;
condition3.signal();//唤醒了线程C
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
//判断
while (number!=3){
condition3.await();
}
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number=1;
condition1.signal();//唤醒了线程A
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
使用多个Condition和Signal来精准地通知下一个线程工作。
3.多线程八锁
1.如果一个对象里面有多个synchronized方法,某一时刻内,只要有一个线程去调用其中的一个synchronized方法了,其他线程都只能等待,换句话说,某一时刻内,只能有一个线程去访问这些synchronized方法,synchronized锁的是一个当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他synchronized方法。
2.类中如果有普通方法,和同步锁无关,其他线程可以访问。
3.若有两个资源类对象,锁是不同的,所以不同的线程可以访问不同的资源类对象中的synchronized方法,互不干扰。
4.所有的非静态同步方法都是同一把锁——类的实例。
5.synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为以下三种形式:
- 对于普通的同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是synchronized括号里配置的对象
6.当一个线程试图访问同步代码块时,它必须得到锁,退出或抛出异常时必须释放锁。
7.所有的静态同步方法都是同一把锁——类的Class对象
8.静态同步方法和普通同步方法锁的是不同的对象,两个方法之间是不会有竞争条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象。
好了,今天的多线程学习就到这里,明天继续咯~