线程的常用方法-wait和notify以及线程的结束方式

再复习一下Java中的线程的状态图

wait和sleep的区别是:wait需要先持有锁(wait需要再synchronized代码块中执行),执行后会让出锁。而sleep不需要先持有锁,执行后也不会释放锁(有锁的话抱着锁睡觉),执行wait之后被notify之前线程的状态是WAITING,而sleep之后的状态是TIMED_WAITING. 

来自互联网的解释:

wait和sleep的区别?

* 单词不一样。
* sleep属于Thread类中的static方法、wait属于Object类的方法
* sleep属于TIMED_WAITING,自动被唤醒、wait属于WAITING,需要手动唤醒。
* sleep方法在持有锁时,执行,不会释放锁资源、wait在执行后,会释放锁资源。
* sleep可以在持有锁或者不持有锁时,执行。 wait方法必须在只有锁时才可以执行。

wait方法会将持有锁的线程从owner扔到WaitSet集合中,这个操作是在修改ObjectMonitor对象,如果没有持有synchronized锁的话,是无法操作ObjectMonitor对象的。

wait和notify

锁池中的状态是BLOCKED(有别的线程释放了锁资源,就会尝试竞争), 等待池中线程的状态是WAITING(需要别人使用notify唤醒),wait之后如果没有其他线程在锁池则线程会一直死等,像下面这样,永远不会结束

四个线程竞争锁

A拿到锁,其他的线程进入锁池

此时如果A执行了wait,则自己进入等待池,其他在锁池中的线程会继续竞争锁资源,最后肯定是一个线程能拿到锁

我们假设CD依次拿到锁并执行wait,则最后ACD3个线程都会出现在等待池中,此时B持有锁

如果此时B执行了notify则随机唤醒等待池中的3个线程中的一个,如果使用的是notifyAll则会把所有的等待池中的线程挪到锁池中准备竞争锁资源

package thread;

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        //sync并没有notify或者notifyAll,则线程会一直停着,什么也不执行
        t1.start();
        t2.start();
        //下面是main线程的执行过程,main现成执行sync同样会出现wait后线程不做任何事情的情况
        Thread.sleep(100);
        sync();
    }

    public static synchronized void sync() throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            if(i == 5) {
                //这个需要考虑后序叫醒的过程,
                //如果所有的线程都wait了,并且wait的时候也没有执行notify和notifyAll,则线程会一直停着不执行任何操作
                WaitAndNotify.class.wait();
            }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

执行结果:

t1:0
t1:1
t1:2
t1:3
t1:4
main:0
main:1
main:2
main:3
main:4
t2:0
t2:1
t2:2
t2:3
t2:4

下面我们尝试从main线程把等待池中的线程唤醒,改完之后的代码如下:

package thread;

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        //sync并没有notify或者notifyAll,则线程会一直停着,什么也不执行
        t1.start();
        t2.start();
        //下面是main线程的执行过程,main现成执行sync同样会出现wait后线程不做任何事情的情况
        Thread.sleep(100);
        Thread.sleep(10000);
        //尝试通过主线程随机唤醒一个等待池中的线程
        WaitAndNotify.class.notify();
    }

    public static synchronized void sync() throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            if(i == 5) {
                //这个需要考虑后序叫醒的过程,
                //如果所有的线程都wait了,并且wait的时候也没有执行notify和notifyAll,则线程会一直停着不执行任何操作
                WaitAndNotify.class.wait();
            }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

执行结果报错:

t1:0
t1:1
t1:2
t1:3
t1:4
t2:0
t2:1
t2:2
t2:3
t2:4
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at thread.WaitAndNotify.main(WaitAndNotify.java:25)

这里是wait和notify、notifyAll要注意的点:必须持有锁才能执行者三个方法,实际表现为这三个方法要放在synchronized包住的块中执行,改造之后的方法如下:

package thread;

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        //sync并没有notify或者notifyAll,则线程会一直停着,什么也不执行
        t1.start();
        t2.start();
        //下面是main线程的执行过程,main现成执行sync同样会出现wait后线程不做任何事情的情况
        Thread.sleep(100);
        Thread.sleep(10000);
        //尝试通过主线程随机唤醒一个等待池中的线程
        synchronized (WaitAndNotify.class) {
            WaitAndNotify.class.notify();
        }

    }

    public static synchronized void sync() throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            if(i == 5) {
                //这个需要考虑后序叫醒的过程,
                //如果所有的线程都wait了,并且wait的时候也没有执行notify和notifyAll,则线程会一直停着不执行任何操作
                WaitAndNotify.class.wait();
            }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

随机唤醒一个线程,我执行的时候唤醒的是t1,这个过程是随机的

t1:0
t1:1
t1:2
t1:3
t1:4
t2:0
t2:1
t2:2
t2:3
t2:4
t1:5
t1:6
t1:7
t1:8
t1:9

线程的结束方式

(1)stop方法

package thread;

public class TestStop {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        //主线程睡眠保证t1跑起来
        Thread.sleep(500);
        //获取t1目前的状态,应该是TIMED_WAITING
        System.out.println(t1.getState());
        //使用stop把t1强行终止掉
        t1.stop();
        //这里要睡眠一会,不然拿到的状态会是TIMED_WAITING,原因是在你调用t1.stop()后,线程调度器可能还没有来得及更新t1的状态。
        Thread.sleep(500);
        //获取t1现在的状态,应该是TERMINATED
        System.out.println(t1.getState());
    }
}

这里非常不推荐这种方式,真实的项目里很少会这么用,另外这个方法也已经过期了 

(2)使用共享变量

  也不常用

package thread;


public class TestStopByVariable {
    //这里一定要加volitile,t1线程无法感知到变化
    static volatile boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
           while(flag) {

           }
            System.out.println("任务结束");
        });
        t1.start();
        Thread.sleep(500);
        flag = false;
    }
}

(3)使用interrupt方式

这种停止线程方式是最常用的一种,在框架和JUC中也是最常见的

interrupt的基本使用

package thread;

public class TestInterrupt {
    public static void main(String[] args) throws InterruptedException {
        //默认情况下interrupt标志位的值为false
        System.out.println(Thread.currentThread().isInterrupted());
        //对线程进行打断
        Thread.currentThread().interrupt();
        //再次查看标志位,
        System.out.println(Thread.currentThread().isInterrupted());
        //这个查看interrupt的方法同时会把interrupt恢复为默认值false
        System.out.println(Thread.interrupted());

        System.out.println(Thread.interrupted());
        Thread t1 = new Thread(()->{
            //获取标志位的状态并在被打断的时候终止循环
            //这个操作和我们使用变量进行停止没有区别
           while(!Thread.currentThread().isInterrupted()) {

           }
            System.out.println("t1结束");
        });
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
    }
}

最常用的使用interrupt方式结束线程的方式

package thread;

public class TestInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
           while(true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   System.out.println("基于打断的形式结束了当前线程");
                   return;
               }
           }
        });
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
    }
}

执行结果如下:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at thread.TestInterrupt2.lambda$main$0(TestInterrupt2.java:8)
    at java.lang.Thread.run(Thread.java:750)
基于打断的形式结束了当前线程

上面的异常是我们printStackTrace打印出来的,如果不需要刻意注释掉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值