一、wait()和sleep()的区别
1、相同点:wait()和sleep()都是用于将线程冻结的方法
2、wait()和sleep()的区别
(1)wait():可以指定时间,也可以不指定
sleep():必须指定时间
(2)在同步中,对于CPU的执行权和锁的处理不同
wait():释放执行权,释放锁(如果不释放锁,其他线程不能进入,就不能用notify()将其唤醒)
sleep():释放执行权,不释放锁(能自己醒,不需要被叫)
注:wait()和sleep()都可以让线程处于冻结状态。冻结状态就是释放CPU的执行权和执行资格
(3)wait():Object中的方法
sleep():Thread中的方法
3、Object类中
(1)public final void wait(long timeout) throws InterruptedException:在其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量前,导致当前线程等待。当前线程必须拥有此对象监视器
参数 timeout:要等待的最长时间。意味着自己可以醒
4、面试题
问题:synchronized(this){ wait(); },在wait()处有t0、t1、t2三个线程,之后,这三个线程被notifyAll()同时唤醒。问:在同步中,某一时刻只能有一个线程在执行,现在有3个线程活着,怎么办?
分析:t0、t1、t2被唤醒后,都处于具备着CPU的执行资格,等待CPU执行权的状态。如果CPU的执行权切换到t0上,t0也不一定能执行。因为t0、t1、t2都在同步中,在同步中想要执行,必须持有锁。只有t0获取到CPU的执行权,并获取到锁,才可以执行
即 同步中活着的线程不止一个,但只有一个线程在执行。谁拿到锁,并获取到CPU的执行权,谁执行
二、停止线程
1、停止线程的方法
(1)stop()方法 -- 已过时(使用此方法会造成:程序戛然而止,不知道完成了什么,也不知道哪些工作还没有做,同时无法完成清理工作)
(2)run()方法结束(线程的任务结束,线程就自动结束了)-- 使用退出标志
2、怎样控制线程的任务结束?
任务中都会有循环结构(要让线程去重复做很多事情),只要控制住循环,就可以结束任务,线程也就结束了
控制循环通常用定义标记来完成(标记即为条件),最常用的标记就是boolean值
注:开启多线程的目的是因为有些代码需要运行很多次,影响了其他代码的执行。想让这两部分代码同时运行,就要开启多线程,在run()方法中写循环
class StopThread implements Runnable {
//标记
private boolean flag = true;
/**
* 对外提供一个能改变标记的方式
*/
public void setFlag() {
flag = false;
}
@Override
public void run() {
//定义条件标记。有标记控制,就可以让while停下
while (flag) {
System.out.println(Thread.currentThread().getName() + "......");
}
}
}
public class Test {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
t0.start();
t1.start();
int num = 1;
for (; ; ) {
if (++num == 50) {
//将线程标记置为false
st.setFlag();
break;
}
System.out.println("main..." + num);
}
System.out.println("over");
}
}
三、interrupt()方法
1、定义标记不一定能让线程停止
class StopThread implements Runnable {
//标记
private boolean flag = true;
/**
* 对外提供一个能改变标记的方式
*/
public void setFlag() {
flag = false;
}
@Override
public synchronized void run() {
//定义条件标记
//此种情况定义标记没有用,因为t0、t1没有读到标记
while (flag) {
try {
wait(); //t0、t1全在此等待
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "..." + e);
}
System.out.println(Thread.currentThread().getName() + "......");
}
}
}
public class Test {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
t0.start();
t1.start();
int num = 1;
for (; ; ) {
if (++num == 50) {
//将线程标记置为false
st.setFlag();
break;
}
System.out.println("main..." + num);
}
System.out.println("over");
}
}
现象:主线程结束,但t0、t1线程全被wait()。t0、t1没有结束,程序没停下来
问题:如果线程处于冻结状态,无法读取标记,如何停止线程?
解决办法:可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。但是强制动作会发生中断异常InterruptedException,需要处理
class StopThread implements Runnable {
//标记
private boolean flag = true;
/**
* 对外提供一个能改变标记的方式
*/
public void setFlag() {
flag = false;
}
@Override
public synchronized void run() {
//定义条件标记
while (flag) {
try {
wait(); //t0、t1全在此等待
} catch (InterruptedException e) {
//强制动作会发生中断异常InterruptedException,需要处理
System.out.println(Thread.currentThread().getName() + "..." + e);
//将线程标记置为false
setFlag();
}
System.out.println(Thread.currentThread().getName() + "......");
}
}
}
public class Test {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
t0.start();
t1.start();
int num = 1;
for (; ; ) {
if (++num == 50) {
//可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格
//interrupt()将当前冻结状态清除,属于强制唤醒,会发生中断异常
t0.interrupt();
t1.interrupt();
break;
}
System.out.println("main..." + num);
}
System.out.println("over");
}
}
2、interrupt()方法
(1)interrupt()方法在Thread类中
(2)public void interrupt():中断线程。如果线程在调用Object类的wait()、wait(long)或wait(long, int)方法,或者该类(Thread类)的join()、join(long)、join(long, int)、sleep(long)或sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException
即 interrupt()将线程的冻结状态清除掉,让线程恢复到具备着CPU执行资格的状态(中断:终止运行,其实就是一种冻结状态)
(3)使用interrupt(),不用notify()也能唤醒被wait()的线程,不用等时间到也能唤醒被sleep()的线程。但interrupt()会抛出中断异常InterruptedException
3、当程序准备结束时,有可能会调用interrupt()强制地将某些线程从冻结状态拉回到运行状态,让其结束(让所有线程都结束,进程才可以结束)
四、守护线程setDaemon()
1、public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程(后台线程)。当正在运行的线程都是守护线程时,Java虚拟机退出。
注:该方法必须在启动线程前调用
2、上面的代码,让t0.interrupt()运行,将t1.interrupt()注释掉,程序不能停下来,因为t1还是活的。如果在开启t1(t1.start())前,将t1设置成守护线程t1.setDaemon(true),程序停了
3、守护线程(后台线程)的特点在于它和前台线程都正常进行开启,运行也一样,相互抢夺CPU的执行权。结束时不一样。结束时前台线程必须要进行手动结束(eg:以设置标志的形式将其结束),不结束线程会一直等待CPU的处理,消耗资源。而对于守护线程,如果所有的前台线程都结束了,守护线程无论处于什么状态,都自动结束
4、如果一个线程何时结束根据其他线程而定,就将该线程定义为守护线程
五、join()方法
1、public final void join() throws InterruptedException:等待该线程终止
2、何时使用join()方法?
当在进行线程运算时,希望在运算过程中临时加入一个线程进行运算,此时就使用join()方法(临时加入一个线程运算时,可以使用join()方法)
线程X执行到了线程A调用join()方法的这句话,线程X释放执行权,同时释放执行资格(相当于被冻结)。线程A和其他线程抢夺CPU的执行权执行,互不影响。等到线程A执行完毕,线程X才恢复执行资格(相当于被唤醒),才会继续抢夺CPU的执行权执行
class Demo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "......" + i);
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread t0 = new Thread(demo);
Thread t1 = new Thread(demo);
t0.start();
//t0.join():t0线程申请加入。主线程释放CPU的执行权,同时释放执行资格。主线程等t0线程执行完终止后,才恢复执行资格,继续抢夺CPU的执行权执行
// t0.join();
t1.start();
//如果将t0.join()放在此处,主线程释放CPU的执行权,同时释放执行资格。主线程等t0线程执行完终止后,才恢复执行资格,继续抢夺CPU的执行权执行
//主线程释放的CPU的执行权,如果t1线程得到,t1执行,如果t0线程得到,t0执行(t0、t1互不影响)
//join()方法只与执行此句话的线程(主线程)和调用join()方法的线程(t0线程)有关。执行此句话的线程必须让调用join()方法的线程先执行完毕,之后,执行此句话的线程再抢夺CPU的执行权执行
// t0.join();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "...main..." + i);
}
}
}
3、join()方法也可以让线程处于冻结状态,因此,该方法也会抛出中断异常InterruptedException(也可以用interrupt()方法将被冻结的线程强制恢复过来)
六、线程组(java.lang.ThreadGroup)
1、线程组:将线程进行组的划分。可以通过调用线程组的方法,操作整个线程组中所有的线程
eg:void interrupt():中断此线程组中的所有线程
void setDaemon(boolean daemon):更改此线程组的后台程序状态(将一个组的线程全设为后台线程)
2、线程在创建对象时,除了可以明确其任务外,还可以明确其名称、组
eg:Thread(ThreadGroup group, Runnable target):将线程存放到指定的组里去
七、创建线程的两种便捷方式
public class Test {
public static void main(String[] args) {
/**
* 创建线程的便捷方式一
*/
new Thread() {
@Override
public void run() {
for (int x = 0; x < 50; x++) {
System.out.println(Thread.currentThread().getName() + "...x = " + x);
}
}
}.start();
/**
* 创建线程的便捷方式二
*/
Runnable r = new Runnable() {
@Override
public void run() {
for (int z = 0; z < 50; z++) {
System.out.println(Thread.currentThread().getName() + "...z = " + z);
}
}
};
new Thread(r).start();
}
}
八、面试题
1、以下代码有无问题?如果有,发生在哪一行?
class Test implements Runnable {
public void run(Thread t) {
}
}
答:有问题,错误在第1行
编译器报错:Test不是抽象的,并且未覆盖java.lang.Runnable中的抽象方法run()
分析:run(Thread)是Test类的特有方法。Test类实现Runnable接口,但没有覆盖Runnable接口中的抽象方法。此时,Test类应该被定义为抽象类,即被abstract修饰
2、下面的代码运行结果是什么?
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run...");
}
}){
@Override
public void run() {
System.out.println("subThread run...");
}
}.start();
}
}
答:subThread run...
分析:以子类为主
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run...");
}
}){
}.start();
}
}
运行结果:runnable run...
分析:以任务为主
public class Test {
public static void main(String[] args) {
new Thread(){
}.start();
}
}
分析:以Thread自己的为主(线程子类调用父类的方法,因为没有覆盖)
总结:子类run() > 任务run() > Thread的run()