【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态

文章目录

  • 一、创建和运行线程
    • 1.1 创建运行线程:Thread
    • 1.2 创建运行线程:Runable配合Thread
    • 1.3 Thread与Runnable的关系
    • 1.4 创建运行线程:FutureTask配合Thread
  • 二、观察多个和线程同时运行
  • 三、查看进程线程方法
    • 3.1 windows
    • 3.2 linux
  • 四、线程运行原理
    • 4.1 栈与栈帧
      • 4.1.1 测试栈帧
      • 4.1.2 观察不同线程的栈帧
    • 4.2 线程上下文切换
  • 五、常见方法
    • 5.1 start()与run()
    • 5.2 sleep()
    • 5.3 yield()
    • 5.4 线程优先级
    • 5.5 join()
      • 5.5.1 只创建一个线程,等待线程运行结束
      • 5.5.2 创建两个线程,等待线程运行结束
      • 5.5.3 有时效的jon
    • 5.6 interrupt()
      • 5.6.1 打断sleep,wait,join线程
      • 5.6.2 打断正常运行的线程
      • 5.6.3 两阶段终止模式
      • 5.6.4 打断park线程
  • 六、主线程与守护线程
  • 七、线程状态
    • 7.1 五种状态
    • 7.2 六种状态

一、创建和运行线程

pom.xml依赖如下:

<dependencies>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.18.10version>
    dependency>
    <dependency>
        <groupId>ch.qos.logbackgroupId>
        <artifactId>logback-classicartifactId>
        <version>1.2.7version>
    dependency>
dependencies>

1.1 创建运行线程:Thread

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t.setName("t1");
        t.start();

        log.debug("running");
    }
}

输出:

21:36:28.304 [main] DEBUG c.Test1 - running
21:36:28.304 [t1] DEBUG c.Test1 - running

1.2 创建运行线程:Runable配合Thread

把【线程】和【任务】(要执行的代码)分开

  • Thread代表线程
  • Runnable可运行的任务(线程要执行的代码)
@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                log.debug("running");
            }
        };

        Thread t = new Thread(r, "t2");
        t.start();
    }
}

使用lambda表达式简化:

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) {
        Runnable r = () -> log.debug("running");

        Thread t = new Thread(r, "t2");
        t.start();
    }
}

再简化:

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> log.debug("running"), "t2");
        t.start();
    }
}

1.3 Thread与Runnable的关系

Thread实现了Runnable

class Thread implements Runnable {...}

所以,在Thread内要实现Runnable接口内的方法run()。可以看到,当target不为空时,在run()方法中又调用target.run()

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

所以接下来要看看target是什么,在初始化方法init()中。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
	...
	this.target = target;
	...
}

所以,target就是一个实现Runnable接口所创建的对象。

所以,如果创建了一个Runnable对象给Thread,那么Thread运行的就是一个Runnable对象中的run方法;如果没创建Runnable对象,就需要重写run方法,Thread将运行这个重写的run方法。


方法1是把线程和任务合并在了一起,方法2是把线程和任务分开了,具有如下优点:

  • 用Runnable更容易与线程池等高级API配合
  • 用Runnable让任务类脱离了Thread继承体系,更灵活

1.4 创建运行线程:FutureTask配合Thread

@Slf4j(topic = "c.Test3")
public class Test3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("running...");
                Thread.sleep(1000);
                log.debug("running...");
                return 100;
            }
        });

        Thread t = new Thread(task, "t1");
        t.start();

        // 阻塞
        log.debug("{}", task.get());

        log.debug("over");
    }
}

只有当log.debug("{}", task.get());运行完才会执行后面的语句,主线程将阻塞在这里。

二、观察多个和线程同时运行

@Slf4j(topic = "c.TestMultiThread")
public class TestMultiThread {
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                log.debug("running");
            }
        }, "t1").start();

        new Thread(() -> {
            while (true) {
                log.debug("running");
            }
        }, "t2").start();
    }
}

三、查看进程线程方法

3.1 windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • 查看进程:tasklist
  • 杀死进程:taskkill
  • 查找指定进程:tasklist | findstr 进程包含的字符串
  • 强制杀死进程:taskkill /F /PID 进程号

3.2 linux

  • 查看所有进程:ps -ef
  • 查看java进程:ps -ef | grep java
  • 查看java进程:jps
  • 杀死java进程:kill 进程号
  • 查看java某进程的线程:top -H -p 进程号
  • 查看java某进程的状态:jstack 进程号

java图形化界面:jconsole
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第1张图片

【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第2张图片

四、线程运行原理

4.1 栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)

我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame) 组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

4.1.1 测试栈帧

