线程通信——多线程(三)

线程通信

一、线程通信的概念

  在操作系统中每个线程都是一个独立的个体,线程通信就是将这些线程个体常量起来的,只有具备了线程通信机制,线程才更有意义。线程通信不仅提高了CPU的利用率,还为程序开发提供了线程过程的监督与把控。

二、等待/通知机制概述

  wait()方法是Object类的方法,它能使当前执行的线程进行等待,将线程置入“与执行队列”中,代码在wait()方法所在的行停止。

  notify()方法需要在同步方法或者同步代码块中进行调用,线程必须获得对象级别的锁,notify()方法用于通知处于等待状态的线程继续执行。notify()方法执行后,不会马上释放锁,需要等待notify()方法所在的线程执行完synchronized代码块后才会释放锁。

  notifyAll()方法使处于等待的线程进入可运行状态,但是哪个线程获得执行需要看CPU分配资源的结果。

1、wait()/notify()方法

  线程类

public class WaitAndNontifyTreadA extends Thread{
    private Object lock;
    public WaitAndNontifyTreadA(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        super.run();
        synchronized(lock){
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行解锁方法," + "时间:" + dateformat.format(System.currentTimeMillis()));
            lock.notify();
            System.out.println("线程:" + Thread.currentThread().getName() + ",开始sleep," + "时间:" + dateformat.format(System.currentTimeMillis()));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + ",解锁结束," + "时间:" + dateformat.format(System.currentTimeMillis()));
        }
    }
}

public class WaitAndNotifyThreadB extends Thread {
    private Object lock;
    public WaitAndNotifyThreadB(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        super.run();
        synchronized(lock){
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
                lock.wait();
                System.out.println("线程:" + Thread.currentThread().getName() + ",继续执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  测试类

@SpringBootTest
public class WaitAndNotifyTest {
    @Test
    public static void main(String[] args) {
        Object lock = new Object();
        WaitAndNontifyTreadA threadA = new WaitAndNontifyTreadA(lock);
        threadA.setName("threadA");
        WaitAndNotifyThreadB threadB = new WaitAndNotifyThreadB(lock);
        threadB.setName("threadB");
        threadB.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadA.start();
    }
}

  控制台输出结果

线程:threadB,开始执行,时间:2020-08-31 19:24:15。
线程:threadA,开始执行解锁方法,时间:2020-08-31 19:24:18
线程:threadA,开始sleep,时间:2020-08-31 19:24:18
线程:threadA,解锁结束,时间:2020-08-31 19:24:20
线程:threadB,继续执行,时间:2020-08-31 19:24:20。

  从控制台输出结果可知,ThreadA解锁后,即退出synchronized代码块后ThreadB才继续执行wait()后面的代码。

2、验证wait()释放锁

  service类

public class WaitReleaseLockService {
    public void testMethod(Object lock){
        synchronized(lock){
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + ",结束执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
        }
    }
}

  线程类

public class WaitReleaseLockThread extends Thread{
    private Object lock;
    public WaitReleaseLockThread(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        super.run();
        WaitReleaseLockService service = new WaitReleaseLockService();
        service.testMethod(lock);
    }
}

  测试类

@SpringBootTest
public class WaitReleaseThreadTest {
    @Test
    public static void main(String[] args) {
        Object lock = new Object();
        WaitReleaseLockThread threadA = new WaitReleaseLockThread(lock);
        threadA.setName("threadA");
        threadA.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        WaitReleaseLockThread threadB = new WaitReleaseLockThread(lock);
        threadB.setName("threadB");
        threadB.start();
    }
}

  控制台输出结果

线程:threadA,开始执行,时间:2020-08-31 20:17:33。
线程:threadB,开始执行,时间:2020-08-31 20:17:34。

  结论:从控制台的输出结果可以看出,当threadA线程wait后,thtreadB线程还能获得锁,并执行同步方法。因此,可知threadA线程执行了wait()方法后释放了锁。

  拓展:当把上面的例子中的线程类的wait方法该为sleep方法,则两个线程同步执行。因此,threadB线程被阻塞。

3、notify()与notifyAll()

  notify()方法一次只能随机唤醒一个线程,notifyAll()方法能唤醒所有线程。

  线程类

public class NotifyService {
    public void method(Object lock){
        synchronized(lock){
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + ",结束执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
        }
    }
    public void notifyMethod(Object lock){
        synchronized(lock){
            lock.notify();
            // lock.notifyAll();
        }
    }
}

  测试类

@SpringBootTest
public class NotifyTest {
    @Test
    public static void main(String[] args) {
        Object lock = new Object();
        NotifyService service = new NotifyService();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                service.method(lock);
            }
        },"threadA");
        threadA.start();
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                service.method(lock);
            }
        },"threadB");
        threadB.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread notifyThread = new Thread(new Runnable() {
            @Override
            public void run() {
                service.notifyMethod(lock);
            }
        },"notifyThread");
        notifyThread.start();
    }
}

  控制台输出结果

