线程介绍
什么是线程
现代操作系统运行一个程序,就会为其创建一个进程,(为什么程序不直接操作进程而需要操作线程呢,主要在于进程如果阻塞挂起,那么整个进程操作的程序都会暂停,不如线程细粒度高。进程一个时间只能干一件事),在一个进程里面可以创建多个线程,每个线程都有自己的堆、栈、程序计数器等属性,还可以访问共享内存。cpu切换不同的线程,让不同线程感觉可以同时执行。
譬如我们启动一下tomcat,操作系统就会为java分配一个进程。
jps查看tomcat启动线程的pid:
java进程同时也会使用线程来执行各种程序:
线程生命周期
- 线程的状态:
NEW :初始状态,线程被构建,但是没有调用start方法
RUNNABLE :运行状态
BLOCKED :阻塞状态
WAITING :等待状态,等待其他线程执行
TERMINATED :终止状态,线程已经执行完毕了 - 生命周期:
main线程
main方法的执行,会构建main线程、启动main线程来执行
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
Arrays.stream(threadInfos).forEach(threadInfo -> {
System.out.println("threadId = " + threadInfo.getThreadId() + " threadName = " + threadInfo.getThreadName());
});
}
Daemon线程
Daemon线程是一中守护线程,主要用于程序中后台调度和支持性工作。譬如jvm中垃圾回收线程就是守护线程(注意守护线程需要在用户线程前启动)。
public static void main(String[] args) {
Thread workerThread = new Thread(() -> {
// 建立远程连接
System.out.println(Thread.currentThread().getName() + " create connect");
// 开启守护线程,ping测试连接状态
Thread doemonThread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() + " tcp ping test connect!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "doemonThread");
doemonThread.setDaemon(true);
doemonThread.start();
// 断开连接,守护线程也推出
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " exit connect");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "workerThread");
workerThread.start();
}
线程中断
线程中断就是给线程打一个中断属性,并不是真的停止线程,其他线程通过调用该线程的interrupt方法对其进行中断操作。
interrupt并非中断线程
调用interrupt()方法仅仅就是给当前线程打了一个中断标记,并不是真的停止线程。
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0 ; i < 500000; i++) {
System.out.println("i = " + (i+1));
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
mt.start();
mt.interrupt();
}
}
interrupted和isInterrupted区别
interrupted例子:
public static void main(String[] args) throws Exception {
Thread.currentThread().interrupt();
System.out.println("Thread.interrupted() " + Thread.interrupted());
System.out.println("Thread.interrupted() " + Thread.interrupted());
}
结果:
Thread.interrupted() true
Thread.interrupted() false
isInterrupted例子:
public static void main(String[] args) throws Exception {
Thread t = Thread.currentThread();
t.interrupt();
System.out.println("t.isInterrupted() " + t.isInterrupted());
System.out.println("t.isInterrupted() " + t.isInterrupted());
}
结果:
t.isInterrupted() true
t.isInterrupted() true
interrupted源码:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
说明:设置为true值状态,返回当前线程状态位,然后清除状态位。
isInterrupted源码:
/**
* Tests whether this thread has been interrupted. The interrupted
* status of the thread is unaffected by this method.
*
* A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return true
if this thread has been interrupted;
* false
otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
说明:设置为false值状态,返回当前线程状态位,不清除状态位。
interrupted例子:第一次打印结果为true,是返回中断状态位。然后清除状态位,所以第二次打印结果为false。
isInterrupted例子:第一次打印结果为true,是返回中断状态位。然后不清除状态位。所以第二次打印结果还为true。
中断异常InterruptedException
sleep/wait/notify方法抛出中断异常前,会清除中断状态位。
public class InterruptedCase {
public static void main(String[] args) throws Exception {
Thread sleepRunner = new Thread(new SleepRunner(), "SleepRunner");
sleepRunner.setDaemon(true);
Thread busyRunner = new Thread(new BusyRunner(), "BusyRunner");
busyRunner.setDaemon(true);
sleepRunner.start();
busyRunner.start();
TimeUnit.SECONDS.sleep(5);
sleepRunner.interrupt();
busyRunner.interrupt();
System.out.println("SleepRunner interrupted is " + sleepRunner.isInterrupted());
System.out.println("BusyRunner interrupted is " + busyRunner.isInterrupted());
TimeUnit.SECONDS.sleep(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
while(true) {
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while(true) {
}
}
}
}
结果:
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 com.sunpy.threadtest.test.InterruptedCase$SleepRunner.run(InterruptedCase.java:34)
at java.lang.Thread.run(Thread.java:748)
SleepRunner interrupted is false
BusyRunner interrupted is true
BusyRunner发生中断,中断状态位为true。
SleepRunner调用sleep方法,在此期间,发生中断,抛出中断异常,中断已经结束,状态位也被清除了,所以返回为false。
sleep方法的中断逻辑:
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* interrupted status of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
中断异常的英文@throws InterruptedException:
如果任一当前线程发生中断,那么当前线程的中断异常状态会被清除,然后抛出异常。(可见sleep方法抛出异常前会清除中断状态位)
线程关闭
安全关闭线程思路
利用中断操作来关闭线程,也可以利用标识位来关闭线程。
利用中断操作关闭线程
public class InterruptedCase {
public static void main(String[] args) throws Exception {
Thread closeRunner = new Thread(new CloseRunner(), "CloseRunner");
closeRunner.start();
TimeUnit.SECONDS.sleep(1);
closeRunner.interrupt();
}
static class CloseRunner implements Runnable {
private long i;
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("i value = " + i);
}
}
}
结果:
i value = 453049242
利用标识位来关闭线程
public class InterruptedCase {
public static void main(String[] args) throws Exception {
CloseRunner closeRunner = new CloseRunner();
Thread closeThread = new Thread(closeRunner, "CloseRunner");
closeThread.start();
TimeUnit.SECONDS.sleep(1);
closeRunner.cancelByFlag();
}
static class CloseRunner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while(on) {
i++;
}
System.out.println("i value = " + i);
}
public void cancelByFlag() {
on = false;
}
}
}
结果:
i value = 405669970
思考:为什么boolean标志位需要volatile修饰?
- CloseRunner线程从主内存读取标志位on为true,则继续循环。
- 当我们的main线程修改volatile修饰的标志位on为false时,将main线程的本地内存共享变量on刷新到主内存了。
- 这样当CloseRunner线程再从主内存读取标志位on时为false了,就结束了线程的循环。
线程等待/通知
wait/notify
wait()方法的作用就是将当前执行代码的线程进行等待,wait()方法时Object类的方法用来将当前线程置入 “预执行队列” 中,在wait()方法所在的代码行处停止执行,直到收到通知或者中断为止。
notify()方法的作用就是将多个等待线程,由线程规划期随机挑选出其中一个wait状态的线程,发出notify唤醒。
1. wait/notify方法调用前,需要先获取对象锁
public class Test {
public static void main(String[] args) {
try {
Object obj = new Object();
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
[Console output redirected to file:D:\console.txt]
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at cn.spy.thread.test.Test.main(Test.java:8)
2. 等待唤醒例子
public class MyThread1 extends Thread {
private Object lock;
public MyThread1(Object lock) {
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " start wait " + System.currentTimeMillis());
lock.wait();
System.out.println(Thread.currentThread().getName() + " end wait " + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread {
private Object lock;
public MyThread2(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " start notify " + System.currentTimeMillis());
lock.notify();
System.out.println(Thread.currentThread().getName() + " end notify " + System.currentTimeMillis());
}
}
}
public class Test {
public static void main(String[] args) {
try {
Object lock = new Object();
MyThread1 mt1 = new MyThread1(lock);
mt1.setName("MyThread1");
mt1.start();
Thread.sleep(5000);
MyThread2 mt2 = new MyThread2(lock);
mt2.setName("MyThread2");
mt2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
[Console output redirected to file:D:\console.txt]
MyThread1 start wait 1541050962125
MyThread2 start notify 1541050967127
MyThread2 end notify 1541050967127
MyThread1 end wait 1541050967127
说明:可以发现MyThread2在执行notify方法唤醒MyThread1,然后MyThread2没有马上释放线程,MyThread1没有马上获取线程。
① 等待通知的执行顺序是如果我们执行了notify();方法并不会马上就释放线程了,而那个wait状态的线程也不能马上获取该对象锁。得等到notify();方法的线程执行完(也就是退出了synchronized代码块后),线程才会释放锁,而呈wait(); 状态的线程才可以获取该对象锁。
② 当前如果有多个线程处于等待的状态,那么使用notify();方法,线程规划器只会随机的挑选出其中一个线程。
③ wait方法执行后,锁自动释放,但是执行完notify方法,锁不会自动释放。
总结
- wait/notify方法调用前,需要先获取对象锁
- notify唤醒多个等待线程,是随机挑选出的。
- wait方法执行后,锁自动释放,但是执行完notify方法,锁不会自动释放。
- notify唤醒的线程不会马上执行(因为notify方法的执行,锁不会自动释放)。
- 当线程呈wait();状态时,调用线程对象的interrupt();方法会报一个InterruptedException异常。
wait扩展(join)
join的作用:
join可以让线程排队执行,而join的内部采用wait方法来实现等待。而join的作用是等待线程对象销毁。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "run end");
}
}
public class JoinTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setName("MyThread");
mt.start();
try {
mt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " execute");
}
}
说明:默认join方法等待0秒,所以t1线程在睡眠5秒之后,wait(0),相当于没等待,所以main execute最后执行。
join(long time)设置等待时间
public class JoinTest {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.printf("%s start %d\n", Thread.currentThread().getName(), System.currentTimeMillis());
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s end %d\n", Thread.currentThread().getName(), System.currentTimeMillis());
}
});
t1.setName("t1");
t1.start();
try {
t1.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s start %d\n", Thread.currentThread().getName(), System.currentTimeMillis());
}
}
结果:
t1 start 1541408642457
main start 1541408644457
t1 end 1541408648500
说明:可以发现在join设置的时间到时之后,将会让join的线程等待,让其他的线程执行。也可以看到join到时间将释放锁。