public class TestFrames {
    public static void main(String[] args) {
        method1(10);
    }

    private static void method1(int x) {
        int y = x +1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object o = new Object();
        return o;
    }
}

method1(10)位置打上断点,debug调试程序:
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第3张图片
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第4张图片
详细过程:https://www.bilibili.com/video/BV16J411h7Rd?p=21

4.1.2 观察不同线程的栈帧

public class TestFrames {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                method1(20);
            }
        };
        t1.setName("t1");
        t1.start();
        method1(10);
    }

    private static void method1(int x) {
        int y = x +1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object o = new Object();
        return o;
    }
}

在method1处打上端点。
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第5张图片
断点模式选择Thread
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第6张图片
debug进行调试,可以选择不同的栈帧进行调试:
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第7张图片

4.2 线程上下文切换

线程上下文切换(Thread Context Switch)

因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码

  • 线程的cpu时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了sleep、 yield、 wait、 join、 park、 synchronized、 lock 等方法

当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址, 是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch频繁发生会影响性能

五、常见方法

方法名 功能说明 注意
start() 启动一个新线程,在新的线程运行run方法中的代码 start方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException
run() 新线程启动后会调用的方法 如果在构造Thread对象时传递了Runnable 参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为
join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等待n毫秒 n毫秒内线程还没有结束就不等了
getId() 获取线程长整型的id id唯一
getName() 获取线程名
setName(String) 修改线程名
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的机率
getState 获取线程状态 Java中线程状态是用6个enum表示,分别为: NEW,RUNNABLE, BLOCKED, WAITING, TIMED _WAITNG,TERMINATED
isInterrupted() 判断当前线程是否被打断 不会清除打断标记
isAlive() 线程是否存活(还没有运行完毕)
interrupt() 打断线程 如果被打断线程正在sleep,wait, join 会导致被打断的线程抛出InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park 的线程被打断,也会设置打断标记
currentThead() 获取当前正在执行的线程
sleep(long n) 让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其它线程.
yield() 提示线程调度器让出当前线程对CPU的使用. 主要为了测试和调试

5.1 start()与run()

直接调用run()方法,就仅仅是调用了run()方法,没有启动线程,并不会提高效率。

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        System.out.println(t1.getState());
        t1.run();  // 仅仅调用了run()方法,没有启动线程
        System.out.println(t1.getState());
    }
}

运行结果

NEW
20:34:52.813 [main] DEBUG c.Test4 - running...
NEW

使用start()开启线程:

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        System.out.println(t1.getState());
        t1.start();
        System.out.println(t1.getState());
    }
}

运行结果:

NEW
RUNNABLE
20:35:47.879 [t1] DEBUG c.Test4 - running...

5.2 sleep()

  1. 调用sleep会让当前线程从Running进入Timed Waiting状态
  2. 其它线程可以使用interrupt 方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性

测试1:调用sleep会让当前线程从Running进入Timed Waiting状态

@Slf4j(topic = "test6")
public class Test6 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        log.debug("t1 state: {}", t1.getState());
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state: {}", t1.getState());
    }
}

运行结果:

20:41:03.324 [main] DEBUG test6 - t1 state: RUNNABLE
20:41:03.827 [main] DEBUG test6 - t1 state: TIMED_WAITING

解释:

程序启动时,主线程先运行,此时t1线程还未进入睡眠状态,当主线程等待了500ms后,t1线程此时已经进入睡眠状态,此时主线程获取的t1线程状态为睡眠状态。


测试2:其它线程可以使用interrupt 方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException

@Slf4j(topic = "test7")
public class Test7 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("enter sleep...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up");
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("interrupt...");
        t1.interrupt();
    }
}

t1线程启动后,进入睡眠状态持续2000ms。主线程睡眠1000ms后打断t1线程。

运行结果:

20:47:50.398 [t1] DEBUG test7 - enter sleep...
20:47:51.397 [main] DEBUG test7 - interrupt...
20:47:51.397 [t1] DEBUG test7 - wake up
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Chapter3.Test7$1.run(Test7.java:23)

测试4:建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第8张图片

@Slf4j(topic = "test8")
public class Test8 {
    public static void main(String[] args) {
        log.debug("enter");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("end");
    }
}

睡眠前后各打印一次,输出结果:

20:52:46.795 [main] DEBUG test8 - enter
20:52:47.797 [main] DEBUG test8 - end

案例:sleep()防止CPU占用100%

在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield 或sleep来让出cpu的使用
权给其他程序

