什么是线程 维基百科 中给到的解释
线程:是操作系统能够进行运算调度的最小单位,大部分情况下,它被包含在进程
之中,是进程中的实际运作单位
线程区别于协程
。线程是抢占式
的,在单CPU单核的计算机上。一次性只能有一个线程处理任务,所谓的多线程,是多个线程相互抢占CPU处理自己的任务。
那为什么多线程能够提高运算能力? 在维基百科 这么形容
因为使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,从而提高了程序的执行效率
笔者理解,计算机处理一个任务。并不是一直不停的执行,而是在不停的调度,例如,在线程A空闲的时候,去执行线程B的任务,从而提高效率。
线程,进程,协程的关系 如上面解释什么是线程。线程是包含在进程
之中。而协程更像是线程中的线程
,协程相对于线程而言,更加轻量级。如果非要说他们之间的关系。笔者认为,就是没有关系。
协程本身就是一个很大的知识点。之后如果有时间,笔者会对协程进行知识总结。目前在看一本Kotlin协程
这一本书。协程使用很简单,而理解它还是有点难的。笔者对协程的了解不是太清楚。还停留在使用阶段。。
如何启动线程 启动线程的方式有两种。
实际使用中很少会继承Thread,只要有下面几个原因
JAVA是单继承。如果使用继承Thread的方式。就很大程度上限制了开发者去复用代码块。 Runnable接口可以多处复用。在JAVA中。接口没有单继承的限制,方便拓展和代码的复用 🌰举例说明
使用继承的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testCustomThread () { new MyThread().start(); } public static class MyThread extends Thread { @Override public void run () { super .run(); System.out.println(Thread.currentThread().getName() + " run..." ); } }
实现Runnable接口的方式,更容易做到拓展
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testRun () { Runnable runnable = new Runnable() { @Override public void run () { System.out.println(Thread.currentThread().getName() + " run..." ); } }; Thread t1 = new Thread(runnable,"t1" ); Thread t2 = new Thread(runnable,"t2" ); t1.start(); t2.start(); }
Thread类中的start()和run()方法有什么区别? 先来一个面试题,请问下面代码。执行的是在哪个线程?
1 2 3 4 5 6 7 8 9 10 @Test public void testRun () { Thread t = new Thread(new Runnable() { @Override public void run () { System.out.println(Thread.currentThread().getName() + " run..." ); } },"t1" ); t.run(); }
恭喜回答主线程的同学,回答对了,没错。运行结果如下。是在主线程。
其实比较好理解。run()
方法相当于你直接执行了代码。原本在哪个线程,还是在那个线程。等同于写了一个普通方法。而start()
则是告诉计算机。线程已经准备好了,随时等着CPU调度
,等CPU调度
选中了这个线程。那么才会执行run()
方法。此时在运行。结果很明显
线程有几种状态? 在Thread
源码中又分成了6种。 在维基百科 中又定义成了4种
其实这么回答都没错,只是选择的角度不同。所以认定的状态也不相同。
一般理解的5种状态 笔者认为下面的5种状态的描述,更容易能够认知到线程。
创建状态
执行了new Thread()
,创建线程,并且为其分配相应的内存空间和资源。此时并没有开始执行
就绪状态
执行了start()
方法。进入线程队列排队。等待CPU调度
。
运行状态
被CPU调度选中,获取处理器资源。执行run
方法的代码
阻塞状态
遇到人为挂起或者需要执行耗时的操作,让出CPU资源
暂停执行。
阻塞时,不在进入线程队列排队 阻塞消除以后,进入就绪状态 终止状态
即死亡状态,run()
任务执行完成,或者执行stop()
或destroy()
,线程结束。
源码中的6种状态 在Java线程的源码中分成了6种状态.先看一张比较经典的图
再来看一下源码中对状态的枚举定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
不难发现。大体上和上面笔者认为比较好理解的5种状态差不多。只是多了等待
和计时等待
。下来一起分析一下每个状态的含义。以及实例。
NEW
其中NEW
可以理解成new Thread()
,分配了内存和资源对应上面理解的创建状态
,比较好理解
RUNNABLE
RUNNABLE
状态可理解成上面的就绪状态
和运行状态
。执行了start()
方法。已经处于线程队列排队,并且可能已经被CPU调度
选中,进入运行状态
,在JVM层面统称为RUNNABLE
可运行状态。
TERMINATED
对应的是上面的终止状态
🌰举例说明
下面代码就很好的演示了,线程t1
的状态,刚开始的创建new Thread()
状态是NEW
,等待1S以后执行start()
,t1
的状态变成了RUNNABLE
,执行完成以后等待1S,在看一下t1
的状态就变成了TERMINATED
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testNew () throws InterruptedException { Thread thread = new Thread(() -> { System.out.println(Thread.currentThread().getName() + " run..." ); }, "t1" ); System.out.println("线程name is " + thread.getName() + "; status is " + thread.getState().name()); Thread.sleep(1000 ); thread.start(); System.out.println("线程name is " + thread.getName() + "; status is " + thread.getState().name()); Thread.sleep(1000 ); System.out.println("线程name is " + thread.getName() + "; status is " + thread.getState().name()); }
最终执行的结果为
1 2 3 4 线程name is t1; status is NEW 线程name is t1; status is RUNNABLE t1 run... 线程name is t1; status is TERMINATED
BLOCKED
阻塞状态BLOCKED
,被synchronized
块阻塞,例如在多线程中。线程A获取锁进入同步块,在其出来之前,如果线程B想进入,就会因为获取不到锁而阻塞在同步块之外,这时线程B的状态就是BLOCKED
🌰举例说明
下面代码有两个线程t1
和t2
,线程t1
先执行。执行任务5S,并且持有block
对象的锁
,等待1S,确保线程t1
顺利执行。1S后,线程t2
准备执行任务,却发现没有锁
,此时的锁还在线程t1
手上。并没释放。那么线程t2
的状态就是BLOCKED
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 @Test public void testBlocked () throws InterruptedException { Object block = new Object(); Thread thread1 = new Thread(() -> { synchronized (block){ System.out.println(Thread.currentThread().getName() + " run..." ); try { sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1" ); thread1.start(); Thread.sleep(1000 ); Thread thread2 = new Thread(() -> { synchronized (block){ System.out.println(Thread.currentThread().getName() + " run..." ); } }, "t2" ); thread2.start(); System.out.println("线程name is " + thread2.getName() + "; status is " + thread2.getState().name()); }
执行结果为
1 2 t1 run... 线程name is t2; status is BLOCKED
WAITING
所谓的等待,是未满足特定条件下,线程执行了wait()
操作,例如线程A需要满足存款大于200元
才执行其他操作,如果未能满足存款大于200元
的特定条件。执行了wait()
操作,此时线程A进入WAITING
等待状态,等待其他线程发出notify
或者notifyAll
的通知。线程A收到通知以后,会重新进入RUNNABLE
状态。
🌰举例说明
下面代码线程t1
先执行。发现存款少于200,执行了wait()
,此时线程t
的状态就是WAITING
。线程t2
给设置到了300元。并且触发了notify()
,线程t1
被重新调度以后,顺利执行。
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 34 35 @Test public void testWaitIng () throws InterruptedException { AtomicInteger data = new AtomicInteger(100 ); Thread thread1 = new Thread(() -> { synchronized (data){ while (data.get() <=200 ){ try { System.out.println(Thread.currentThread().getName() + " wait..." ); data.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " run..." ); } }, "t1" ); thread1.start(); Thread.sleep(1000 ); System.out.println("线程name is " + thread1.getName() + "; status is " + thread1.getState().name()); Thread thread2 = new Thread(() -> { synchronized (data){ data.set(300 ); System.out.println(Thread.currentThread().getName() + " notify..." ); data.notifyAll(); } }, "t2" ); thread2.start(); }
运行结果
1 2 3 4 t1 wait... 线程name is t1; status is WAITING t2 notify... t1 run...
TIMED_WAITING
和WAITING
相似。区别在于使用wait(time)
,即超时时间多久。wait()
则是无限等待。这里就不在写实例了,读者可以修改上面的wait
方法即可。
join()有什么作用 在join
方法的注释上这么描述
Waits for this thread to die.
翻译过来就是等待线程死亡。意思是。只有等这个线程结束以后才会执行下面的代码。
🌰举例说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testJoin () throws InterruptedException { Thread thread = new Thread(() -> { System.out.println(Thread.currentThread().getName() + " run..." ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " finish" ); }, "t1" ); thread.start(); thread.join(); System.out.println(Thread.currentThread().getName() + " finish" ); }
未添加join运行结果
未添加join()
方法,程序都没有将t1 finish
打印出来。因为对于main线程
来说。他已经执行结束了。也不去关心t1线程
是否执行完了。
添加join 运行结果
1 2 3 t1 run... t1 finish main finish
添加join()
方法。main线程
只能等着。因为t1线程
还没执行完成。只有等t1线程
执行完了。main线程
才能继续执行。
sleep(),wait()的区别 sleep()是线程的方法。而wait()是Object的方法 sleep()和wait()都会释放cpu资源,而sleep()不会释放锁,wait()会释放锁 针对第二个差别,笔者这里可准备一个小的测试代码,请问下面代码 t1
和t2
的状态是什么。
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 34 @Test public void test () throws InterruptedException { Object obj = new Object(); Thread t1 = new Thread(() -> { synchronized (obj){ System.out.println(Thread.currentThread().getName() + " run..." ); try { Thread.sleep(4000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " finish" ); } }, "t1" ); t1.start(); Thread t2 = new Thread(() -> { synchronized (obj){ System.out.println(Thread.currentThread().getName() + " run..." ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " finish" ); } }, "t2" ); t2.start(); System.out.println(t1.getName() + " status:" + t1.getState().name()); System.out.println(t2.getName() + " status:" + t2.getState().name()); }
先看一下。笔者这里准备了两个线程,t1
线程先执行,并且获取了锁
,然后执行4S,这个时候会释放CPU资源。那么线程t2
准备执行。但是因为现在的锁
还在线程t1
手上。所以。线程t1
的状态应该是TIMED_WAITING
等待状态。而线程t2
因为获取不到锁而阻塞在同步块之外,其状态是BLOCKED
运行结果如下,你答对了嘛
1 2 3 t1 run... t1 status:TIMED_WAITING t2 status:BLOCKED
如果此时将Thread.sleep(time);
代码替换成obj.wait(time)
,请问 t1
和t2
的状态是什么。
读者可以想一下。本小节的区别sleep()不会释放锁,wait()会释放锁
,所以在t1
wait的时候,t2
开始执行。然后t1
和t2
都等待着被唤醒。可惜啊。没人唤醒他们。运行结果如下。读者答对没有呢
1 2 3 4 t1 run... t2 run... t1 status:TIMED_WAITING t2 status:TIMED_WAITING
yield()作用 源码中注释这么标注
A hint to the scheduler that the current thread is willing to yield
表示当前线程愿意让步。其本意是说,放弃cpu的资源。主动回归到等待队列中。让cpu进行调度。但是回归了队列,意味着依然有可能被重新选中。
🌰举例说明
准备了两个线程。t0
和t1
,让他们打印0到4,并且在打印到第2个以后,yield()
一下。按照我们的理解,他应该有两种可能。
回归等待队列以后。又被重新选中了。prepare yield
之后。继续执行当前线程的任务 回归等待队列以后。没有重新,让步给了其他线程。prepare yield
之后。执行其他线程的任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testYield () throws InterruptedException { Runnable runnable = () -> { for (int i = 0 ; i < 5 ; i++) { System.out.println(Thread.currentThread().getName() + " run " + i); if (i == 2 ) { System.out.println(Thread.currentThread().getName() + " prepare yield" ); Thread.yield(); } } }; for (int i = 0 ; i < 2 ; i++) { Thread thread = new Thread(runnable, "t" + i); thread.start(); } }
死锁是什么 百度百科 中解释
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
🌰举例说明
注意,之前的测试都是在测试代码中运行。这里是运行在main方法
中,部分新的gralde版本在运行main方法会抛出错误。main not found
,只需要在项目目录下的.idea/gradle.xml
中添加<option name="delegatedBuild" value="false" />
即可。
下面代码运行就发生死锁。线程t1
锁住了obj1
,线程t2
锁住了obj2
,就导致了死锁。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public static void main (String[] args) { testDeadlock(); } public static void testDeadlock () { Object obj1 = new Object(); Object obj2 = new Object(); Thread thread1 = new Thread(() -> { synchronized (obj1) { System.out.println(Thread.currentThread().getName() + " 锁住obj1" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2){ System.out.println(Thread.currentThread().getName() + " 进不来的" ); try { Thread.sleep(1000 ) ; } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1" ); Thread thread2 = new Thread(() -> { synchronized (obj2) { System.out.println(Thread.currentThread().getName() + " 锁住obj2" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1){ System.out.println(Thread.currentThread().getName() + " 进不来的" ); try { Thread.sleep(1000 ) ; } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t2" ); thread1.start(); thread2.start(); }
运行结果如下。注意看红框表示线程还在运行。并没有结束。
如何终止线程 正常退出
比较好理解。run()
内的方法全部执行完成。线程正常结束。并且推出。就像在上面举例说明NEW
–>RUNNABLE
—>TERMINATED
的过程。就是正常的退出
stop/destroy
Thread提供了 stop
和destroy
的方法用于退出程序。本质上他们都是抛出异常终止线程,可以看一下他们的源码。这种属于强行使用异常中断,并且方法被标记为废弃Deprecated
.不建议使用
1 2 3 4 @Deprecated public void destroy () { throw new UnsupportedOperationException(); }
1 2 3 4 @Deprecated public final void stop () { throw new UnsupportedOperationException(); }
interrupt
本质上是通过标记。判断状态。会抛出java.lang.InterruptedException: sleep interrupted
的异常。标记可以通过isInterrupted
判断。下面演示一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testInterrupt () throws InterruptedException { Thread thread = new Thread(() -> { if (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + " run..." ); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " finish" ); } else { System.out.println(Thread.currentThread().getName() + " interrupt" ); } }, "t1" ); thread.start(); Thread.sleep(1000 ); thread.interrupt(); }
运行结果
1 2 3 4 5 6 t1 run... java.lang.InterruptedException: sleep interrupted at java.base/java.lang.Thread.sleep(Native Method) at com.allens.sample_thread.MainTest.lambda$testInterrupt$7(MainTest.java:125) at java.base/java.lang.Thread.run(Thread.java:834) t1 finish
参考