书接上文。上文主要讲了下线程的基本概念,三种建立线程的方式与区别,还介绍了线程的状态,线程通知和等待,join等,本篇继续介绍并发编程的基础知识。java
当一个执行的线程调用了Thread的sleep方法,调用线程会暂时让出指定时间的执行权,在这期间不参与CPU的调度,不占用CPU,可是不会释放该线程锁持有的监视器锁。指定的时间到了后,该线程会回到就绪的状态,再次等待分配CPU资源,而后再次执行。程序员
咱们有时会看到sleep(1),甚至还有sleep(0)这种写法,确定会以为很是奇怪,特别是sleep(0),睡0秒钟,有意义吗?实际上是有的,sleep(1),sleep(0)的意义就在于告诉操做系统马上触发一次CPU竞争。编程
让咱们来看看正在sleep的进程被中断了,会发生什么事情:并发
class MySleepTask implements Runnable{ @Override public void run() { System.out.println("MyTask1"); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { System.out.println("中断"); e.printStackTrace(); } System.out.println("MyTask2"); } } public class Sleep { public static void main(String[] args) { MySleepTask mySleepTask=new MySleepTask(); Thread thread=new Thread(mySleepTask); thread.start(); thread.interrupt(); } }
运行结果:ide
MyTask1 中断 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.codebear.MySleepTask.run(Sleep.java:10) at java.lang.Thread.run(Thread.java:748) MyTask2
咱们知道线程是以时间片的机制来占用CPU资源并运行的,正常状况下,一个线程只有把分配给本身的时间片用完以后,线程调度器才会进行下一轮的线程调度,当执行了Thread的yield后,就告诉操做系统“我不须要CPU了,你如今就能够进行下一轮的线程调度了 ”,可是操做系统能够忽略这个暗示,也有可能下一轮仍是把时间片分配给了这个线程。函数
咱们来写一个例子加深下印象:操作系统
class MyYieldTask implements Runnable { @Override public void run() { for (int i = 10; i > 0; i--) { System.out.println("我是" + Thread.currentThread().getName() + ",我分配到了时间片"); } } } public class MyYield { public static void main(String[] args) { Thread thread1 = new Thread(new MyYieldTask()); thread1.start(); Thread thread2 = new Thread(new MyYieldTask()); thread2.start(); } }
运行结果:
线程
固然因为线程的特性,因此每次运行结果可能都不太相同,可是当咱们运行屡次后,会发现绝大多数的时候,两个线程的打印都是比较平均的,我用完时间片了,你用,你用完了时间片了,我再用。code
当咱们调用yield后:对象
class MyYieldTask implements Runnable { @Override public void run() { for (int i = 10; i > 0; i--) { System.out.println("我是" + Thread.currentThread().getName() + ",我分配到了时间片"); Thread.yield(); } } } public class MyYield { public static void main(String[] args) { Thread thread1 = new Thread(new MyYieldTask()); thread1.start(); Thread thread2 = new Thread(new MyYieldTask()); thread2.start(); } }
运行结果:
固然在通常状况下,可能永远也不会用到yield,可是仍是要对这个方法有必定的了解。
当线程调用sleep后,会阻塞当前线程指定的时间,在这段时间内,线程调度器不会调用此线程,当指定的时间结束后,该线程的状态为“就绪”,等待分配CPU资源。
当线程调用yield后,不会阻塞当前线程,只是让出时间片,回到“就绪”的状态,等待分配CPU资源。
死锁是指多个线程在执行的过程当中,由于争夺资源而形成的相互等待的现象,并且没法打破这个“僵局”。
死锁的四个必要条件:
要想打破“死锁”僵局,只须要破坏以上四个条件中的任意一个,可是程序员能够干预的只有“请求并持有”,“环路等待”两个条件,其他两个条件是锁的特性,程序员是没法干预的。
聪明的你,必定看出来了,所谓“死锁”就是“悲观锁”形成的,相对于“死锁”,还有一个“活锁”,就是“乐观锁”形成的。
Java中的线程分为两类,分别为 用户线程和守护线程。在JVM启动时,会调用main函数,这个就是用户线程,JVM内部还会启动一些守护线程,好比垃圾回收线程。那么守护线程和用户线程到底有什么区别呢?当最后一个用户线程结束后,JVM就自动退出了,而无论当前是否有守护线程还在运行。
如何建立一个守护线程呢?
public class Daemon { public static void main(String[] args) { Thread thread = new Thread(() -> { }); thread.setDaemon(true); thread.start(); } }
只须要设置线程的daemon为true就能够。
下面来演示下用户线程与守护线程的区别:
public class Daemon { public static void main(String[] args) { Thread thread = new Thread(() -> { while (true){} }); thread.start(); } }
当咱们运行后,能够发现程序一直没有退出:
由于这是用户线程,只要有一个用户线程还没结束,程序就不会退出。
再来看看守护线程:
public class Daemon { public static void main(String[] args) { Thread thread = new Thread(() -> { while (true){} }); thread.setDaemon(true); thread.start(); } }
当咱们运行后,发现程序马上就中止了:
由于这是守护线程,当用户线程结束后,无论有没有守护线程还在运行,程序都会退出。
之因此把线程中断放在后面,是由于它是并发编程基础中最难以理解的一个,固然这也与不常用有关。如今就让咱们好好看看线程中断。
Thread提供了stop方法,用来中止当前线程,可是已经被标记为过时,应该用线程中断方法来代替stop方法。
中断线程。当线程A运行(非阻塞)时,线程B能够调用线程A的interrupt方法来设置线程A的中断标记为true,这里要特别注意,调用interrupt方法并不会真的去中断线程,只是设置了中断标记为true,线程A仍是活的好好的。若是线程A被阻塞了,好比调用了sleep、wait、join,线程A会在调用这些方法的地方抛出“InterruptedException”。
咱们来作个试验,证实下interrupt方法不会中断正在运行的线程:
class InterruptTask implements Runnable { @Override public void run() { CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList(); try { long start = System.currentTimeMillis(); for (int i = 0; i < 150000; i++) { copyOnWriteArrayList.add(i); } System.out.println("结束了,时间是" + (System.currentTimeMillis() - start)); System.out.println(Thread.currentThread().isInterrupted()); } catch (Exception ex) { ex.printStackTrace(); } } } public class InterruptTest { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new InterruptTask()); thread1.start(); thread1.interrupt(); } }
运行结果:
结束了,时间是7643 true
在子线程中,咱们经过一个循环往copyOnWriteArrayList里面添加数据来模拟一个耗时操做。这里要特别要注意,通常来讲,咱们模拟耗时操做都是用sleep方法,可是这里不能用sleep方法,由于调用sleep方法会让当前线程阻塞,而如今是要让线程处于运行的状态。咱们能够很清楚的看到,虽然子线程刚运行,就被interrupt了,可是却没有抛出任何异常,也没有让子线程终止,子线程仍是活的好好的,只是最后打印出的“中断标记”为true。
若是没有调用interrupt方法,中断标记为false:
class InterruptTask implements Runnable { @Override public void run() { CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList(); try { long start = System.currentTimeMillis(); for (int i = 0; i < 500; i++) { copyOnWriteArrayList.add(i); } System.out.println("结束了,时间是" + (System.currentTimeMillis() - start)); System.out.println(Thread.currentThread().isInterrupted()); } catch (Exception ex) { ex.printStackTrace(); } } } public class InterruptTest { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new InterruptTask()); thread1.start(); } }
运行结果:
结束了,时间是1 false
在介绍sleep,wait,join方法的时候,你们已经看到了,若是中断调用这些方法而被阻塞的线程会抛出异常,这里就再也不演示了,可是还有一点须要注意,当咱们catch住InterruptedException异常后,“中断标记”会被重置为false,咱们继续作实验:
class InterruptTask implements Runnable { @Override public void run() { CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList(); try { long start = System.currentTimeMillis(); TimeUnit.SECONDS.sleep(3); System.out.println("结束了,时间是" + (System.currentTimeMillis() - start)); } catch (Exception ex) { System.out.println(Thread.currentThread().isInterrupted()); ex.printStackTrace(); } } } public class InterruptTest { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new InterruptTask()); thread1.start(); thread1.interrupt(); } }
运行结果:
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 com.codebear.InterruptTask.run(InterruptTest.java:20) at java.lang.Thread.run(Thread.java:748)
能够很清楚的看到,“中断标记”被重置为false了。
还有一个问题,你们能够思考下,代码的本意是当前线程被中断后退出死循环,这段代码有问题吗?
Thread th = Thread.currentThread(); while(true) { if(th.isInterrupted()) { break; } try { Thread.sleep(100); }catch (InterruptedException e){ e.printStackTrace(); } }
本题来自 极客时间 王宝令 老师的 《Java并发编程实战》
代码是有问题的,由于catch住异常后,会把“中断标记”重置。若是正好在sleep的时候,线程被中断了,又重置了“中断标记”,那么下一次循环,检测中断标记为false,就没法退出死循环了。
这个方法在上面已经出现过了,就是 获取对象线程的“中断标记”。
获取当前线程的“中断标记”,若是发现当前线程被中断,会重置中断标记为false,该方法是static方法,经过Thread类直接调用。
并发编程基础到这里就结束了,能够看到内容仍是至关多的,虽然说是基础,可是每个知识点,若是要深究的话,均可以牵扯到“操做系统”,因此只有深刻到了“操做系统”,才能够说真的懂了,如今仍是仅仅停留在Java的层面,唉。