多线程基础-4-线程间通信

多线程

4 线程间通信

4.1 等待/通知机制

4.1.1 什么是线程等待/通知机制

在单线程程序中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在 if 语句块中。

在多线程编程中,可能 A 线程的条件没有满足只是暂时的, 稍后其他的线程 B 可能会更新条件,使得 A 线程的条件得到满足. 所以可以将 A 线程暂停,直到满足其条件后再将线程唤醒.其伪代码:

atomics{// 原子操作
    while(条件不成立){
        等待
    }
    满足被唤醒条件后,执行后续操作
}
4.1.2 等待/通知机制的实现

wait()方法是Object类中的,可以使执行当前代码的线程等待,暂停执行,直到被notify或被中断为止。

注意:

​ 1)wait()方法只能在同步代码块中,且由锁对象调用

​ 2)调用wait()方法,当前线程会立即释放锁

其伪代码如下:

// 在调用wait()方法之前,要先获得对象的内部锁
synchronized(锁对象){
    while(条件不成立){
    	// 通过锁对象调用wait()方法,使线程暂停,立即释放锁对象
        锁对象.wait();
    }
    // 线程的条件满足后,继续向下执行
}

notify()是Object 类中的,可以唤醒线程,该方法也必须在同步代码块中由锁对象调用 .

如果不使用锁对象调用 wait()/notify() 会抛出IllegalMonitorStateExeption 异常.

如果有多个等待的线程,notify()方法只能唤醒其中的一个.

在同步代码块中调用 notify()方法后,并不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,所以一般将notify()方法放在同步代码块的最后. 其伪代码如下:

synchronized(锁对象){
    // 执行修改保护条件的代码
    // 唤醒其他线程
    锁对象.notify();
}

实例代码:

package com.yupeng.wait;

import java.sql.SQLOutput;

/**
 * 通过notify唤醒等待的线程
 * @author Yupeng
 * @create 2020-12-09 11:35
 */
public class Test01 {
    public static void main(String[] args) {
        // 定义一个字符串为锁对象
        String lock = "abc";
        // 创建一个线程,进入等待状态
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        System.out.println("线程1开始等待:" + System.currentTimeMillis());
                        lock.wait(); // 线程等待,会立即释放锁对象,当前线程进入等待状态
                        System.out.println("线程1结束等待:" + System.currentTimeMillis());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 创建一个线程,通知其他线程结束等待
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println("线程2开始唤醒:" + System.currentTimeMillis());
                    lock.notify();
                    System.out.println("线程2结束唤醒:" + System.currentTimeMillis());
                }
            }
        });

        // 开启线程1
        t1.start();
        // main线程睡眠2秒,保证t1线程进入等待状态
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程1开启2秒后,再开启线程2来唤醒线程1
        t2.start();
    }
}

// 运行结果:
线程1开始等待:1607485402715
线程2开始唤醒:1607485404716
线程2结束唤醒:1607485404716
线程1结束等待:1607485404716
4.1.3 notify()方法不会立即释放锁对象

在同步代码块中调用 notify()方法后,并不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象。

package com.yupeng.wait;

/**
 * notify()方法不会立即释放锁对象
 * @author Yupeng
 * @create 2020-12-09 11:46
 */
