线程间通信

线程间通信

同步机制

生产者线程 ProducerThread:依次对共享数据 Student 进行赋值。
消费者线程 ConsumerThread:打印输出共享数据 Student。
共享资源 Student

Studentpublic class Student {
    String name;
    int age;
}

ProducerThreadpublic class ProducerThread implements Runnable {

    private Student s;
    private int x = 0;

    public ProducerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                s.name = "lili";//刚走到这里,就被别人抢到了执行权
                s.age = 18;
            } else {
                s.name = "jack"; //刚走到这里,就被别人抢到了执行权
                s.age = 20;
            }
            x++;
        }
    }
}

ConsumerThreadpublic class ConsumerThread implements Runnable {
    private Student s;

    public ConsumerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(s.name + "---" + s.age);
        }
    }
}

StudentDemopublic class StudentDemo {
    public static void main(String[] args) {
        //创建共享资源
        Student s = new Student();

        //设置和获取的类
        ProducerThread st = new ProducerThread(s);
        ConsumerThread gt = new ConsumerThread(s);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}

输出:
...
jack---18
lili---18
lili---18
lili---18
jack---20
lili---18
lili---20

结果输出有jack---18lili---20,显然不符合预期。那什么原因导致的呢?

  1. 同一个数据打印多次:CPU的一点点时间片的执行权,就足够你执行很多次。
  2. 姓名和年龄不匹配:线程运行的随机性。

首先,我们分析下代码:

  1. 是否有多线程环境?      
  2. 是否有共享数据?       。共享对象 s。
  3. 是否有多条语句操作共享数据? 。s.name = “lili”;s.age = 18;

那么,解决方案是:加锁

ProducerThread:
public class ProducerThread implements Runnable {

    private Student s;
    private int x = 0;

    public ProducerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            // 增加同步代码块
            synchronized (s) {
                if (x % 2 == 0) {
                    s.name = "lili";
                    s.age = 18;
                } else {
                    s.name = "jack";
                    s.age = 20;
                }
                x++;
            }
        }
    }
}

ConsumerThread:
public class ConsumerThread implements Runnable {
    private Student s;

    public ConsumerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            // 增加同步代码块
            synchronized (s) {
                System.out.println(s.name + "---" + s.age);
            }
        }
    }
}

输出:
...
lili---18
lili---18
jack---20
jack---20
...

输出正常。

等待唤醒机制

需求:让lili---18jack---20交替打印,其实就是生产者 ProducerThread 执行一次后等待,然后消费者 ConsumerThread 执行一次后等待,然后生产者 ProducerThread 再执行一次,交替执行。

Java提供的等待唤醒机制解决。

  • public final void wait():当前线程堵塞等待。除非调用此对象的notify()方法或notifyAll()方法。
  • public final void wait(long timeout):当前线程堵塞等待。除非调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量前
  • public final void notify():唤醒在此对象监视器上等待的单个线程
  • public final void notifyAll():唤醒在此对象监视器上等待的所有线程

只有获取到锁才能调用wait()(在同步块内使用),未获得锁时调用wait()抛出异常IllegalMonitorStateException调用wait()会使当前线程进入阻塞等待状态,并且会释放执行 wait() 的锁资源

被notify,notifyAll唤醒后,阻塞等待才会结束,线程进入就绪状态,抢占cpu执行权后程序才会往下执行。

public class WaitDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        long startTime = System.currentTimeMillis();
        Runnable rs1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("【rs1】启动:" + (System.currentTimeMillis() - startTime));
                synchronized (lock) {
                    System.out.println("【rs1】拿到了锁:" + (System.currentTimeMillis() - startTime));

                    try {
                        // wait会堵塞等待,并释放锁。
                        System.out.println("【rs1】开始wait,释放了锁:" + (System.currentTimeMillis() - startTime));

                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 接收notify()唤醒后,才往下接着执行。
                    System.out.println("【rs1】 wait结束:" + (System.currentTimeMillis() - startTime));
                }
            }
        };

        Runnable rs2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("【rs2】启动:" + (System.currentTimeMillis() - startTime));
                synchronized (lock) {
                    System.out.println("【rs2】拿到了锁:" + (System.currentTimeMillis() - startTime));

                    lock.notify();
                }
                System.out.println("【rs2】同步代码块执行完成,【rs2】释放了锁:" + (System.currentTimeMillis() - startTime));

            }
        };

        Thread ts1 = new Thread(rs1);
        Thread ts2 = new Thread(rs2);
        ts1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ts2.start();
    }
}

在这里插入图片描述
分析:rs1 先获取到锁,调用wait()会使得当前线程进入堵塞等待状态,并释放执行wait()的锁资源。此时,rs2拿到了锁,调用notify()唤醒堵塞的线程,rs1被唤醒结束堵塞等待状态,程序继续往下执行。

等待唤醒改进