while (true) {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  • 可以用wait或条件变量达到类似的效果
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep适用于无需锁同步的场景

5.3 yield()

  1. 调用yield会让当前线程从Running进入Rummable状态,然后调度执行其它同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果
  2. 具体的实现依赖于操作系统的任务调度器

5.4 线程优先级

线程优先级会提示(hint) 调度器优先调度该线程,但它仅仅是一个提示, 调度器可以忽略它
如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用

@Slf4j(topic = "test9")
public class Test9 {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            while (true) {
                System.out.println("----->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            while (true) {
                // Thread.yield();
                System.out.println("        ----->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");

        // t1.setPriority(Thread.MIN_PRIORITY);
        // t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

通过yeild()或是优先级的设置,可以起到一定效果,让其中的一个线程的count增长的快一些。

5.5 join()

5.5.1 只创建一个线程,等待线程运行结束

@Slf4j(topic = "test10")
public class Test10 {
    static int r = 0;

    public static void main(String[] args) {
        test1();
    }
    private static void test1() {
        log.debug("start");
        Thread t1 = new Thread(() -> {
            log.debug("start");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("end");
            r = 10;
        }, "t1");
        t1.start();
        log.debug("r is {}", r);
        log.debug("end");
    }
}

运行结果:

10:04:14.629 [main] DEBUG test10 - start
10:04:14.658 [t1] DEBUG test10 - start
10:04:14.658 [main] DEBUG test10 - r is 0
10:04:14.659 [main] DEBUG test10 - end
10:04:14.669 [t1] DEBUG test10 - end

分析

因为主线程和线程t1是并行执行的,t1 线程需要1秒之后才能算出r=10
而主线程一开始就要打印r的结果,所以只能打印出r=0

解决方法

用join, 加在t1.start()之后即可

@Slf4j(topic = "test10")
public class Test10 {
    static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("start");
        Thread t1 = new Thread(() -> {
            log.debug("start");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("end");
            r = 10;
        }, "t1");
        t1.start();
        t1.join();   // 加上这句就可以了
        log.debug("r is {}", r);
        log.debug("end");
    }
}

运行结果:

10:07:36.843 [main] DEBUG test10 - start
10:07:36.872 [t1] DEBUG test10 - start
10:07:37.872 [t1] DEBUG test10 - end
10:07:37.872 [main] DEBUG test10 - r is 10
10:07:37.873 [main] DEBUG test10 - end

以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第9张图片

5.5.2 创建两个线程,等待线程运行结束

当有两个线程同时运行并使用join时,线程1需要睡眠1s,线程2需要睡眠2s。
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第10张图片
此时程序的总运行时间需要2s,t1.join()t2.join()互换位置也一样。
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第11张图片

5.5.3 有时效的jon

@Slf4j
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {

        // t1线程运行2s
        Thread t1 = new Thread(() -> {
            log.debug("start");
            try {
                TimeUnit.SECONDS.sleep(2);
                log.debug("end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        
        t1.start();
        t1.join(1000);
        log.debug("end");
    }
}

运行结果:

10:25:36.407 [t1] DEBUG Chapter3.TestJoin - start
10:25:37.406 [main] DEBUG Chapter3.TestJoin - end
10:25:38.410 [t1] DEBUG Chapter3.TestJoin - end

从运行时间上看,主线程只等待了1s。而t1线程花费2s才运行完。

5.6 interrupt()

5.6.1 打断sleep,wait,join线程

@Slf4j
public class Test11 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("sleep....");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }
}

运行结果:

10:42:50.898 [t1] DEBUG Chapter3.Test11 - sleep....
10:42:51.897 [main] DEBUG Chapter3.Test11 - interrupt
10:42:51.897 [main] DEBUG Chapter3.Test11 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at Chapter3.Test11.lambda$main$0(Test11.java:24)
	at java.lang.Thread.run(Thread.java:748)

5.6.2 打断正常运行的线程

@Slf4j
public class Test12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if (interrupted) {
                    log.debug("被打断,退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("interrupt");
        t1.interrupt();
    }
}

运行结果:

10:48:21.350 [main] DEBUG Chapter3.Test12 - interrupt
10:48:21.352 [t1] DEBUG Chapter3.Test12 - 被打断,退出循环

通过验证打断标记来决定是否退出循环,否则即使打断t1线程,while循环也不会结束。

5.6.3 两阶段终止模式

Two Phase Termination

在一个线程T1中如何“优雅”终止线程T2;这里的【优雅】指的是给T2一个料理后事的机会。

错误思路

  • 使用线程对象的stop()方法停止线程,stop方法会真正杀死线程。如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁。
  • 使用System.exit(int)方法停止线程。目的仅是停止一个线程,但这种做法会让整个程序都停止。

正确思路
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第12张图片

@Slf4j(topic = "test13")
public class Test13 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
        twoPhaseTermination.start();

        Thread.sleep(3500);
        twoPhaseTermination.stop();
    }
}
@Slf4j(topic = "twoPhaseTermination")
class TwoPhaseTermination {
    private Thread monitor;

    // 启动监控线程
    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread currentThread = Thread.currentThread();
                if (currentThread.isInterrupted()) {
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);  // 情况1:被打断时,打断标记为加,抛出InterruptedException异常
                    log.debug("执行监控记录");  // 情况2:被打断时,打断标记置为真
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 重新设置打断标记
                    currentThread.interrupt();
                }
            }
        });
        monitor.start();
    }

    // 停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