public class Test02 {
    public static void main(String[] args) {
        // 创建一个字符串作为锁对象
        String lock = "abc";
        // 创建一个线程,进入等待状态
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        System.out.println("线程1开始等待:" + System.currentTimeMillis());
                        lock.wait();
                        System.out.println("线程1结束等待:" + System.currentTimeMillis());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 创建另一个线程,唤醒其他线程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println("线程2开始唤醒:" + System.currentTimeMillis());
                    for (int i = 0; i < 10; i++) {
                        System.out.println(Thread.currentThread().getName() + "-->" + i);
                        if (i == 5){
                            lock.notify();
                            System.out.println("线程2结束唤醒:" + System.currentTimeMillis());
                        }
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        // 开启线程1
        t1.start();
        // main线程睡眠2秒,保证t1线程进入等待状态
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 线程1开启2秒后,再开启线程2来唤醒线程1
        t2.start();
    }
}
4.1.4 interrupt()方法会中断 wait()

当线程处于wait()等待状态时, 调用线程对象的interrupt()方法会中断线程的等待状态, 产生 InterruptedException 异常。

package com.yupeng.wait;

/**
 * interrupt()方法会中断 wait()
 * @author Yupeng
 * @create 2020-12-09 11:56
 */
public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        //创建一个子线程对象
        SubThread t1 = new SubThread();
        // 开启线程
        t1.start();
        // main线程休眠2秒,保证t1线程进入等待状态
        Thread.sleep(2000);
        // 使用interrupt()方法中断wait()
        t1.interrupt();
    }
    // 定义一个常量作为锁对象
    public static final Object LOCK = new Object();
    static class SubThread extends Thread{
        @Override
        public void run() {
            synchronized (LOCK) {
                try {
                    System.out.println("begin wait...");
                    LOCK.wait();
                    System.out.println("end wait...");
                } catch (InterruptedException e) {
                    System.out.println("等待被中断...");
                    e.printStackTrace();
                }
            }
        }
    }
}

// 运行结果:
begin wait...
等待被中断...
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.yupeng.wait.Test03$SubThread.run(Test03.java:27)
4.1.5 notify()与 notifyAll()

notify()一次只能唤醒一个线程,如果有多个等待的线程,只能随机唤醒其中的某一个; 想要唤醒所有等待线程,需要调用notifyAll().

package com.yupeng.wait;

/**
 * notifyAll()唤醒所有线程
 * @author Yupeng
 * @create 2020-12-09 15:03
 */
public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        SubThread t1 = new SubThread(lock);
        SubThread t2 = new SubThread(lock);
        SubThread t3 = new SubThread(lock);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
        // main线程睡眠1000毫秒,保证3个线程都进入准备状态
        Thread.sleep(1000);
        synchronized (lock){
//            lock.notify(); // 只有一个线程被唤醒,其他线程仍处于等待状态,对于处于等待状态的线程来说,错过了通知信号,这种现象也称为信号丢失
            lock.notifyAll(); // 3个线程都被唤醒
        }
    }
    static class SubThread extends Thread{
        private Object lock;

        public SubThread(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            synchronized (lock){
                try {
                    System.out.println(Thread.currentThread().getName() + "-->begin wait...");
                    lock.wait();
                    System.out.println(Thread.currentThread().getName() + "-->end wait...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

// 运行结果:
t1-->begin wait...
t3-->begin wait...
t2-->begin wait...
t2-->end wait...
t3-->end wait...
t1-->end wait...
4.1.6 wait(long)的使用

wait(long)是带有 long 类型参数的 wait()等待,如果在参数指定的时间内没有被唤醒,超时后会自动唤醒。

package com.yupeng.wait;

/**
 * wait(long)的使用
 * @author Yupeng
 * @create 2020-12-09 15:14
 */
public class Test05 {
    public static void main(String[] args) {
        final Object obj = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        System.out.println("线程开始等待...");
                        obj.wait(3000); // 如果3000毫秒内没有被唤醒,会自动唤醒
                        System.out.println("线程结束等待");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
4.1.6 notify()通知过早

线程 wait()等待后,可以调用 notify()唤醒线程, 但如果 notify()唤醒的过早,在等待之前就调用了 notify()可能会打乱程序正常的运行逻辑.

package com.yupeng.wait;

/**
 * notify()通知过早
 * @author Yupeng
 * @create 2020-12-09 15:17
 */
public class Test06 {
    public static void main(String[] args) {
        final Object obj = new Object();
        // 创建等待线程
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        System.out.println("begin wait...");
                        obj.wait();
                        System.out.println("end wait!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 创建唤醒线程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("begin notify...");
                    obj.notify();
                    System.out.println("end notify!");
                }
            }
        });
        // 如果先开启t1,再开启t2,大多数情况下,t1先等待,t2再把t1唤醒
//        t1.start();
//        t2.start();
        // 如果先开启t2通知线程,再开启t1等待线程,可能会出现t1线程等待没有收到通知的情况
        t2.start();
        t1.start();
        
    }
}

可以通过增加判断标志,来解决以上问题:

package com.yupeng.wait;

/**
 * 解决notify()通知过早
 * @author Yupeng
 * @create 2020-12-09 15:17
 */
public class Test07 {
    static boolean isFirst = true; // 定义静态变量,作为是否是第一个运行的标志
    public static void main(String[] args) {
        final Object obj = new Object();
        // 创建等待线程
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    while (isFirst){ // 如果当线程是第一个开启的线程,就等待
                        try {
                            System.out.println("begin wait...");
                            obj.wait();
                            System.out.println("end wait!");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        // 创建唤醒线程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("begin notify...");
                    obj.notify();
                    System.out.println("end notify!");
                    isFirst = false; // 修改标志
                }
            }
        });
        // 实际上,调用start()仅仅是告诉线程调度器,当前线程准备就绪,而线程调度器在什么时候开启这个线程是不确定的,即调用start()方法的顺序,不一定是线程实际开启的顺序.
        // 在当前示例中,t1等待后让t2线程唤醒, 如果t2线程先唤醒了,就不让t1线程等待了
        t2.start();
        t1.start();

    }
}
4.1.7 wait 等待条件发生了变化

在使用 wait/nofity 模式时,如果wait 条件发生了变化,也可能会造成逻辑的混乱。

示例代码:1)定义一个集合;2)定义一个子线程,向集合中添加数据,添加完数据后,通知另外的线程从集合中取数据;3)定义一个子线程从集合中取数据,如果集合中没有数据就等待。