改进如下:

  1. 生产者线程 ProducerThread:判断是否有数据,如果有,等待;如果没有,进行赋值并且修改数据标识为true,并唤醒消费者线程。
  2. 消费者线程 ConsumerThread:判断是否有数据,如果没有,等待;如果有,打印输出并且修改数据标识为false,并唤醒生产者线程。
  3. 共享资源 Student:增加数据是否存在标识 flag。
Student:
public class Student {
    String name;
    int age;

    boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}

ProducerThread:
public class ProducerThread implements Runnable {

    private Student s;
    private int x = 0;

    public ProducerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (s.flag) {
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (x % 2 == 0) {
                    s.name = "lili";
                    s.age = 18;
                } else {
                    s.name = "jack";
                    s.age = 20;
                }
                x++;

                //修改标记
                s.flag = true;
                //唤醒线程
                s.notify();
            }
        }
    }
}

ConsumerThread:
public class ConsumerThread implements Runnable {
    private Student s;

    public ConsumerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name + "---" + s.age);

                //修改标记
                s.flag = false;
                //唤醒线程
                s.notify();
            }
        }
    }
}

StudentDemo:
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();

        //设置和获取的类
        ProducerThread st = new ProducerThread(s);
        ConsumerThread gt = new ConsumerThread(s);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}

输出:
...
lili---18
jack---20
lili---18
jack---20
...

线程通信的方法 wait()、notify() 为什么定义在 Object 类中?而不定义在Thread类中呢?

因为这些方法的调用必须通过锁对象调用,比如,s.wait()s.notify(),而使用的锁对象是任意锁对象。所以,这些方法必须定义在 Object 类中。

共享资源类线程安全化

把共享资源 Student 的成员变量私有化。
把设置和获取的操作封装成共享资源功能,并加上同步。使得共享资源类成为线程安全类
设置或者获取的线程只需要调用方法即可。

Student:
public class Student {
    private String name;
    private int age;
    private boolean flag; // 默认情况是没有数据,如果是true,说明有数据

    public synchronized void set(String name, int age) {
        // 如果有数据,就等待
        if (this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 设置数据
        this.name = name;
        this.age = age;

        // 修改标记
        this.flag = true;
        this.notify();
    }

    public synchronized void get() {
        // 如果没有数据,就等待
        if (!this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 获取数据
        System.out.println(name + "---" + age);

        // 修改标记
        this.flag = false;
        this.notify();
    }
}

ProducerThread:
public class ProducerThread implements Runnable {

    private Student s;
    private int x = 0;

    public ProducerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                s.set("lili", 18);
            } else {
                s.set("jack", 20);
            }
            x++;
        }
    }
}

ConsumerThread:
public class ConsumerThread implements Runnable {
    private Student s;

    public ConsumerThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            s.get();
        }
    }
}

StudentDemo:
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();

        //设置和获取的类
        ProducerThread st = new ProducerThread(s);
        ConsumerThread gt = new ConsumerThread(s);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}

输出:
...
lili---18
jack---20
lili---18
jack---20
...

共享资源类成为线程安全类,线程只需要直接调用同步方法即可,调用线程不需要编写同步代码逻辑


定时器的使用

定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。依赖TimerTimerTask这两个类。

  • Timer:定时执行
    • public Timer()
    • public void cancel()
    • public void schedule(TimerTask task, long delay):延时 delay 执行。
    • public void schedule(TimerTask task, long delay, long period):延时 delay 执行,且每间隔 period 执行一次。
    • public void schedule(TimerTask task, Date time):指定时间执行。
  • TimerTask:执行任务
    • public abstract class TimerTask extends Object implements Runnable:继承 Runnable 接口
public class TimerDemo {
    public static void main(String[] args) {
        // 创建定时器对象
        Timer t = new Timer();

        // 创建任务
        t.schedule(new MyTask(t), 1000, 2000);
    }
}

// 做一个任务
class MyTask extends TimerTask {

    private Timer t;

    public MyTask(){}

    public MyTask(Timer t){
        this.t = t;
    }

    @Override
    public void run() {
        System.out.println("beng,爆炸了");
    }

}

输出:
beng,爆炸了
beng,爆炸了
beng,爆炸了
...

等待1秒输出,然后每隔2秒输出一次。

需求:在指定的时间删除我们的指定目录

class DeleteFolder extends TimerTask {

    @Override
    public void run() {
        File srcFolder = new File("demo");
        deleteFolder(srcFolder);
    }

    // 递归删除目录
    public void deleteFolder(File srcFolder) {
        File[] fileArray = srcFolder.listFiles();
        if (fileArray != null) {
            for (File file : fileArray) {
                if (file.isDirectory()) {
                    deleteFolder(file);
                } else {
                    System.out.println(file.getName() + ":" + file.delete());
                }
            }
            System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
        }
    }
}

public class TimerTest {
    public static void main(String[] args) throws ParseException {
        Timer t = new Timer();

        String s = "2021-06-29 15:45:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(s);

        t.schedule(new DeleteFolder(), d);
    }
}

