<1> 概念
每个运行中的应用程序就是一个进程。
线程又叫做轻量级进程,是进程内部的一个单一顺序的执行流,是进程中的实际运作单位。在多线程OS中,一个进程可能有多个线程,每条线程并行执行不同的任务。
进程是资源分配的基本单位;线程是独立运行、调度、分派的基本单位。
<2> 区别总结
在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
因为创建和撤销进程时,系统要为之分配或回收资源,如内存空间、I/O 设备等;切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置。
而线程切换时只需保存和设置少量寄存器内容,开销很小。
<1> 初始态(New)
创建后尚未启动。
<2> 运行态(Runnable)
可能正在运行,也可能正在等待 CPU 时间片。
Java中的线程将操作系统中的就绪和运行两种状态笼统称为运行态。
<3> 阻塞态(Blocked)
在Java中,指线程请求锁失败时进入的状态。由一个阻塞队列存放所有阻塞态的线程。
阻塞态等待获取一个排它锁,不断请求资源,一旦请求成功,就会进入就绪队列。
<4> 等待态(Waiting)
线程等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。由一个等待队列存放所有等待态的线程。
进入方法 | 退出方法 |
---|---|
调用了 无Timeout参数的 object.wait() 方法 | 其它线程调用object.notify() / object.notifyAll() |
调用了 无Timeout参数的 thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | LockSupport.unpark(Thread) |
<5> 超时等待态(Timed Waiting)
无需等待其它线程显式地唤醒,在一定时间之后会被系统线程自动唤醒。
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 object.wait() 方法 | 时间结束 / object.notify() / object.notifyAll() |
设置了 Timeout 参数的 thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
阻塞和等待的区别
<6> 终止态(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束。
有三种使用线程的方法:
<1> 实现 Runnable 接口
public class A implements Runnable {
public void run() {
// ...
}
}
public static void main(String[] args) {
A a = new A();
Thread thread = new Thread(a);
thread.start();
}
<2> 实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
public class B implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args)
throws ExecutionException, InterruptedException {
B b = new B();
FutureTask<Integer> ft = new FutureTask<>(b);
Thread thread = new Thread(ft);
thread.start();
Integer res = ft.get());
}
<3> 继承 Thread 类
public class C extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) {
C c = new C();
c.start();
}
<4> 从线程池中获取
应用程序可以使用Executor框架来创建线程池。
<5> 实现接口 VS 继承 Thread
一般实现接口更好,因为:
运行一个线程,只需调用Thread实例的start()
方法即可。当调用start()
方法之后,Java虚拟机会创建一个线程,然后在该线程中运行run()方法中定义的任务,真正实现多线程。
调用start()和run()的区别:
这里必须强调的是应该调用start()
方法,而不是run()
方法。直接调用run()
方法的结果是,虚拟机不会新建线程运行任务,而是在当前线程执行任务,无法实现多线程并发。
调用静态方法 Thread.sleep(millisec)
会休眠当前正在执行的线程,参数单位为毫秒。
可能会抛出 InterruptedException
,而且因为异常不能跨线程传播回 来,因此必须在本地线程进行处理。线程中抛出的其它异常也同样需要在本地进行处理。即,要放在try-catch(InterruptedException e)
中。
注意:
Thread.sleep(0)
是让当前线程放弃剩余时间片,CPU重新选择优先度最高的线程(可能还是自己)。在hotspot中相当于Thread.yield()
。wait() 和 sleep() 的区别
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
调用静态方法 Thread.yield()
,声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。
该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
在hotspot中,Thread.yield()
相当于 Thread.sleep(0)
。
守护线程是程序运行时在后台提供服务的线程,不属于程序中必要的部分。当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
使用 setDaemon(true)
方法将一个线程设置为守护线程。
main() 属于非守护线程。
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
在A线程中调用B线程的 join() 方法,会将A线程挂起,让B线程执行,直到线程B执行完毕。要放在try-catch(InterruptedException e)
中。
join
的内部使用了this.wait
,也就是说,如果threadA
持有threadB
的锁,在threadA
中调用threadB.join
时,会释放该锁。
但如果threadA
持有的其它对象的锁,则threadB.join
无法释放,如果threadB
也要竞争该锁,可能会产生死锁。
// 锁的是obj,join只释放threadB。因此join不释放锁,会死锁。
synchronized(obj){
threadB.join();
}
// 锁的是threadB,join可以释放锁
synchronized(threadB){
threadB.join();
}
public class JoinExample {
public void work1(Thread threadB){
try {
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i<5; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A");
}
}
public void work2(){
for(int i = 0; i<5; i++){
System.out.println("B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
JoinExample example = new JoinExample();
Thread threadB = new Thread(() -> example.work2());
Thread threadA = new Thread(() -> example.work1(threadB));
threadA.start();
threadB.start();
}
}
/*
B
B
B
B
B
A
A
A
A
A
*/
// A start -> A锁住o -> B阻塞 -> A让B join -> (A不释放o,却等待B;B被阻塞,却需要先结束)死锁
public class JoinExample {
public void work1(Object o, Thread threadB){
synchronized (o){
try{
// 确保join前B已经start
Thread.sleep(2000);
threadB.join();
for(int i = 0; i<5; i++){
Thread.sleep(1000);
System.out.println("A");
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void work2(Object o){
synchronized (o){
for(int i = 0; i<5; i++){
System.out.println("B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
JoinExample example = new JoinExample();
Object o = new Object();
Thread threadB = new Thread(() -> example.work2(o));
Thread threadA = new Thread(() -> example.work1(o, threadB));
// A先start,确保A已经锁住o后,B再start
threadA.start();
Thread.sleep(1000);
threadB.start();
}
}
原文:https://www.cnblogs.com/BWSHOOTER/p/14372397.html