// notif()输出结果
线程:threadA,开始执行,时间:2020-08-31 21:04:29。
线程:threadB,开始执行,时间:2020-08-31 21:04:29。

// notifAll()输出结果
线程:threadA,开始执行,时间:2020-08-31 20:58:49。
线程:threadB,开始执行,时间:2020-08-31 20:58:49。
线程:threadB,结束执行,时间:2020-08-31 20:58:51。
线程:threadA,结束执行,时间:2020-08-31 20:58:51。

4、wait(long)方法

5、生产者消费者模型

(1)、单生产者单消费者

  产品栈类

public class ProduceStack {
    private List stackList = new ArrayList();
    synchronized public void push(){
        while (stackList.size() == 1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        stackList.add("push = " + Math.random());
        this.notify();
        System.out.println("执行进栈后   stackList size = " + stackList.size());
    }
    synchronized public void pop(){
        while (stackList.size() == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        stackList.remove(0);
        this.notify();
        System.out.println("执行出栈后stackList size = " + stackList.size());
    }
}

  生产者&消费者线程类

// 生产者线程类
public class ProducerThread extends Thread{
    private ProduceStack producerList;

    public ProducerThread(ProduceStack producerList) {
        this.producerList = producerList;
    }
    @Override
    public void run() {
        super.run();
        while(true){
            producerList.push();
        }
    }
}
// 消费者线程类
public class ConsumerThread extends Thread {
    private ProduceStack producerList;

    public ConsumerThread(ProduceStack producerList) {
        this.producerList = producerList;
    }
    @Override
    public void run() {
        super.run();
        while(true){
            producerList.pop();
        }
    }
}

  测试累

@SpringBootTest
public class ProducerAndConsumerTest {
    @Test
    public static void main(String[] args) {
        ProduceStack producerList = new ProduceStack();
        ProducerThread producerThread = new ProducerThread(producerList);
        ConsumerThread consumerThread = new ConsumerThread(producerList);
        producerThread.setName("producerThread");
        consumerThread.setName("consumerThread");
        producerThread.start();
        consumerThread.start();
    }
}

  控制台输出结果

执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
...

(2)、线程假死

  在多生产者,多消费者模式下,容易出现线程假死的情况。线程假死是因为生产者和消费者的线程都处于WAITING状态中,程序不再往下执行了。修改上面例子中的测试类验证线程假死。

  测试类修改

@SpringBootTest
public class ProducerAndConsumerTest {
    @Test
    public static void main(String[] args) {
        ProduceStack producerList = new ProduceStack();
        ProducerThread[] producerThreadList = new ProducerThread[2];
        ConsumerThread[] consumerThreadList = new ConsumerThread[2];
        for (int i = 0; i < 2 ; i++){
            producerThreadList[i] = new ProducerThread(producerList);
            producerThreadList[i].setName("生产者" + (i + 1));
            consumerThreadList[i] = new ConsumerThread(producerList);
            consumerThreadList[i].setName("消费者" + (i + 1));
            producerThreadList[i].start();
            consumerThreadList[i].start();
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread[] threadList = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
        Thread.currentThread().getThreadGroup().enumerate(threadList);
        for (int i = 0; i < threadList.length; i++){
            System.out.println(threadList[i].getName() + " " + threadList[i].getState());
        }
    }
}

  控制台输出结果

...
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
执行出栈后stackList size = 0
执行进栈后   stackList size = 1
main RUNNABLE
Monitor Ctrl-Break RUNNABLE
生产者1 WAITING
消费者1 WAITING
生产者2 WAITING
消费者2 WAITING

  造成线程假死的原因是生产者唤醒了同类线程,消费者同样唤醒了同类线程,造成生产者和消费者两边同时处于等待状态。假死是个概率事件,为了消除线程假死的影响,可以使用notifyAll()方法,当一个线程释放锁后,将其他线程全部唤醒。

6、线程通信——管道

  管道(pipeStream)是一种特殊的流,用于线程间的通信。一个线程发送数据到管道,另外一个线程从管道中读取数据。

  (1)、字节流管道

  读写类

public class WriteData {
    public void writeMethode(PipedOutputStream out){
        try {
            System.out.println("write: ");
            for (int i = 0; i < 300; i++) {
                String outData = "" + (i + 1);
                out.write(outData.getBytes());
                System.out.print(outData);
            }
            System.out.println();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ReadData {
    public void readMethod(PipedInputStream input){
        try {
            System.out.println("read: ");
            byte[] byteArray = new byte[200];
            int readLenght = input.read(byteArray);
            while(readLenght != -1){
                String newData = new String(byteArray, 0, readLenght);
                System.out.println(newData);
                readLenght = input.read(byteArray);
            }
            System.out.println();
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  线程类

public class ThreadWrite extends Thread{
    private WriteData write;
    private PipedOutputStream out;
    public ThreadWrite(WriteData write, PipedOutputStream out) {
        this.write = write;
        this.out = out;
    }
    @Override
    public void run() {
        write.writeMethode(out);
    }
}
public class ThreadRead extends Thread{
    private ReadData read;
    private PipedInputStream input;
    public ThreadRead(ReadData read, PipedInputStream input) {
        this.read = read;
        this.input = input;
    }
    @Override
    public void run() {
        read.readMethod(input);
    }
}

  测试类

@SpringBootTest
public class PipedTest {
    @Test
    public static void main(String[] args) {
        try {
            WriteData writeData = new WriteData();
            ReadData readData = new ReadData();
            PipedInputStream inputStream = new PipedInputStream();
            PipedOutputStream outputStream = new PipedOutputStream();
            outputStream.connect(inputStream);
            ThreadRead threadRead = new ThreadRead(readData,inputStream);
            threadRead.start();
            Thread.sleep(2000);
            ThreadWrite threadWrite = new ThreadWrite(writeData,outputStream);
            threadWrite.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

read: 
write: 
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545512345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
53545556
5657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
57585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
13813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320
42052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702
71272273274275276277278279280281282283284285286287288289290291292293294295296297298299300

  从控制台输出结果可以看出,读取和写入两个线程时异步运行。

三、join()方法

  join()方法能让线程排队运行,具有类似同步的效果。join内部使用的是wait()方法,当线程isActive状态时在无限循环中,只有线程销毁后才退出循环继续后续代码的执行。

1、join()和synchronized的区别

  synchronized内部使用的时对象监听器。因此,join和synchronized两种的原理不一样。

  任务类

public class TaskA {
    synchronized public void methodA(){
        System.out.println("-------------运行methodA方法---------------");
    }
    public void methodB(){
        synchronized(this){
            for (int i = 0; i < 5; i++){
                System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i + ";");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  测试类

@SpringBootTest
public class JoinSynTest {
    @Test
    public static void main(String[] args) {
        try {
            TaskA task = new TaskA();
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    task.methodB();
                }
            },"threadA");

            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    task.methodB();
                }
            },"threadB");
            threadA.start();
            System.out.println("--------线程:" + Thread.currentThread().getName() + ",开始时间" + System.currentTimeMillis() + "---------");
            threadA.join(3000);
            System.out.println("--------线程:" + Thread.currentThread().getName() + ",结束时间" + System.currentTimeMillis() + "---------");
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

--------线程:main,开始时间1599013870456---------
线程:threadA;i = 0;
线程:threadA;i = 1;
线程:threadA;i = 2;
--------线程:main,结束时间1599013873457---------
线程:threadA;i = 3;
线程:threadA;i = 4;
线程:threadB;i = 0;
线程:threadB;i = 1;
线程:threadB;i = 2;
线程:threadB;i = 3;
线程:threadB;i = 4;

  从控制台输出结果可以看出,因为methdB()方法的对象锁是task,join()方法的锁是对象threadA,因此两个线程异步运行。threadA和threadB的对象锁都是task,因此两个线程同步执行。

  另外,上面例子还使用了join(long)方法,当线程isAlive将一直等待,即使设置时间到了还是处于等待状态。

  join()方法

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

2、join()和sleep()的区别

  join内部是实现了wait方法,因此是释放锁的,sleep是不释放锁的。

  测试类

@SpringBootTest
public class JoinTest {
    @Test
    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程:" + Thread.currentThread().getName() + "执行方法A。" + System.currentTimeMillis());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"threadA");
        threadA.start();
        try {
            threadA.join();
            // Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程:" + Thread.currentThread().getName() + "执行方法B。" + System.currentTimeMillis());
    }
}

  控制台输出结果

线程:threadA执行方法A。1598966676610
主线程:main执行方法B。1598966678611

四、ThreadLocl类

  ThreadLocl类使每个线程都绑定自己的值,主要做的是变量在不同线程间的隔离性。

1、ThreadLocl的隔离性

  ThreadLocl具有隔离性,每个线程都能取到自己的私有值。从这个例子的控制台打印结果可以看出,两个线程set和get到了自己私有值。

  常量类

public class ConstantUtil {
    public static ThreadLocal<String> t1 = new ThreadLocal();
}

  测试类

@SpringBootTest
public class ThreadLoclTest {
    @Test
    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 5; i++){
                        ConstantUtil.t1.set("线程:" + Thread.currentThread().getName() + ",时间:" + System.currentTimeMillis());
                        System.out.println(ConstantUtil.t1.get());
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"ThreadA");
        Thread threadB = new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 5; i++){
                        ConstantUtil.t1.set("线程:" + Thread.currentThread().getName() + ",时间:" + System.currentTimeMillis());
                        System.out.println(ConstantUtil.t1.get());
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"ThreadB");
        threadA.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadB.start();
    }
}

  控制台输出结果

线程:ThreadA,时间:1599018738241
线程:ThreadB,时间:1599018738340
线程:ThreadA,时间:1599018738442
线程:ThreadB,时间:1599018738541
线程:ThreadA,时间:1599018738642
线程:ThreadB,时间:1599018738741
线程:ThreadB,时间:1599018738942
线程:ThreadB,时间:1599018739142

2、重写initialValue()方法,解决第一次get值为null问题

  继承ThreadLocal类

public class InitialThreadLocal extends ThreadLocal{
    @Override
    protected Object initialValue() {
        return "线程:" + Thread.currentThread().getName() + ",时间:" + System.currentTimeMillis();
    }
}

  测试类

@SpringBootTest
public class InitialValueTest {
    @Test
    public static void main(String[] args) {
        InitialThreadLocal threadLocal = new InitialThreadLocal();
        System.out.println(threadLocal.get());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
            }
        },"threadA");
        threadA.start();
    }
}

  控制台输出结果

线程:main,时间:1599044771216
线程:threadA,时间:1599044772217

五、InheritableThreadLocal类

  InheritableThreadLocal类继承了ThreadLocal类,提供了子线程从父线程取值的方法,子线程可以通过InheritableThreadLocal的get方法取到父线程的值。同时通过重写childValue()方法,子线程也可以再父线程的基础上对数据进行加工。

  继承类

public class InheritableThreadLocal1 extends InheritableThreadLocal {
    @Override
    protected Object initialValue() {
        return System.currentTimeMillis();
    }
    @Override
    protected Object childValue(Object parentValue) {
        return super.childValue(parentValue) + " i am the child value";
    }
}

  测试类

@SpringBootTest
public class InitialValueTest {
    @Test
    public static void main(String[] args) {
        InheritableThreadLocal1 it1 = new InheritableThreadLocal1();
        System.out.println( "线程:" + Thread.currentThread().getName() + ",时间:" + it1.get());
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println( "线程:" + Thread.currentThread().getName() + ",时间:" + it1.get());
            }
        },"threadA");
        threadA.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println( "线程:" + Thread.currentThread().getName() + ",时间:" + it1.get());
    }
}

  控制台输出结果

线程:main,时间:1599045818241
线程:threadA,时间:1599045818241 i am the child value
线程:main,时间:1599045818241
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值