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.静态同步方法和普通同步方法锁的是不同的对象,两个方法之间是不会有竞争条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象。

好了,今天的多线程学习就到这里,明天继续咯~

发布了31 篇原创文章 · 获赞 78 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43395911/article/details/104471769
今日推荐