这一篇说一下比较枯燥的东西, 为什么说枯燥呢, 因为我写这都感觉很无聊, 无非就是几个阻塞线程的方法和唤醒线程的方法...
1. 线程中断
首先我们说一说怎么使得一个正在运行中的线程进入阻塞状态, 这也叫做线程中断, 最常见的就是 Thread.sleep(1000)这种方式的, 我们直接看一个简单粗暴的图:
此图应该列举了所有中断, 我们选择几个比较重要的说说, 其中有几个方法已经被废弃了, 原因要么就是不安全, 要么就是容易产生死锁等等, 图中已经划去了!
2. 等待通知和收到通知(wait,notify,notifyAll)
由于这 wait/notify 这种不怎么好理解, 我们就详细说说, 其他的随便看看就好...
大家不知道有木有发现, java 中几乎所有的数据类型都重写了 Object 的 wait(),notify(),notifyAll()这三个方法, 那这三个方法到底是干什么的呢?
前提: 要用这三个方法必须要放在 synchronized 里面, 这是规则!!! 至于调用这三个方法必须是锁定 (也就是我前面说的锁芯) 才能调用, 还有, 锁定几乎可以是任何类型的!
. 举例下面两种用法:
介绍一个东西, 名字叫做 "wait set", 这是个什么东西呢? 你就把这个看作是一个存阻塞线程的集合 (大白话来说就是线程的休息室, 把线程用 wait 阻塞了就相当于让线程去休息室休息, 并且线程很有自觉, 去休息了之后就会释放锁), 而且每个锁定(也就是我前面说的锁芯) 都有一个. 比如一个线程调用 obj.wait()之后, 那么这个线程就被阻塞了, 这个阻塞的线程就被放在了 obj 的 "wait set" 中了, 我们可以用一个图来表示:
当线程 A 阻塞之后就被丢进了 obj 的 wait set 中之后, 线程 A 就会释放当前的锁, 此时线程 B 就可以访问这个方法或相同锁定的方法; 但是假如在 B 中调用了 notify()方法, 那么就是从 obje 的 wait set 中唤醒 A 线程, 然后直到 B 线程结束后释放锁, A 线程才变成准备就绪状态, 可以随时被 CPU 调度再次获得这个锁;
注意: 必须等 B 线程执行完之后释放锁, 线程 A 才能变成准备就绪状态(代码是从 wait 方法后面的代码开始执行, 不是重新开始)
根据上面两个图随意举个小例子:
- package com.wyq.thread;
- public class Bank {
- Object obj = new Object();// 我们随便创建一个锁定
- public void tomoney(Integer money){
- // 在转账方法的锁中调用 wait 方法, 此时执行这个方法的线程会中断, 保存在 obj 的 wait set 中, 并且该线程会释放锁其他线程可以访问相同锁定的锁
- synchronized(obj){
- try {
- obj.wait();
- System.out.println("转账:"+money+"元");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public void save(Integer money){
- // 在存钱方法的锁中我们调用 notify 从 obj 的 wait set 中唤醒存在其中的某一个线程, 那个被唤醒的线程不会马上变成准备就绪状态,
- // 必须要等本存钱方法的线程执行完毕释放锁, 才会进入准备就绪状态
- synchronized(obj){
- obj.notify();
- System.out.println("存钱:"+money+"元");
- }
- }
- public static void main(String[] args) {
- Bank bank = new Bank();
- // 我们可以多次运行这两个线程, 总是先执行存钱方法, 然后才是转账方法(其实转账线程可以利用 for 循环创建几十个, 这样效果更明显)
- new Thread(new Runnable() {
- @Override
- public void run() {
- bank.tomoney(100);
- }
- }).start();
- new Thread(new Runnable() {
- @Override
- public void run() {
- bank.save(100);
- }
- }).start();
- }
- }
运行结果如下:
notify()是唤醒 wait set 集合中随意一个线程; 而那个 notifyAll()方法可以唤醒 wait set 集合中所有的线程, 用法和 notify 一样, 就不举例子了; 那么我们平常应该用哪一个呢? 用 notify 的刷唤醒的线程比较少效率高一点, 但是缺点就是到底唤醒哪一个线程的实现可能有点难度, 一个处理不好程序就可能挂掉了; 但是用 notifyAll 的话效率比较低一点, 但是却比较可靠稳定;
所以啊, 如果我们对于程序代码都理解得十分透彻, 那就用 notify 比较好, 否则还是用稳妥一点的 notifyAll 吧!
顺便说一点, 有的时候我们把一个线程阻塞之后放进 wait set 中之后, 却忘记调用 notify/notifyAll 了, 那么这些阻塞线程就会一直留在了 wait set 中, 我们可以在 wait()方法指定一个时间, 在规定时间内如果没有被 notify 唤醒, 那么放在 wait set 中的该线程就会自动唤醒! 还有 obj.wait()方法其实本质是调用 obj.wait(0),wait(long timeout)是一个 Native 方法! 比如 obj.wait(3000)表示三秒之后会自动唤醒! 这里就是随意提一下, 一般很少去指定这个超时时间的
补充 wait set 的定义: wait set 是一个虚拟的概念, 它既不是实例的字段, 也不是可以获取在实例上 wait 中线程的列表的方法.
3.sleep 和 interrupt 方法
有没有觉得上面的这种用法比较麻烦, 虽然在某些情况下比较适用, 但是我们平常测试用的话这也太麻烦了, 还有个什么锁定这种鬼东西, 有没有比较简单的用法啊!
于是我们有了 sleep 方法对应于 wait 方法, interrupt 方法对应于 notify 方法;(注意, 这里只是功能上面的对应, 但是其中的原理是不相同的!)
首先说说 sleep 方法, 这个应该比较熟悉, 直接就是 Thread.sleep(xxx)这种方式来使得当前线程阻塞一定时间 (注意 sleep 方法和 wait 方法最大的区别就是 sleep 方法不会释放锁), 如果没有到达相应时间我们非要让阻塞状态的线程又重新变成准备就绪状态, 就使用 a.interrupt() 方法, 其中 a 指的是当前线程的实例;
我们看一个最简单的例子:
- package com.wyq.thread;
- public class Bank {
- public void tomoney(Integer money){
- try {
- // 将运行这个方法的线程停止一个星期
- Thread.sleep(604800000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("转账:"+money+"元");
- }
- public static void main(String[] args) {
- Bank bank = new Bank();
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- bank.tomoney(100);
- }
- });
- thread.start();
- // 如果没有调用 interrupt 方法, 那么 thread 这个线程就会暂停一个星期
- thread.interrupt();
- }
- }
会抛出一个异常这也是用 interrupt 方法的特色;
随意一提: 这个 interrupt 方法比较厉害, 即使线程中断是由于 wait(),join(),sleep()造成的, 但是都可以用 interrupt 方法进行唤醒, 比较厉害!
4.join()方法
由于线程的执行是随机的, 那么我们有没有设什么方法可以让线程以一定的顺序执行呢? 虽然可能会有点影响性能, 但这不是我们暂时关心的.
join()方法可以让一个线程 B 在线程 A 之后才执行, 我们继续看一个简单的例子;
- package com.wyq.thread;
- public class Bank {
- public void tomoney(String name,Integer money){
- System.out.println(name+"转账:"+money+"元");
- }
- public static void main(String[] args) {
- Bank bank = new Bank();
- Thread A = new Thread(new Runnable() {
- @Override
- public void run() {
- bank.tomoney("A",100);
- }
- });
- Thread B = new Thread(new Runnable() {
- @Override
- public void run() {
- bank.tomoney("B",200);
- }
- });
- A.start();
- try {
- // 必要要等到 A 线程执行完毕才会执行其他的线程
- A.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 这个时候查看一下 B 的线程是 NEW, 说明 B 线程还没调用 start()方法,
- // 调用 start 方法之后就成 Runnable 状态了
- System.out.println(B.getState());
- B.start();
- }
- }
5. 线程优先级和 yield()方法
其实线程优先级这这种东西比较坑, 这是一个概率问题, 优先级分为 10 级, 1 级最低, 10 级最高, 我们一般创建的线程默认 5 级; 但是优先级高的线程不一定先执行, 这个优先级的意思就是线程获取 CPU 调用的概率比较大, 所以这是一个概率问题, 基本用法是 Thread thread = new Thread(xxx); thread.setPriority(1); thread.start();
那么 yield 方法是干什么的呢? yield 的意思是放手, 让步, 投降, 在多线程中是对一个正在运行的 A 线程用这个方法表示该线程放弃 CPU 的调度, 重新回到准备就绪状态, 然后让 CPU 去执行和 A 线程相同优先级的线程, 而且有可能又会执行 A 线程; 而且还有可能调用 yield 方法无效, emmmm...... 日了狗了!
我表示最这个方法没有什么好感, 感觉很难控制, 这个方法是 Thread 的静态方法, 直接用 Thread.yield()直接用即可, 我感觉我一辈子都不会用到这个.... 这个方法就不测试了, 有兴趣的小伙伴自己查查别的资料吧!
6. 总结
不知道大家有没有觉得多线程这个东西不能随便用, 用好了虽然说可以提高效率, 但是用的不好很容易出现不可控制的问题, 这让我有一种错觉就是引入了多线程之后, 又要解决由多线程引起的更多更麻烦的问题, emmm....
来源: https://www.cnblogs.com/wyq1995/p/10770473.html