Timer 负责定时执行计划;TimerTask 的run()方法负责具体的执行任务。


sleep与wait在释放锁上的区别

sleep期间不会释放锁资源

public class SleepDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        long startTime = System.currentTimeMillis();
        Runnable rs1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("rs1启动:" + (System.currentTimeMillis() - startTime));
                synchronized (lock) {
                    System.out.println("rs1拿到了锁:" + (System.currentTimeMillis() - startTime));
                    try {
                        System.out.println("rs1睡眠3s...");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("rs1 睡眠结束:" + (System.currentTimeMillis() - startTime));
                }
                System.out.println("rs1释放了锁:" + (System.currentTimeMillis() - startTime));
            }
        };

        Runnable rs2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("rs2启动:" + (System.currentTimeMillis() - startTime));
                synchronized (lock) {
                    System.out.println("rs2拿到了锁:" + (System.currentTimeMillis() - startTime));
                    try {
                        System.out.println("rs2睡眠1s...");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("rs2睡眠结束:" + (System.currentTimeMillis() - startTime));
                }
                System.out.println("rs2释放了锁:" + (System.currentTimeMillis() - startTime));
            }
        };

        Thread ts1 = new Thread(rs1);
        Thread ts2 = new Thread(rs2);
        ts1.start();
        ts2.start();
    }
}

输出:
【rs1】启动:1
【rs2】启动:1
【rs1】拿到了锁:2
【rs1】睡眠3s...
【rs1】睡眠结束:3015
【rs1】释放了锁:3015
【rs2】拿到了锁:3015
【rs2】睡眠1s...
【rs2】睡眠结束:4030
【rs2】释放了锁:4030

分析:rs1 和 rs2 在 1ms 同时启动,然后 rs1 在 2ms 拿到锁并开始睡眠,在 3015ms 释放了锁,rs2直到 3015ms 才拿到了锁,说明 rs1 在睡眠期间并没有释放锁

sleep() 是 Thread 类中的静态方法,调用 sleep() 会使得当前线程睡眠一段时间。睡眠状态开始时放弃对 CPU 的掌控,并在睡眠持续期间不再抢夺 CPU 计算资源,但是睡眠状态并不会释放持有的锁资源

调用 sleep() 需要捕获 InterruptedException,避免数据期间设置中断标识。

注:wait() 是 object 类的方法,sleep() 是 Thread 类的方法。


线程的生命周期和线程的状态

Thread.state枚举类有6个状态:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
  • 新建(new):新建一个线程对象,JVM为其分配内存
  • 就绪(runnable):调用线程的start()方法后,线程处于等待 CPU 分配资源阶段,谁先抢到 CPU 资源,谁开始执行。JVM会为其创建方法调用栈和程序计数器
  • 运行(running):当就绪的线程被调度并获得 CPU 资源时,便进入运行状态。
  • 阻塞(blocked):在运行期间,因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify()或者notifyAll()方法。唤醒的线程不会立刻执行run()方法,它们要再次等待 CPU 分配资源再进入运行状态。
  • 死亡(terminated):线程执行完成或者因异常退出了 run 方法,该线程结束生命周期。

在这里插入图片描述

1. RUNNABLE
因为CPU时间片抢占很快,所以,就绪运行状态,都表现为RUNNABLE

2. 阻塞的三种情况

  1. 同步阻塞
    运行的线程在获取对象的同步锁时(比如进入synchronized同步代码块或同步方法时),若该同步锁被别的线程占用,线程就进入了BLOCKED状态,并且 JVM 会把该线程放入“锁池”中。
  2. 等待阻塞
    运行的线程执行wait()方法,该线程会释放占用的所有资源,线程就进入了WAITING状态。必须依靠其他线程调用notify()notifyAll()方法才能被唤醒。
  3. 其他阻塞
    运行的线程执行sleep()join()等待I/O流的输入输出等待网络资源时,JVM 会把该线程置为阻塞状态。当 sleep 状态超时、join 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

当线程调用sleep(time),或者wait(time)时,进入TIMED_WAITING状态,当休眠时间结束后,或者调用notify()notifyAll()方法时会重新进入RUNNABLE状态。

BLOCKED、WAITING、TIMED_WAITING 我们都称为阻塞状态。

public class ThreadStateDemo {
    public static void main(String[] args) throws InterruptedException {
        /* 定义锁 */
        Object obj = new Object();

        /* sleep() */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        /* 抢占锁后wait() */
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        Thread.sleep(1000);
                        obj.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t2.start();

        /* 抢占已经占用的锁 */
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {

                }
            }
        });
        t3.start();

        System.out.println(t1.getState());//TIMED_WAITING
        System.out.println(t2.getState());//TIMED_WAITING
        System.out.println(t3.getState());//BLOCKED
        Thread.sleep(3000);
        System.out.println("++++++");
        System.out.println(t1.getState());//TERMINATED
        System.out.println(t2.getState());//WAITING
        System.out.println(t3.getState());//TERMINATED
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值