运行结果

11:16:51.748 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
11:16:52.750 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
11:16:53.750 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Chapter3.TwoPhaseTermination.lambda$start$0(Test13.java:39)
	at java.lang.Thread.run(Thread.java:748)
11:16:54.247 [Thread-0] DEBUG twoPhaseTermination - 料理后事

注意:

  • 正常运行的线程被打断时,会设置打断标记(打断标记置为true)。
  • sleep、wait、join的线程被打断时,会抛出InterruptedException异常,会清除打断标记(打断标记置为false)。

5.6.4 打断park线程

@Slf4j
public class Test14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
    }
}

运行结果:

11:26:13.031 [t1] DEBUG Chapter3.Test14 - park...
11:26:14.030 [t1] DEBUG Chapter3.Test14 - unpark...
11:26:14.030 [t1] DEBUG Chapter3.Test14 - 打断状态:true

如果不打断,将一直卡在LockSupport.park()这个位置。

六、主线程与守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

@Slf4j
public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        log.debug("start");
        Thread t1 = new Thread(() -> {
            log.debug("start");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("end");
        }, "daemon");
        t1.setDaemon(true);
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("end");
    }
}

将t1设置为守护线程,当主线程结束时,守护线程被强制结束。

运行结果:

12:30:25.900 [main] DEBUG Chapter3.Test15 - start
12:30:25.929 [daemon] DEBUG Chapter3.Test15 - start
12:30:26.930 [main] DEBUG Chapter3.Test15 - end

垃圾回收器线程就是一种守护线程
Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求

七、线程状态

7.1 五种状态

操作系统层描述,有五种状态
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第13张图片

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】 (就绪状态) 指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行
  • 【运行状态】指获取了CPU时间片运行中的状态。当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】
    • 等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与[可运行状态]的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考
      虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

7.2 六种状态

JAVA API层描述,根据Thread.State枚举,分为六种状态:
【并发编程JUC】Java线程、创建运行线程、线程运行原理、常见方法、线程状态_第14张图片

  • NEW:线程刚被创建,还没有调用start()方法
  • RUNNABLE:当调用了start() 方法之后,注意,Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】 (由于 BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行)
  • BLOCKEDWAITINGTIMED_WAITING 都是Java API层面对【阻塞状态】的细分,后面会在状态换节一详述
  • TERMINATED当线程代码运行结束

测试6种状态

@Slf4j
public class Test16 {
    public static void main(String[] args) {
        // NEW
        Thread t1 = new Thread(() -> {
            log.debug("running...");
        },"t1");

        // RUNNABLE
        Thread t2 = new Thread(() -> {
            while (true) {

            }
        },"t2");
        t2.start();
        
        // TERMINATED
        Thread t3 = new Thread(() -> {
            log.debug("running...");
        },"t3");
        t3.start();
        
        // TIMED_WAITING
        Thread t4 = new Thread(() -> {
            synchronized (Test16.class) {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t4");
        t4.start();
        
        // WAITING
        Thread t5 = new Thread(() -> {
            try {
                t4.join();
                log.debug("running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t5");
        t5.start();
        
        // BLOCKED
        Thread t6 = new Thread(() -> {
            synchronized (Test16.class) {
                log.debug("running");
            }
        },"t6");
        t6.start();

        log.debug("t1 state: {}", t1.getState());
        log.debug("t2 state: {}", t2.getState());
        log.debug("t3 state: {}", t3.getState());
        log.debug("t4 state: {}", t4.getState());
        log.debug("t5 state: {}", t5.getState());
        log.debug("t6 state: {}", t6.getState());
    }
}

NEW:线程没有start()
RUNNABLE:while循环,线程正在运行
TERMINATED:线程运行完毕
TIMED_WAITING:线程睡眠种sleep
WAITING:线程等待join
BLOCKED:线程阻塞

你可能感兴趣的:(并发编程JUC,Java,java,开发语言,后端)