2020-04-08,2020-04-09整理
线程创建
一个java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上java程序天生就是一个多线程程序,包含了
1 | 1. 分发处理发送给给JVM信号的线程 |
- 创建多线程的三种方式
需要注意的是:
1.由于java不能多继承可以实现多个接口,因此,在创建线程的时候尽量多考虑采用实现接口的形式;
2.实现callable接口,通过submit提交给ExecutorService返回的是异步执行的结果,
通常也可以利用FutureTask(Callable callable)将callable进行包装,然后FutureTask提交给ExecutorsService
可参考一千万个数高效求和
线程状态
线程的状态,在Thread类中枚举类State中的定义,下面是原文翻译
1 | 1. NEW |
关于调度问题:
1 | 1. RUNNABLE状态的线程随时可能被CPU调度执行; |
- 线程创建之后调用start()方法开始运行
- 调用wait(),join(),LockSupport.lock()进入
等待状态
- 调用wait(long timeout),sleep(long),join(long),LockSupport.parkNanos(),LockSupport.parkUtil()进入
超时等待状态
- 当
超时等待状态
时间到达后,线程会切换到运行状态(就绪状态)
- 另外
等待状态
和超时等待状态
时可以通过Object.notify(),Object.notifyAll()方法使线程转换到运行状态(就绪状态)
- 当线程出现资源竞争时,即等待获取锁的时候,线程会进入到
阻塞状态
- 当线程获取锁时,线程进入到
运行状态(就绪状态)
- 线程运行结束后,线程进入到
终止状态
- 状态转换可以说是线程的生命周期
- 另外需要注意的是
- 当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是
阻塞状态
- 而使用java.util.concurrent.locks下lock进行加锁的时候线程切换的是
等待
或者超时等待
状态,因为lock会调用LockSupport的方法。
- 当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是
用一个表格将上面六种状态进行一个总结归纳。
线程操作
除了新建一个线程外,线程在生命周期内还有需要基本操作,而这些操作会成为线程间一种通信方式
例如使用中断(interrupt)方式通知实现线程间的交互等等,下面就将具体说说这些操作。
run和start
start会开启一个新线程,run不会
1 | 对象.start(); 会先执行后面的内容,再执行对象中的run方法 |
interrupt
- 一个案例
join
一个小案例来分析源码
下面再用一个例子来说明join方法的控制线程执行顺序
sleep
可以写在同步代码块中,也可以写在非同步代码块中。
wait和notify
sleep和wait
相同点
1 | 1. 都是把运行机会交给其它线程。 |
不同点
1 | 1. 所在类不同 |
yield
1 | public static native void yield();这是一个静态方法,一旦执行,它会是当前线程让出CPU |
sleep和yield
相同点
都是当前线程会交出处理器资源不同点
sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。
而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。
守护线程
守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾回收线程,JIT线程就可以理解守护线程。
与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它会完成整个系统的业务操作。
用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退。
当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。
- Daemon线程的示例上面的例子中daemodThread run方法中是一个while死循环,会一直打印,但是当main线程结束后守护线程daemonThread就会退出所以不会出现死循环的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class DaemonDemo {
public static void main(String[] args) {
Thread daemonThread = new Thread(new Runnable() {
public void run() {
while (true) {
try {
System.out.println("i am alive");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("finally block");
}
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
try {
//确保main线程结束前能给daemonThread能够分到时间片
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
输出结果
i am alive
finally block
i am alive
*/
main线程先睡眠800ms保证daemonThread能够拥有一次时间片的机会,也就是说可以正常执行一次打印“i am alive”操作和一次finally块中”finally block”操作。
紧接着main 线程结束后,daemonThread退出,这个时候只打印了”i am alive”并没有打印finnal块中的。
因此,这里需要注意的是守护线程在退出的时候并不会执行finnaly块中的代码,所以将释放资源等操作不要放在finnaly块中执行,这种操作是不安全的
线程可以通过setDaemon(true)的方法将线程设置为守护线程。
并且需要注意的是设置守护线程要先于start()方法,否则会报如下异常,但是该线程还是会执行,只不过会当做正常的用户线程执行。
1 | Exception in thread "main" java.lang.IllegalThreadStateException |