线程的实现
- 通过实现Runnable接口的方式,实现其中的run方法。
- 继承Thread类,然后重写其中的run方法。
- 通过线程池创建线程,默认采用DefaultThreadFactory。
- 有返回值的callable,实现callable接口,实行call方法。
本质上,都是通过1,2两种类型创建方式
实现runnable接口要比继承thread类更好
好处:
- 可以把不同的内容进行解耦,权责分明。
- 某些情况下可以提升性能,减小开销。
- 继承Thread类相当于限制了代码未来的可拓展性。
如何正确停止线程
启动线程需要调用Thread类的start()方法,并且在run()方法中定义需要执行的任务。
使用interrupt停止线程,这个是属于通知接口,是否真的暂停取决于线程本身。
public class StopThread implements Runnable {
@Override
public void run() {
int count = 0;
// 判断线程释放被中断,并且判断count是否小于1000
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
// 休眠50秒钟
Thread.sleep(50000);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
// 休眠5秒钟
Thread.sleep(5000);
// 终端线程
thread.interrupt();
}
}
如果在第一段代码中休眠的时候,第二段代码线程发起中断,则休眠线程则会java.lang.InterruptedException: sleep interrupted 异常中断,抛出异常。
错误的停止方法
- stop()会把线程停止,导致任务戛然而止有风险
- suspend()容易导致死锁,因为线程A调用suspend()让B挂起,B线程没有释放锁就进入休眠,如果A想要拿到B持有的锁,这是B在休眠就导致A拿不到锁就死锁了。
- resume()
使用volatile标记为的停止方法是可能出现问题
public class StopThread implements Runnable {
// volatile标记位
public volatile boolean canceled = false;
@Override
public void run() {
int count = 0;
// 判断线程释放被中断,并且判断count是否小于1000
while (!canceled && !Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
// 休眠50秒钟
Thread.sleep(50000);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
// 休眠5秒钟
Thread.sleep(5000);
// 终端线程
thread.interrupt();
thread.canceled = true;
}
}
线程在6种状态之间转化
线程的生命周期
- New(新创建)表示线程被创建但尚未启动的状态,当线程调用start(),就变成Runnable。
- Runnable(可运行)表示java中的线程可以running或者是ready状态
- Blocked(被阻塞)
从runnable状态转到Blocked状态,只有一种可能性,就是进入synchronized保护的代码时没有抢到monitor锁。
转化成runnable状态:获取到monitor锁
- Waiting(等待)
进入waiting状态的三种可能性
a. 没有设置Timeout参数的Object.wait()方法
b. 没有设置Timeout参数的Thread.join()方法
c. LockSupport.park()方法
wait()会释放monitor锁
唤醒:如果其他线程调用notify()或者notifyAll()来唤醒它,会直接进入到Blocked状态。因为唤醒Waiting线程的线程如果调用notify()或者notifyAll(),要求必须首先持有该monitor锁,所以处于waiting状态的线程被唤醒时拿不到该锁,会进入blocked状态
- Timed_waiting(计时等待)
跟waiting的区别是时间到了就自动唤醒或者是等待唤醒信号进行唤醒
- Terminated(被终止)
wait/notify/notifyAll方法的使用注意事项
为什么wait必须在synchronized保护的同步代码中使用?
/**
* 在使用wait()方法时,必须将wait方法写在synchronized保护的while代码块中,并始终判断条件是否满足
* 如果满足就往下执行,如果不满足就执行wait()方法,在执行wait()方法之前,必须持有对象的monitor锁,也就synchronized锁
*/
public void give(String data) {
synchronized (this) {
buffer.add(data);
notify();
}
}
public String take() throws InterruptedException {
synchronized (this) {
while (buffer.isEmpty()) {
wait();
}
return buffer.remove();
}
}
为什么wait/notify/notifyAll方法被定义在Object类中,而sleep定义在Thread类中?
因为Java中每个对象都有一把称之为monitor监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置,这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll也都是锁级别的操作,它们的锁属于对象。
所以把它们定义在Object类中是最合适,因为Object类是所有对象的父类
wait/notify和sleep方法的异同
相同点:
- 都可以让线程阻塞
- 它们都可以响应interrupt中断:在等待的过程中如果收到中断信号,都可以进行响应,
并抛出InterruptedException异常
不同点:
- wait方法必须在synchronized保护的代码中使用,而sleep方法并没有这个要求
- 在同步代码中执行sleep方法时,并不会释放monitor锁,但执行wait方法时会主动释放monitor锁
- sleep方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的wait方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复
- wait/notify是Object类的方法,而sleep是Thread类的方法