欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java多线程学习总结(四)

程序员文章站 2022-05-04 17:54:36
...

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();
    }
}

运行结果为:
Java多线程学习总结(四)
在这其中,用到了生产者消费者模式
生产者消费者模式说明:

  • 生产者只在仓库未满时进行生产,仓库满时生产者进程被阻塞;
  • 消费者只在仓库非空时进行消费,仓库为空时消费者进程被阻塞;
  • 当生产者发现仓库满时会通知消费者消费;

A相当于一个生产者,B相当于一个消费者。
现在,如果多加入两个线程,看看程序的运行结果会怎么样?
Java多线程学习总结(四)
可以发现,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.静态同步方法和普通同步方法锁的是不同的对象,两个方法之间是不会有竞争条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象。

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