1. 新建线程的三种方式
1.1. 通过继承Thread重写run方法;
/**
* 1.继承Thread 重写run方法
* 程序里面调用myThread.start();,虚拟机会创建一个线程,然后等到线程第一次得到时间片时再调用run()方法。
*/
public static class MyThread extends Thread{
@Override
public void run(){
System.out.println("myThread");
}
}
public static void main1(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
1.2. 通过实现runable接口
/**
* 2.实现Runnable
*/
public static class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("myThread2");
}
}
public static void main2(String[] args) {
new Thread(new MyThread2()).start();
//java8函数式编程调用,省略MyThread2
new Thread(()->{
System.out.println("java8匿名内部类");
}).start();
}
1.3. 通过实现callable接口
/**
* 3.实现callable接口,有返回值
* 注意调用get方法会阻塞当前线程,直到得到结果。
* 所以实际编码中建议使用可以设置超时时间的重载get方法
*/
public static void main3(String[] args) {
//1:
ExecutorService service = Executors.newCachedThreadPool();
Future<String>future = service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "通过实现Callable接口";
}
});
try {
String s = future.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//2:
Task task = new Task();
Future<Integer> submitTask = service.submit(task);
try {
Integer taskResult = submitTask.get();
System.out.println(taskResult);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//FutureTask
FutureTask futureTask = new FutureTask(task);
service.submit(futureTask);
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(1000L);
return 2;
}
}
2. 线程的六种状态:
- NEW:初始状态,线程被创建出来但没有被调用
start()
。 - RUNNABLE: 运行状态,线程被调用了
start()
等待运行的状态。 - BLOCKED:阻塞状态,需要等待锁释放。
- WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
- TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
- TERMINATED:终止状态,表示该线程已经运行完毕。
线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。
在操作系统层面有READY和RUNNABLE状态,而在JVM中只有RUNNING ,所以JAVA系统一般将这两个状态统称为RUNNABLE(运行中) 状态 。
2.1. NEW
处于NEW状态的线程此时尚未启动,这里是指还没有调用start()
方法
@Test
public void testStateNew(){
Thread thread = new Thread(() -> {});
System.out.println(thread.getState());//NEW
}
2.1.1. NEW ->RUNNABLE
调用线程的start()方法
2.1.2. 关于start()的两个问题:
- 反复调用同一个线程的start方法是否可行?
答:在start()内部,这里有一个threadStatus的变量。如果它不等于0,调用start()是会直接抛出异常的。在第一次调用start()之后,threadStatus的值会改变。ThreadStatus!=0,此时再次调用start()就会抛出IllegalThreadStateException异常。
- 假如一个线程执行完毕(状态是TERMINATED),再次调用这个线程是否可以?
答:不可以,ThreadStatus=2代表当前线程状态为TERMINATED。
2.2. RUNNABLE
表示当前线程正在运行中。处于RUNNABLE状态的线程在java虚拟机中运行,也可能等待cpu分配资源。
java线程中的RUNNABLE状态其实包含了传统操作系统中的ready和running两个状态。
2.3. BLOCKED
阻塞状态。处于BLOCKED状态的线程正等待另一个线程的锁释放进入同步区。
比如线程1进入一个加锁的同步去还未执行完,线程2也调用这个加锁的同步方法,此时线程1在执行,线程2就是阻塞状态。
2.3.1. BLOCKED ->RUNNABLE
处于BLOCKKED状态的线程是因为在等待锁的释放。所以加入有两个线程a、b,a线程提前获得锁并且暂时未释放锁,那么此时的b就处于BLOCK状态。
@Test
public void test() throws InterruptedException {
Thread a = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
},"a");
Thread b = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
},"b");
a.start();
/**
* 执行的主线程只保证了a,b两个线程调用start()方法(转化为RUNNABLE状态),
* 如果CPU执行效率高一点,还没等两个线程真正开始争夺锁,就已经打印此时两个线程的状态(RUNNABLE)了。
* 当然,如果CPU执行效率低一点,其中某个线程也是可能打印出BLOCKED状态的(此时两个线程已经开始争夺锁了)。
* 休眠1000<testMethod:2000,这样就能在a运行且未执行完时候,b就是阻塞状态
*/
Thread.sleep(1000);
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 输出?
System.out.println(b.getName() + ":" + b.getState()); // 输出?
}
private synchronized void testMethod(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.4. WAITING
等待状态。处于等待状态的线程变成RUNNABLE状态需要其他线程的唤醒。
调用如下方法可以使线程进入等待状态:
- Object.wait(): 使当前线程处于等待状态直到另外一个线程唤醒它(Thread.notify(),Thread.notifyAll())。该操作会释放锁。
- Thread.join(): 等待线程执行完毕,底层调用的是Object的wait方法。
- LockSupport.park(): 除非获得调用许可,否则禁用当前线程进行线程调度。
2.4.1. RUNNABLE ->WAITING
根据转化图有三种可以转化,这里只介绍Object.wait()和Thread.join();
- Object.wait()
- 调用wait()方法前线程必须持有对象的锁,必须在同步代码块中调用。
- 线程调用wait()方法时,会释放当前的锁,直到有其他线程调用notify()/notifyAll()方法唤醒等待锁的线程。
- 需要注意的是,其他线程调用notify()方法只会唤醒单个等待锁的线程。如果有多个线程都在等待这个锁的话不一定会唤醒之前调用wait()方法的线程。
- 同样,调用notifyAll()方法唤醒所有等待锁的线程之后,也不一定马上把时间片分给刚放弃锁的那个线程,具体看系统的调度。
- Thread.join()
- 调用join()方法,会一直等待这个线程执行完毕(转换成TERMINATED状态)。如在a线程中调用b.start();b.join();那么a线程会阻塞,等待b线程执行完毕。
2.5. TIMED_WAITING
超时等待状态。线程等待一个具体时间,时间到了后会自动唤醒。
调用一下方法会使线程进入超时等待状态:
- Thread.sleep(long millis): 使当前线程睡眠指定时间,不会释放锁
- Object.wait(long timeout): 线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒。会释放锁
- Thread.join(long millis) : 等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行。
- LockSupport.parkNanos(long nanos) : 除非获得调用许可,否则禁用当前线程进行线程调度指定时间。
- LockSupport.parkUntil(long deadline): 同上,也是禁止线程进行调用指定时间。
2.6. RUNNABLE -> TIMED_WAITING
TIMED_WAITING 和WAITING 类似。只是TIMED_WAITING时间是指定的。
- Thread.sleep(long) : 当前线程睡眠指定时间,需要注意的是这里的“睡眠”只是暂时停止执行,不会释放锁,线程会重新进入RUNNABLE状态。
- Object.wait(long) : 使当前线程进入TIMED_WAITING状态。这里的wait(long)和无参的wait()相同的地方是,都可以通过其他线程调用notify()/notifyAll()唤醒。不同地方是,有参的就算其他线程不唤醒,经过指定的时间后会自动唤醒,拥有去争夺锁的资格。
- Thread.join(long) : 使当前线程执行指定时间,并且使线程进TIMED_WAITING状态。
public void blockedTest() {
······
a.start();
a.join(1000L);
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 输出 TIEMD_WAITING
System.out.println(b.getName() + ":" + b.getState());
}
这里 a.join(1000L); 小于a线程sleep时间,那么a的状态输出TIMED_WAITING
2.7. terminated
终止状态。此时线程已执行完毕。
3. 线程状态的基本操作
除了新建一个线程,线程在生命周期还有其他操作,而这些操作是线程的一种通信方式
3.1. interrupted -中断
在某些情况下,我们在线程启动后发现并不需要它继续执行下去时,需要中断线程。目前在Java里还没有安全直接的方法来停止线程,但是Java提供了线程中断机制来处理需要中断线程的情况。
线程中断机制是一种协作机制。需要注意,通过中断操作并不能直接终止一个线程,而是通知需要被中断的线程自行处理。
在线程中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态被设置为true,但是具体被要求中断的线程要怎么处理,完全由被中断线程自己而定,可以在合适的实际处理中断请求,也可以完全不处理继续执行下去。
线程在进入WAITING[join()/wait()] 和TIMED_WAITING[wait(long millis)/sleep(long millis)/join((long millis))]对线程对象调用interrupt()会使得该线程抛出InterruptedException,抛出异常后,中断标志位会被清空(线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态。),而不是被设置。
3.1.1. Thread提供的关于线程中断的几个方法
- Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为true(默认是flase);
- Thread.currentThread().isInterrupted():测试当前线程是否被中断。
- Thread.isInterrupted():测试当前线程是否被中断。与上面方法不同的是调用这个方法并不会影响线程的中断状态。
不同状态的终止情况见附录:
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/u2364617/agpd7w/cux31zguoqwn5vaw
private static void InterrpDemoOne() throws InterruptedException {
//sleepThread睡眠1000ms
final Thread sleepThread = new Thread(){
@Override
public void run(){
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//busyThread一直死循环
Thread busyThread = new Thread(){
@Override
public void run(){
while (true){
}
}
};
sleepThread.start();
busyThread.start();
Thread.sleep(2000L);//休眠两秒
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());//sleepThread isInterrupted: true
System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());//busyThread isInterrupted: true
}
3.1.2. 安全的终止线程
/**
* 安全终止线程
*/
private static class Shutdown{
public static void main(String[] args) throws InterruptedException{
Runner one = new Runner();
Thread countThread = new Thread(one,"CountThread");
countThread.start();
Thread.sleep(1000L);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two,"CountThreadT");
countThread.start();
Thread.sleep(1000L);
two.cancel();
}
public static class Runner implements Runnable{
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
}
public void cancel(){
on = false;
}
}
}
3.2. join -加入当前线程
如果在在线程a中执行了bThread.join()语句,表示当前线程a等待bThread线程终止才会从bThread.join()返回。
线程Thread除了提供join()方法外,还提供了join(long millis)和join(long millis,int nanos)这两种具备超时特性的方法。这两个超时方法表示,如果线程thread在给定时间内没有终止,那么将会从改方法中返回。
public static class JoinDemo{
public static void main(String[] args) {
//每个线程的结束是前驱线程的终止
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
//每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回。
Thread thread = new Thread(new Domino(previous));
//相当于在thread线程中调用了当前线程的join
thread.start();
previous = thread;
}
}
static class Domino implements Runnable{
private Thread thread;
public Domino(Thread thread){
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
}
3.3. sleep -休眠
sleep(long millis)是Thread的静态方法。让当前线程按照指定的时间休眠。需要注意的是如果当前线程获得了锁,sleep方法不会失去锁。
public static class SleepDemo{
public static void main(String[] args) throws InterruptedException {
Thread.sleep(3000L);
}
}
3.3.1. sleep()和wait()区别
- sleep()是Thread的静态方法。wait()是Object实例方法。
- wait()必须在同步方法或者方法块中调用,也就是必须已经获得对象锁。sleep()可以随处调用
- sleep()只会让出cpu不会释放掉当前的锁。wait()会释放当前占有的对象锁,使该线程进入到等待池中,等待下一次获取资源。
- sleep(long millis)方法在休眠时间到达后如果再次获得CPU时间就会继续执行。wait()方法必须等待Object.notify()/Object.notifyAll()通知后,才会离开等待池,并且再次获得CPU时间片才继续执行。
3.4. yield - 让出当前CPU
yield()是Thread的静态方法。它会让当前的线程让出CPU。需要注意的是,让出的CPU并不代表当前线程不再运行了,如果在下一次竞争中又获得了CPU时间片当前线程会继续运行。另外,让出的时间片只会分配给当前同等优先级的线程。
java程序中,通过设置整型成员变量Priority来控制优先级。优先级范围从0-10.可以通过setPriority(int)方法进行设置,默认是5,优先级高的线程相较于优先级低的线程先获得时间片。
public static class YieldDemo{
public static void main(String[] args) {
Thread t1 = new Thread(new demo(),"dmeo1");
Thread t2 = new Thread(new demo(),"dmeo2");
//设置优先级,范围1-10。默认5.越高约优先获取时间片
t1.setPriority(10);
t2.setPriority(10);
t1.start();
t2.start();
}
public static class demo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i );
if(i%20 == 0){
Thread.yield();
}
}
}
}
}
3.4.1. sleep()和yield方法的区别
- 同样都会让出当前线程交给处理器资源。但是不同的是,sleep()交出去的时间片其他线程都可以去竞争。yield()让出去的时间片只允许与当前线程具有同等优先级的线程获得时间片。
- yield, sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。
- yield, sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。
3.5. Daemon -守护线程
Daemon 线程是一种支持型线程,以为他的主要作用是用作程序中后台调度以支持性工作。比如垃圾回收线程,JIT线程就可以理解为守护线程。与之对应的是用户线程,用户线程可以认为是系统的工作线程,完成系统业务操作。用户线程结束后就以为着整个系统业务全部结束,因此就没有对象需要守护。守护线程自然就会退。当一个java虚拟机没有非Daemon线程的时候,java虚拟机就会退出。
用户线程结束Daemon会立即终止。在构建Daemon线程时候,不能倚靠finally中代码内容执行关闭或清理资源。
通过setDaemon(true)设置为守护线程。需要注意需要在线程start()前设置。
public static class DaemonDemo{
//没有任何输出,执行完main方法线程直接退出,不会执行finally方法。
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner(),"DaemonRunner");
thread.setDaemon(true);
thread.start();
}
static class DaemonRunner implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("DaemonThread finally run .");
}
}
}
}
4. 线程组
java中有ThreadGroup来表示线程组,我们可以通过线程组对线程进行批量控制。
每一个线程必然会存在于一个线程组中,执行main()方法的线程组名称就是main,如果new Thread时没有显式指定,那么线程默认将父线程(当前执行new Thread的线程)设置自己的线程组
public static class ThreadGroupDemo{
public static void main(String[] args) {
//1.测试线程组名字未定义会随父线程线程组名字
IntStream.range(1,10).forEach(i->{
Thread t = new Thread(new T1(),"线程:"+i);
t.setPriority(i);
t.start();
});
//2.线程统一处理异常
ThreadGroup threadGroup1 = new ThreadGroup("group1"){
//继承ThreadGroup并重新定义一下方法
//在线程抛出uncheck exception会执行此方法
@Override
public void uncaughtException(Thread t, Throwable e){
System.out.println(t.getName() + ":" + e.getMessage());
}
};
Thread thread1 = new Thread(threadGroup1, new Runnable() {
@Override
public void run() {
throw new RuntimeException("测试异常");
}
});
thread1.start();
}
public static class T1 extends Thread {
@Override
public void run(){
System.out.println(String.format("当前执行的线程是:%s,优先级:%d",Thread.currentThread().getName(),Thread.currentThread().getPriority()));
}
}
}