线程间通信

一、等待,通知机制

1.1 while()语句轮询机制

用while语句轮询机制虽然可以实现线程间的通信,但是如果轮询等待时间间隔很小,更浪费CPU资源,如果轮询等待的时间间隔很大,有可能会取不到想要的数据。

1.2 等待,通知机制

  • wait()方法
  • wait()方法使当前执行的代码的线程进行等待,该方法是Object类的方法,该方法用来将当前线程置入“预执行队列中”,并且在wait所在的代码行处停止,直到接到通知或者中断为止。
  • 在调用wait方法之前,线程必须获得该对象的对象级锁,即只能在同步方法中调用wait()方法。
  • 执行wait方法之后,线程释放锁。在从wait方法返回之前,线程之间竞争重新获得锁
  • notify()方法
  • 同步代码块中调用
  • 调用前,线程也必须获得对象级锁
  • 该方法用来通知等待的线程,如果有多个线程在等待,则由线程规划器随机挑选一个正在等待的线程,对其发出notify通知,并使它等待获得对象级的锁。
    *在执行notify方法后,当前线程不会马上释放锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才释放锁。呈wait状态的线程才能获得锁。

1.3 验证执行notify()方法后线程不会立即释放锁

等待线程:

public class waitThread extends Thread{
    Object lock;
    public waitThread(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println("等待方法运行前");
                lock.wait();
                System.out.println("等待方法运行后");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

通知线程:

public class notifyThread extends Thread {
    Object lock;
    public notifyThread(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println("通知方法运行前");
                lock.notify();
                System.out.println("通知方法运行后");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

主方法:

public class main {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            waitThread waitThread = new waitThread(lock);
            notifyThread notifyThread = new notifyThread(lock);
            waitThread.start();
            Thread.sleep(1000);
            notifyThread.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

等待方法运行前
通知方法运行前
通知方法运行后
等待方法运行后

说明:线程在执行nofity()方法后,并不会立即释放锁,而是等notify()方法所在的同步块代码执行完后,再释放锁。

1.4 锁对象拥有的队列

  • 就绪队列
    存储了将要获得所锁的线程,一个线程被唤醒后就会进入就绪队列
  • 阻塞队列
    阻塞队列存储了被阻塞的线程,一个线程被wait后,会进入阻塞队列

1.5 interrupt方法遇到wait方法

当线程处于wait状态时,调用线程对象的interrupt方法会出现InterruptException异常

1.6 小结

  • 执行完对象的同步代码后会释放锁
  • 执行同步代码的过程中,遇到异常而导致线程终止,锁也会被释放掉。
  • 执行了锁所属对象的wait()方法,这个线程会释放锁,而此线程会进入线程等待池中,等待被唤醒。

二、等待,通知方法

2.1 只通知一个下线程

  • notify()方法一次只随机通知一个线程进行唤醒
  • 多次调用notify方法时,会随机将等待wait状态的线程进行唤醒

2.2 唤醒所有线程

  • notifyAll()方法可以使所有正在等待队列中等待统一共享资源的全部线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程先执行,但也有可能是随机执行。

2.3 wait(long)方法

等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

2.4 while替换if

while和if的区别:
如果是if :线程被唤醒,不会去再次执行if中的判断语句,如果是while,线程被唤醒会再次执行条件的判断。这也就是下面的语句中while会比if多输出一句。

mylist:

public class mylist {
    List list;
    public mylist(List list){
        this.list = list;
    }
    public void addUser(){
        list.add("你好");
    }
    public int size(){
        return list.size();
    }
    public void remove(){
        list.remove(0);
    }
}

waitThread:


public class waitThread extends Thread{
    mylist list;
    public waitThread(mylist list){
        this.list = list;
    }
    @Override
    public void run() {
        try {
            synchronized (list){
                if(list.size() == 0){
                    System.out.println("处于等待状态");
                    list.wait();
                }
                list.remove();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

notifyThread:


public class notifyThread extends Thread {
    mylist list;
    static int count = 0;
    public notifyThread(mylist list) {
        this.list = list;
    }
    //添加一个对象那个
    @Override
    public void run() {
            synchronized (list) {
                list.addUser();
                list.notifyAll();
                System.out.println("添加了第" + ++count + "个对象");
        }
    }
}

主方法:

public class main {
    public static void main(String[] args) {
        try {
            ArrayList<Object> list = new ArrayList<>();
            mylist lock = new mylist(list);
            waitThread waitThread = new waitThread(lock);
            waitThread waitThread1 = new waitThread(lock);
            notifyThread notifyThread = new notifyThread(lock);
            waitThread.start();
            waitThread1.start();
            Thread.sleep(1000);
            notifyThread.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

处于等待状态
处于等待状态
添加了第1个对象
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at com.example.demo.other.mylist.remove(mylist.java:18)
	at com.example.demo.other.waitThread.run(waitThread.java:18)

原因:

两个移除方法的线程首先被阻塞,notify线程添加一个元素并执行notifyAll方法会唤醒两个移除的线程,移除两次,但是只添加了一个元素,就会出现异常。

解决方法:

将if换为while

public class waitThread extends Thread{
    mylist list;
    public waitThread(mylist list){
        this.list = list;
    }
    @Override
    public void run() {
        try {
            synchronized (list){
                while(list.size() == 0){
                    System.out.println("处于等待状态");
                    list.wait();
                }
                list.remove();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

处于等待状态
处于等待状态
添加了第1个对象
处于等待状态

三、生产者和消费者模式

3.1 一生产者与一消费者(操作值)

锁:

public class myLock {
    Boolean flag;
    public myLock(Boolean flag){
        this.flag  = flag;
    }
}

生产者类:

public class produce {
    myLock myLock;
    public produce(myLock myLock){
        this.myLock = myLock;
    }
    public void produceRes(){
        try {
            synchronized (myLock){
                if(myLock.flag == true){
                    myLock.wait();
                }
                //没有就生产
                System.out.println("生产者生产");
                myLock.flag = true;
                myLock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

消费者类:

public class spend {
    myLock myLock;
    public spend(myLock myLock){
        this.myLock = myLock;
    }
    public void spendRes(){
        try {
            synchronized (myLock){
                if(myLock.flag == false){
                    myLock.wait();
                }
                System.out.println("消费者消费");
                myLock.flag = false;
                myLock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

生产者线程:

public class pthread extends Thread{
    produce produce;
    public pthread(produce produce){
        this.produce = produce;
    }
    @Override
    public void run() {
        while (true) {
            produce.produceRes();
        }
    }
}

消费者线程:

public class sthread extends Thread {
    spend spend;
    public sthread(spend spend){
        this.spend = spend;
    }
    @Override
    public void run() {
        while (true){
            spend.spendRes();
        }
    }
}

主方法:

public class main {
    public static void main(String[] args) {
        myLock myLock = new myLock(false);
        spend spend = new spend(myLock);
        produce produce = new produce(myLock);
        sthread sthread = new sthread(spend);
        pthread pthread = new pthread(produce);
        sthread.start();
        pthread.start();
    }
}

结果:

生产者生产
消费者消费
生产者生产
消费者消费
生产者生产
消费者消费
......

3.2 多生产与多消费:操作值-假死

生产者:

public class produce {
    myLock myLock;
    public produce(myLock myLock){
        this.myLock = myLock;
    }
    public void produceRes(){
        try {
            synchronized (myLock) {
                if (myLock.flag == true) {
                    System.out.println("生产者处于waiting" + myLock.flag);
                    myLock.wait();
                }
                if (myLock.flag == false) {
                    //没有就生产
                    myLock.flag = true;
                    System.out.println("生产者running" + myLock.flag);
                    myLock.notify();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

消费者:

public class spend {
    myLock myLock;
    public spend(myLock myLock){
        this.myLock = myLock;
    }
    public void spendRes(){
        try {
            synchronized (myLock) {
                if (myLock.flag == false) {
                    System.out.println("消费者处于waiting" + myLock.flag);
                    myLock.wait();
                }
                if (myLock.flag == true) {
                    myLock.flag = false;
                    System.out.println("消费者running" + myLock.flag);
                    myLock.notify();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

主线程:

public class main {
    public static void main(String[] args) {
        myLock myLock = new myLock(false);
        spend spend = new spend(myLock);
        produce produce = new produce(myLock);
        for (int i = 0; i < 20; i++) {
            sthread sthread = new sthread(spend);
            pthread pthread = new pthread(produce);
            sthread.start();
            pthread.start();
        }
    }
}

结果:

......
消费者runningfalse
消费者处于waitingfalse
生产者runningtrue
生产者处于waitingtrue
消费者runningfalse
消费者处于waitingfalse
生产者runningtrue
生产者处于waitingtrue
生产者处于waitingtrue

原因:

生产者唤醒了生产者,而不是消费者,因为notify方法可能唤醒的是同类线程

解决方法:

使用notifyAll方法,他就会唤醒所有的线程,在唤醒异类线程后,异类线程执行的条件就会满足,就会去执行。

结果:

.......
生产者处于waitingtrue
生产者处于waitingtrue
消费者runningfalse
消费者处于waitingfalse
生产者runningtrue
生产者处于waitingtrue
消费者runningfalse

虽然也会使同类型线程处于等待状态,但是此时执行条件已经满足异类线程去执行,notifyAll方法已经唤醒所有线程,只要等到cpu分配到资源就回去执行,之间还可能出现多个同类线程处于等待状态。

3.3 一生产与一消费:操作栈

用ArrayList模拟栈:

public class myLock {
    ArrayList arrayList;
    public myLock( ArrayList arrayList){
    this.arrayList = arrayList;
    }
}

生产者线程:

public class produce {
    myLock myLock;
    public produce(myLock myLock){
        this.myLock = myLock;
    }
    public void produceRes(){
        try {
            synchronized (myLock) {
                if (myLock.arrayList.size() == 1) {
                    System.out.println("生产者处于waiting" + myLock.arrayList.size());
                    myLock.wait();
                }
                if (myLock.arrayList.size() == 0) {
                    //没有就生产
                    myLock.arrayList.add("hello");
                    System.out.println("生产者running" + myLock.arrayList.size());
                    myLock.notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

消费者线程:

public class spend {
    myLock myLock;
    public spend(myLock myLock){
        this.myLock = myLock;
    }
    public void spendRes(){
        try {
            synchronized (myLock) {
                if (myLock.arrayList.size() == 0) {
                    System.out.println("消费者处于waiting" + myLock.arrayList.size());
                    myLock.wait();
                }
                if (myLock.arrayList.size() == 1) {
                    myLock.arrayList.remove(0);
                    System.out.println("消费者running" + myLock.arrayList.size());
                    myLock.notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

主方法:

public class main {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        myLock myLock = new myLock(arrayList);
        spend spend = new spend(myLock);
        produce produce = new produce(myLock);
            sthread sthread = new sthread(spend);
            pthread pthread = new pthread(produce);
            sthread.start();
            pthread.start();
    }
}

结果:

消费者处于waiting0
生产者running1
生产者处于waiting1
消费者running0
消费者处于waiting0
生产者running1
生产者处于waiting1
消费者running0
.......

3.4 多生产与多消费

主方法:

public class main {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        myLock myLock = new myLock(arrayList);
        spend spend = new spend(myLock);
        produce produce = new produce(myLock);
        for (int i = 0; i < 20; i++) {
            sthread sthread = new sthread(spend);
            pthread pthread = new pthread(produce);
            sthread.start();
            pthread.start();
        }
    }
}

消费者处于waiting0
生产者running1
生产者处于waiting1
生产者处于waiting1
生产者处于waiting1
生产者处于waiting1

解决方法:

notify方法换成notifyAll方法,记住用while,用if就会出错。

结果:

.......
消费者running0
消费者处于waiting0
生产者running1
生产者处于waiting1
生产者处于waiting1
消费者running0
消费者处于waiting0
消费者处于waiting0
......

3.5 一生产与多消费

主方法:

public class main {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        myLock myLock = new myLock(arrayList);
        spend spend = new spend(myLock);
        produce produce = new produce(myLock);
        sthread sthread = new sthread(spend);
        sthread.start();
        for (int i = 0; i < 20; i++) {
            pthread pthread = new pthread(produce);
            pthread.start();
        }
    }
}

结果:

生产者处于waiting1
生产者处于waiting1
消费者running0
消费者处于waiting0

3.6 多生产与一消费

public class main {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        myLock myLock = new myLock(arrayList);
        spend spend = new spend(myLock);
        produce produce = new produce(myLock);
        pthread pthread = new pthread(produce);
        pthread.start();
        for (int i = 0; i < 20; i++) {
            sthread sthread = new sthread(spend);
            sthread.start();
        }
    }
}

结果:

消费者running0
消费者处于waiting0
消费者处于waiting0
消费者处于waiting0
消费者处于waiting0
消费者处于waiting0
消费者处于waiting0
消费者处于waiting0

3.7 管道流

管道流pipeStream是一种特殊的流,用于在不同线程之间传送数据,一个线程发送数据到输出管道,另一个线程从输入管道间读数据。

JDk提供的4个类来使线程间进行通信:

  • PipeInoutStream和PipeOutputStream
  • PipedReader和PipedWriter

3.8 通过管道进行线程间通信(字节流)

使用时要进行连接
写出:

public class spend {
    PipedOutputStream out;
    public spend(PipedOutputStream out){
        this.out = out;
    }
    public void spendRes(PipedOutputStream out){
        try {
            String str = "卡三等奖阿卡丽时间段来";
            out.write(str.getBytes());
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读入:

public class produce {
    PipedInputStream in;
    public produce(PipedInputStream in){
        this.in = in;
    }
    public void produceRes(PipedInputStream in){
        try {
            byte  array[] = new byte[1024];
            int flag = 0;
            flag = in.read(array);
            while (flag!= -1){
                String s = new String(array, 0, array.length);
                System.out.println(s);
                flag = in.read(array);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读入线程:

public class pthread extends Thread{
    produce produce;
    PipedInputStream inputStream;
    public pthread(produce produce,PipedInputStream inputStream ){
        this.produce = produce;
        this.inputStream = inputStream;
    }
    @Override
    public void run() {
            produce.produceRes(inputStream);
    }
}

写出线程:

public class sthread extends Thread {
    spend spend;
    PipedOutputStream out;
    public sthread(spend spend,PipedOutputStream out ){
        this.spend = spend;
        this.out = out;
    }
    @Override
    public void run() {
            spend.spendRes(out);
    }
}

3.9 通过管道进行线程间通信(字符流)

读入类:

public class Read {
    PipedReader reader;
    public Read(PipedReader reader){
        this.reader = reader;
    }
    public void readSth() {
        try {
            char array[] = new char[1024];
            int flag = 0;
            while ((flag = reader.read(array)) != -1){
                String s = new String(array, 0, array.length);
                System.out.println(s);
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

写出类:

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedWriter;

public class Writer {
  PipedWriter writer;
  public Writer(PipedWriter writer){
      this.writer = writer;
  }
  public void writerSth(){
    try {
      writer.write("你好呀");
      writer.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

read线程:

public class readThread extends Thread {
    Read read;
    public readThread(Read read){
        this.read = read;
    }
    @Override
    public void run() {
        read.readSth();
    }
}

write线程:

public class writeThread extends Thread{
    Writer writer;
    public writeThread(Writer writer){
        this.writer = writer;
    }
    @Override
    public void run() {
        writer.writerSth();
    }
}

主类:

public class main {
    public static void main(String[] args) {
        try {
            PipedReader pipedReader = new PipedReader();
            PipedWriter pipedWriter = new PipedWriter();
            Read read = new Read(pipedReader);
            Writer writer = new Writer(pipedWriter);
            pipedReader.connect(pipedWriter);
            readThread readThread = new readThread(read);
            writeThread writeThread = new writeThread(writer);
            readThread.start();
            writeThread.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果:

你好呀   

3.10 实战:等待/通知之交叉备份

锁类:

public class myLock {
   Boolean flag;
}

线程A:

public class ThreadA extends Thread {
    Catch_test catch_test;
    public ThreadA(Catch_test catch_test){
        this.catch_test = catch_test;
    }
    @Override
    public void run() {
      catch_test.catchA();
    }
}

线程B:

public class ThreadB extends Thread {
    Catch_test catch_test;
    public ThreadB(Catch_test catch_test){
        this.catch_test = catch_test;
    }
    @Override
    public void run() {
        catch_test.catchB();
    }
}

主类:

public class main {
    public static void main(String[] args) {
        myLock myLock = new myLock();
        myLock.flag = true;
        Catch_test catch_test = new Catch_test(myLock);
        try {
            for (int i = 0; i < 10; i++) {
                new ThreadA(catch_test).start();
                Thread.sleep(1000);
                new ThreadB(catch_test).start();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类:

public class Catch_test {
    myLock myLock;
    public Catch_test(myLock myLock){
        this.myLock = myLock;
    }
    public void catchA(){
        synchronized (myLock){
            try {
                while (myLock.flag){
                    myLock.wait();
                }
                myLock.flag = true;
                myLock.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("将数据拷贝到数据库A");
        }

    }
    public void catchB(){
        synchronized (myLock){
            try {
                while (!myLock.flag){
                    myLock.wait();
                }
                myLock.flag = false;
                myLock.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("将数据拷贝到数据库B");
        }

    }
}

结果:

将数据拷贝到数据库B
将数据拷贝到数据库A
将数据拷贝到数据库B
将数据拷贝到数据库A
将数据拷贝到数据库B
将数据拷贝到数据库A
将数据拷贝到数据库B
将数据拷贝到数据库A
将数据拷贝到数据库B

四、join()方法

4.1 作用

如果子线程进行大量的数据运算,主线程会提早结束运行的,如果想让主线程在子线程运行结束后再结束,则可以使用join方法。

使所属的线程对象x正常执行run()方法中的方法,而使当前线程z进行无限期的阻塞,等待线程x执行结束后再执行线程z。

4.2 join与synchronized的比较

  • join在内部使用wait()方法进行等待,而syncronized关键字使用的是"对象监视器"原理作为同步。

4.3 方法join与异常

join方法遇到interrupt方法会出现异常

4.4 join(long)方法与sleep(long)方法的区别

设定等待的时间

join(long)内部使用wait(long)方法实现的,所以具有释放锁的特点,sleep(long)方法不释放锁。

五、ThreadLocal类

5.1 变量共享的方法

  • 将变量申请为static
  • 使用ThreadLocal类

ThreadLocal解决的是变量在不同线程间的隔离性。

5.2 简单使用

线程A:

public class A extends Thread {
    ThreadLocal a;
    public A(ThreadLocal a){
        this.a = a;
    }
    @Override
    public void run() {
        a.set("你好A");
        Thread.currentThread().setName("A");
        System.out.println(Thread.currentThread().getName()+a.get());
    }
}

线程B:

public class B extends Thread{
    ThreadLocal a;
    public B(ThreadLocal a){
        this.a = a;
    }
    @Override
    public void run() {
        a.set("你好B");
        Thread.currentThread().setName("B");
        System.out.println(Thread.currentThread().getName()+a.get());
    }
}

主类:

public class main {
    public static void main(String[] args) {
        ThreadLocal<Object> objectThreadLocal = new ThreadLocal<>();
        objectThreadLocal.set("大家好");
        Thread.currentThread().setName("main");
        System.out.println(Thread.currentThread().getName() + objectThreadLocal.get());
        A a = new A(objectThreadLocal);
        B b = new B(objectThreadLocal);
        a.start();
        b.start();
    }
}

结果:

main大家好
A你好A
B你好B

5.3 解决get返回null问题

自己的threadLocal类:

public class myThreadLocal extends ThreadLocal {
    @Override
    protected Object initialValue() {
        return "我是默认值";
    }
}

主类:

public class test1 {
    public static void main(String[] args) {
        myThreadLocal a = new myThreadLocal();
        System.out.println(a.get());
    }
}

结果:

我是默认值

5.4 类InheritableThreadLocal的使用

使用InheritableThreadLocal类可以让子线程从父线程中取值
测试:

public class c extends Thread{
    myIN a;
    public c(myIN a){
        this.a = a;
    }
    @Override
    public void run() {
        System.out.println(a.get());
    }

    public static void main(String[] args) {
        myIN a = new myIN();
        a.set("你好呀");
        new c(a).start();
    }
}

结果:

你好呀

5.5 InheritableThreadLocal中的childvalue()方法

重写InheritableThreadLocal接口的childvalue()方法,会对继承自父线程的值做修改。
继承 InheritableThreadLocal接口:

public class myIN extends InheritableThreadLocal {
    @Override
    protected Object childValue(Object parentValue) {
        return parentValue + "zzzzzzzzzzz";
    }
}

测试:

public class c extends Thread{
    myIN a;
    public c(myIN a){
        this.a = a;
    }
    @Override
    public void run() {
        System.out.println(a.get());
    }

    public static void main(String[] args) {
        myIN a = new myIN();
        a.set("你好呀");
        new c(a).start();
    }
}

结果:

你好呀zzzzzzzzzzz
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值