package com.yupeng.wait;

import java.util.ArrayList;
import java.util.List;

/**
 * 如果wait条件发生了变化,也可能会造成逻辑的混乱。
 * @author Yupeng
 * @create 2020-12-09 15:34
 */
public class Test08 {
    public static void main(String[] args) throws InterruptedException {
        // 定义添加数据的线程对象
        ThreadAdd threadAdd = new ThreadAdd();
        // 定义取出数据的线程对象
        ThreadSubtract threadSubtract = new ThreadSubtract();
        threadSubtract.setName("subtract-1");

        // 测试一:先开启添加数据的线程,再开启一个取数据的线程,大多数情况下会正常取出数据
//        threadAdd.start();
//        threadSubtract.start();
        // 测试二:先开启取数据的线程,再开启添加数据的线程,取数据的线程会先等待,等到添加数据之后,再取数据
//        threadSubtract.start();
//        threadAdd.start();
        // 测试三:开启两个取数据的线程,再开启添加数据的线程
        ThreadSubtract threadSubtract2 = new ThreadSubtract();
        threadSubtract2.setName("subtract-2");
        threadSubtract.start();
        threadSubtract2.start();
        Thread.sleep(100);
        threadAdd.start();
        /*
        程序可能出现以下异常:
        subtract-1 begin wait...
        subtract-2 begin wait...
        Thread-0向集合中添加了一个数据
        subtract-2 end wait!
        subtract-2从集合中取出了data后,集合中的数据数量为0
        subtract-1 end wait!
        Exception in thread "subtract-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0*/
        /*分析可能的执行顺序
        * 两个threadSubtract子线程先启动,取数据时,集合中没有数据,wait()等待
        * threadAdd线程获得CPU执行权,添加数据,然后将两个取数据子线程唤醒
        * subtract-2结束wait,正常取出数据
        * subtract-1结束wait,然后再执行list.remove(0)取数据时,现在list集合中已经没有数据了,这时会产生java.lang.IndexOutOfBoundsException异常
        * 所以出现异常的原因是: 向 list 集合中添加了一个数据,remove()了两次
         * */
        /*解决方法是当等待的线程被唤醒后,再判断一次集合中是否有数据可取.即需要把subtract()方法中的if判断改为while*/


    }
    // 1)定义List集合
    static List<String> list = new ArrayList<>();
    // 2)定义方法,从集合中取出数据
    public static void subtract() {
        synchronized (list){
            //if (list.size() == 0){
            while (list.size() == 0){
                try {
                    System.out.println(Thread.currentThread().getName() + " begin wait...");
                    list.wait();
                    System.out.println(Thread.currentThread().getName() + " end wait!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            String data = list.remove(0);
            System.out.println(Thread.currentThread().getName() + "从集合中取出了" + data + "后,集合中的数据数量为" + list.size());
        }
    }
    // 3)定义方法,向集合中添加数据
    public static void add() {
        synchronized (list) {
            list.add("data");
            System.out.println(Thread.currentThread().getName() + "向集合中添加了一个数据");
            list.notifyAll();
        }
    }
    // 4)定义线程类,调用subtract()方法
    static class ThreadSubtract extends Thread{
        @Override
        public void run() {
            subtract();
        }
    }
    // 5)定义线程类,调用add()方法
    static class ThreadAdd extends Thread{
        @Override
        public void run() {
           add();
        }
    }
}

4.2 生产者-消费值模式

在Java中,负责产生数据的模块是生产者,负责使用数据的模块是消费者. 生产者消费者解决的是数据的平衡问题,即先有数据,消费值才能使用,没有数据时,消费者需要等待。

4.2.1 生成-消费:操作值

示例代码:

定义操作数据的类

package com.yupeng.producerandconsumer.data;

/**
 * 操作数据的类
 * @author Yupeng
 * @create 2020-12-09 18:03
 */
public class ValueOP {
    private String value = "";

    /**
     * 定义方法,修改value的值
     */
    public void setValue() {
        synchronized (this) {
            // 如果value值不是空串,就等待
//            while (!value.equalsIgnoreCase("")) {
            if (!value.equalsIgnoreCase("")) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 如果value值是空串,就设置值
            String value = System.currentTimeMillis() + "--" + System.nanoTime();
            this.value = value;
            System.out.println("set设置的值是:" + this.value);
            this.notify();
        }
    }

    public void getValue() {
        synchronized (this) {
            // 如果是value的值是空串,就等待
//            while (value.equalsIgnoreCase("")) {
            if (value.equalsIgnoreCase("")) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 如果value的值不是空串,就打印值
            System.out.println("get获得的值是:" + this.value);
            this.value = "";
            this.notify();
        }
    }
}

定义线程类,模拟生产者:

package com.yupeng.producerandconsumer.data;

/**
 * 定义线程类,模拟生产者
 * @author Yupeng
 * @create 2020-12-09 18:12
 */
public class ProducerThread extends Thread{
    // 生产者生产数据,就是调用ValueOP类的setValue方法给value字段赋值
    private ValueOP obj;

    public ProducerThread(ValueOP obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        while (true) {
            obj.setValue();
        }
    }
}

定义线程类,模拟消费值:

package com.yupeng.producerandconsumer.data;

/**
 * 定义线程类,模拟消费值
 * @author Yupeng
 * @create 2020-12-09 18:12
 */
public class ConsumerThread extends Thread{
    // 消费者消费数据,就是调用ValueOP类的getValue方法,获取value字段的值
    private ValueOP obj;

    public ConsumerThread(ValueOP obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        while (true) {
            obj.getValue();
        }
    }
}

测试单生产,单消费的情况:

package com.yupeng.producerandconsumer.data;

/**
 * 测试单生产,单消费
 * @author Yupeng
 * @create 2020-12-09 18:15
 */
public class Test {
    public static void main(String[] args) {
        ValueOP valueOP = new ValueOP();

        ProducerThread p1 = new ProducerThread(valueOP);
        ConsumerThread c1 = new ConsumerThread(valueOP);

        p1.start();
        c1.start();
    }
}

测试多生产,多消费的情况:

应注意两点:

(1)可能会出现以下情况:

...
set设置的值是:1607509591131--2193907297551100
get获得的值是:1607509591131--2193907297551100
get获得的值是:
set设置的值是:1607509591131--2193907297589100
get获得的值是:1607509591131--2193907297589100
get获得的值是:
...

没有获取到值,是因为生产者线程set某个值后,可能消费者线程1将该值取出,然后消费者线程2获得CPU执行权,继续执行wait()的后续代码,此时没有值,则get不到值。解决方法是将if判断条件改为while条件,如果字符串为空串,则再回到wait状态,释放锁对象。

while (!value.equalsIgnoreCase("")) {
//if (!value.equalsIgnoreCase("")) {
	try {
		this.wait();
        ...

(2)可能会出现以下情况:

...
set设置的值是:1607515525623--2199841789599300
get获得的值是:1607515525623--2199841789599300
// 空白,程序卡死

出现假死情况,可能是以下执行顺序:1)生产者线程1set某个值,字符串不是空串,然后唤醒某一个线程,释放对象锁;2)如果生产者线程2得到CPU执行权,字符串不是空串,则进入等待状态,释放对象锁,生产者线程3同理;如果生产者线程1继续得到CPU执行权,因为字符串不是空串,同样进入等待状态;3)然后消费者线程2获得CPU执行权,将该值取出并将字符串设置为空串,并唤醒某一个线程,释放对象锁;4)如果消费者线程1获得CPU执行权,发现字符串为空串,则进入等待状态,释放对象锁;同理消费者线程2和3如果获取CPU执行权,也将进入等待状态。这样,所有的生产者线程和消费者线程都进入等待状态,程序假死。

解决方法是将notify()改为notifyAll(),唤醒所有的线程,总有一个可以继续执行的线程。

4.2.2 生产-消费:操作栈

生产者把数据存储到 List 集合中, 消费者从 List 集合中取数据,使用 List 集合模拟栈。

模拟栈:

package com.yupeng.producerandconsumer.stack;

import java.util.ArrayList;
import java.util.List;

/**
 * 模拟栈
 * @author Yupeng
 * @create 2020-12-09 20:18
 */
public class MyStack {
    // 定义集合,模拟栈
    private List list = new ArrayList();
    // 定义常量,模拟栈的最大容量
    public static final int MAX = 3;

