1.基础
1.1线程状态
New: 这个状态呢,就是线程对象创建之后、启动之前,就是这个状态。
用代码来说呢,就是
Thread t = new Thread();
t.getState(); // New
Runnable: 当调用start方法后呢,线程就会进入Runnable状态,表示,我这个线程可以被执行了,如果调度器给这个线程分配了CPU时间,那么这个线程就可以被执行,这里一定要正确区分一下Runnable不是说正在执行,而是可以被执行,这两个还是有区别的。
Thread t = new Thread();
t.getState(); // New
t.start(); // Runnable
Blocked: 这个状态,当线程要进入临界区的时候,会发生。如果有一个线程已经到了获取锁进入了临界区,那么其他线程到了临界区会进入Blocked状态,由调度器选一个来执行,如果这个线程执行完毕后,大家还是一同为Blocked状态,调度器再选一个来执行。
Thread t = new Thread(new Runnable {
void run() {
synchronized (lock) { // <3> Blocked
// dothings
}
}
});
t.getState(); // <1> New
t.start(); // <2> Runnable
Waiting: 当调用了wait,join方法后,就会进入这个状态。一旦进入到这个状态,CPU就不会管你了,直到有别的线程通过notify方法将它唤起,否则的话,就会一直在等待中。其主要作用是需要等待其他线程执行完,满足了一定条件才可以继续往下执行,所以不会浪费 cpu 资源。
Thread t = new Thread(new Runnable {
void run() {
synchronized (lock) { // Blocked
// dothings
while (!condition) {
lock.wait(); // <3> Waiting
}
}
}
});
t.getState(); // <1> New
t.start(); // <2> Runnable
Timed_Waiting: 这个状态也是等待,但是是有一个计时器在里面,最常见的是使用 Thread.sleep 方法触发,触发后,线程就进入了 Timed_waiting 状态,随后会由计时器触发,再进入Runnable状态。
Thread t = new Thread(new Runnable {
void run() {
Thread.sleep(1000); // Timed_waiting
}
});
t.getState(); // New
t.start(); // Runnable
Terminated: 终结状态,当线程的所有代码都被执行完毕后,会进入到这个状态,这个就是字面意思了。
2.API
线程的新建
Thread t = new Thread();
run()
Thread t = new Thread();
t.run();
不能使用上面的方式启动线程,这样调用不会产生任何新的线程,只是一个单纯的方法调用。要想开启线程,不能使用run(),而应该用start()。
start()
new Thread(new Runnale() {
@Override
public void run() {}
}).start();
产生一个新线程去执行 run() 方法,只能调用一次,如果第二次调用,会抛出异常
线程的终止
stop()方法,但是已经被废弃。该方法太过暴力,强行把执行到一半的线程终止掉,可能会引起数据不一致的问题。stop()方法会直接终止线程,并立即释放这个线程持有的锁,而锁恰好是用来维持对象的一致性的。
那么要如何终止线程呢?可以自定义退出线程的方法,比如设置标志位
volatile boolean stoped;
public void myStop() {
stoped = true;
}
在线程合适的地方, 通过判断stoped变量来手动退出线程。
@Override
public void run() {
while (true) {
if (stoped) {
break;
}
}
// synchronized (object) {do sth.}
}
线程的中断
线程中断不会使线程立即退出,而是通知目标线程“你应该退出了”,退不退出是由目标线程来决定的。
设置中断
Thread类的实例通过 interrupt() 方法,它通知目标线程中断,也就是设置中断标志位。
检查是否中断(但同时会清除当前线程的中断标志位 )
Thread类的实例通过 isInterrupt() 方法,用于判断当前线程是否被中断(通过检查中断标志位);
静态方法 Thread.interrupted() 也可以判断当前线程的中断状态
wait() 和 notify()
wait() 是 Object的方法,也就是说每个对象都能调用。在当前线程中某个对象调用了 wait() 方法则该线程就停止执行,转为等待状态,并加入对象 obj 的等待队列,一直等到其他线程调用了obj.notify(),才从等待队列移出,重新尝试获取锁。
Object类还有一个notifyAll()方法,会唤醒某对象等待队列中的所有线程。
notify() 和 wait()方法在执行前必须获得对象的锁。为此在synchronized (obj)中先获得了锁,才能调用wait()或者notify()方法
线程的挂起和继续执行
Thread类中还有两个已经被废弃的方法,分别是suspend()和resume()。
suspend()在导致线程暂停的同时,不会释放任何锁,而且线程状态还是Runnable,其他任何线程都得不到被该线程占用的锁,直到在线程上执行了resume(),被挂起的线程才能继续,其他阻塞在相关锁上的线程也才可以继续执行。
但是如如resume()不小心在suspend()之前调用了,可能造成线程被永久挂起,从而永久占用锁。
注意和notify()、wait()方法的区别。由于方法已被废弃,不再讨论更多。
join()
join()表示将线程“加入”到当前线程中,会一直阻塞当前线程,直到该线程执行完毕,或者说:当前线程一直等待join入的线程执行完毕后,才能继续执行。
public class Abc {
public static volatile int i;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {for (i = 0; i < 100; i++);});
t.start();
t.join();
System.out.println(i);
}
}
举个例子,上面的代码中有main线程和t线程,由于线程 t 的 join ,使得 main 线程中System.out.println(i)的一定是等待t线程执行完毕后才能执行,因此打印值一定是100 .
join() 的实现依赖于 wait()。A线程中调用了B线程的 join 方法,则相当于A线程调用了B线程的 wait 方法,在调用了B线程的 wait 方法后,A线程就会进入阻塞状态。所以 A 线程会等待 B 执行完毕后才能继续执行。
yield()
yield()表示主动礼让当前线程的CPU使用权,让出不是会不会回来参与CPU资源的争夺,下次还能被分配到。调用该方法,好比是当前线程说:我的工作暂时做完,给其他线程一些工作机会。
3.其他概念
守护线程
守护线程用于守护用户进程。守护进程在后台默默完成一些系统性的服务,比如垃圾回收线程、JIT 线程就可理解成守护线程。
而用户线程是用于完成程序的业务操作的,当程序中的所有用户线程执行完毕,守护线程没有了可守护的对象,程序自然就退出。
即:在一个Java程序中只有守护线程时,虚拟机就自然退出。
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
}
});
t.setDaemon(true);
t.start();
System.out.println("over");
}
在上面的代码中,线程 t 被设置成守护线程(必须在start()之前调用),用户线程 main 在打印 “over” 后执行完毕,t 没有要守护的线程了,所以程序会出,如果不设置t为守护线程,我们知道由于while (true)程序永远不会退出。
线程优先级
Java 的线程有 优先级,高优先级线程在竞争资源时更有优势,但这也是个概率问题,高优先级也可能抢占失败,低优先级线程也不是说一定会饥饿。
Java中的线程有1~10的优先级,数字越大,优先级越高。Thread类中内置了三个默认的优先级,如下。
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;