再复习一下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打印出来的,如果不需要刻意注释掉