    /**
     * 定义方法,模拟入栈
     */
    public synchronized void push() {
        // 栈中数据已满,就等待
        while (list.size() >= MAX) {
            System.out.println(Thread.currentThread().getName() + "begin wait...");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String data = "data-->" + Math.random();
        System.out.println(Thread.currentThread().getName() + "添加了数据:" + data);
        list.add(data);
        this.notifyAll();
    }

    public synchronized void pop() {
        // 栈中没有元素了,就等待
        while (list.size() == 0) {
            System.out.println(Thread.currentThread().getName() + "begin wait...");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "取出了数据:" + list.remove(list.size() - 1));
        this.notifyAll();
    }
}

生产者线程:

package com.yupeng.producerandconsumer.stack;

/**
 * 生产者线程
 * @author Yupeng
 * @create 2020-12-09 20:31
 */
public class ProducerThread extends Thread{
    private MyStack myStack;

    public ProducerThread(MyStack myStack) {
        this.myStack = myStack;
    }

    @Override
    public void run() {
        while (true) {
            myStack.push();
        }
    }
}

消费者线程:

package com.yupeng.producerandconsumer.stack;

/**
 * 消费者线程
 * @author Yupeng
 * @create 2020-12-09 20:31
 */
public class ConsumerThread extends Thread{
    private MyStack myStack;

    public ConsumerThread(MyStack myStack) {
        this.myStack = myStack;
    }

    @Override
    public void run() {
        while (true) {
            myStack.pop();
        }
    }
}

测试:同样应注意4.2.1节中可能出现的两个问题。

package com.yupeng.producerandconsumer.stack;

/**
 * @author Yupeng
 * @create 2020-12-09 20:33
 */
public class Test {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();

        ProducerThread p1 = new ProducerThread(myStack);
        ProducerThread p3 = new ProducerThread(myStack);
        ProducerThread p2 = new ProducerThread(myStack);
        ConsumerThread c1 = new ConsumerThread(myStack);
        ConsumerThread c2 = new ConsumerThread(myStack);
        ConsumerThread c3 = new ConsumerThread(myStack);

        p1.setName("生产者线程1");
        p2.setName("生产者线程2");
        p3.setName("生产者线程3");
        c1.setName("消费者线程1");
        c2.setName("消费者线程2");
        c3.setName("消费者线程3");

        p1.start();
        p2.start();
        p3.start();
        c1.start();
        c2.start();
        c3.start();
    }
}

4.3 通过管道实现线程间通信

java.io包中的PipeStream管道流,用于再线程之间传送数据。一个线程发送数据到输出管道,两一个线程从输入管道中读取数据。相关的类包括:字节流PipedInputStream和PipedOutputStream,字符流PipedReader和PipedWriter。

package com.yupeng.pipeStream;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

/**
 * 使用PipedInputStream和PipedOutputStream管道字节流再线程之间传递数据
 * @author Yupeng
 * @create 2020-12-09 20:44
 */
public class Test {
    public static void main(String[] args) throws IOException {
        // 定义管道字节流
        PipedInputStream inputStream = new PipedInputStream();
        PipedOutputStream outputStream = new PipedOutputStream();
        // 输入管道流和输出管道流之间建立连接
        inputStream.connect(outputStream);

        // 创建线程,向管道流中写入数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                writeData(outputStream);
            }
        }).start();
        // 创建线程,向管道流中读取数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                readData(inputStream);
            }
        }).start();
    }

    // 定义方法从管道流中写入数据
    public static void writeData(PipedOutputStream out) {
        // 将0-100之间的数字写入到管道流
        try {
            for (int i = 0; i < 100; i++) {
                String data = "" + i;
                out.write(data.getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    // 定义方法从管道流中读取数据
    public static void readData(PipedInputStream in) {
        byte[] bytes = new byte[1024];
        try {
            // 从管道输入字节流中,读取字节,并保存到字节数组中
            int len = in.read(bytes);
            while (len != -1) {
                // 把bytes数组中从0开始的len个字节转换为字符串并打印
                System.out.println(new String(bytes, 0, len));
                len = in.read(bytes); // 继续从管道中读取数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4.4 ThreadLocal 的使用

除了控制资源的访问外, 还可以通过增加资源来保证线程安全.ThreadLocal主要解决的是为每个线程绑定自己的值。

package com.yupeng.threadlocal;

/**
 * ThreadLocal的基本使用
 * @author Yupeng
 * @create 2020-12-09 21:00
 */
public class Test01 {
    public static void main(String[] args) {
        SubThread t1 = new SubThread();
        SubThread t2 = new SubThread();
        t1.start();
        t2.start();
    }
    // 定义ThreadLocal对象
    static ThreadLocal threadLocal = new ThreadLocal();
    // 定义线程类
    static class SubThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                // 设置线程关联的值
                threadLocal.set("-->" + i);
                // 读取设置的值
                System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
            }
        }
    }
}

在多线程环境中,把字符串转换为日期对象,多个线程使用同一个 SimpleDateFormat 对象可能会产生线程安全问题,有异常。

package com.yupeng.threadlocal;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 为每个线程指定自己的SimpleDateFormat,使用ThreadLocal
 * @author Yupeng
 * @create 2020-12-09 21:12
 */
public class Test02 {
    public static void main(String[] args) {
        // 创建100个线程
        for (int i = 0; i < 100; i++) {
            new Thread(new ParseDate(i)).start();
        }
    }
    // 定义ThreadLocal对象
    static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
    // 定义Runnable接口的实现类
    static class ParseDate implements Runnable{
        private int i;

        public ParseDate(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                String timeStr = "2020-12-22 09:20:" + i % 60; // 定义日期字符串
                threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                Date date = threadLocal.get().parse(timeStr);
                System.out.println(i + "-->" + date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值