主页 > 软件开发  > 

JUC并发编程——Java线程(一)

JUC并发编程——Java线程(一)

文章目录 1. 线程的创建1.1 方法1: 直接使用Thread1.2 方法2:使用Runnable配合Thread1.3 方法3:FutureTask配合Thread 2. 线程运行2.1 原理2.2 常见方法2.2.1 start与run2.2.2 sleep与yield2.2.3 join2.2.4 interrupt 3. 主线程和守护线程4. 线程状态4.1 五种状态4.2 六种状态


1. 线程的创建 1.1 方法1: 直接使用Thread Thread t = new Thread(){ @Override public void run(){ // 要执行的任务 } }; // 启动线程 t.start(); 1.2 方法2:使用Runnable配合Thread

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

Thread代表线程Runnable可执行的任务(线程需要执行的代码) Runnable runnable = new Runnable() { public void run(){ // 要执行的任务 } } // 创建线程对象 Thread t = new Thread( runnable ); // 启动线程 t.start();

java8以后可以使用lambda精简代码

Runnable r = () -> { log.debug("running"); }; Thread t = new Thread(r, "t2"); t.start();

小结:

方法1是把线程和任务合并在了一起,方法2是把线程和任务分开了。用Runnable更容易把线程池等高级API配合。用Runnable让任务类脱离了Thread继承体系,更灵活。 1.3 方法3:FutureTask配合Thread

FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况。

FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>(){ @Override public Integer call() throws Exception { log.debug("running"); Thread.sleep(1000); return 100; } }); Thread t = new Thread(task); t.start(); log.debug("{}", task.get()); 2. 线程运行 2.1 原理

栈与栈帧 JVM由堆、栈、方法区所组成,其中栈内存给线程使用,每个线程启动后哦,虚拟机就会为其分配一块栈内存。

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

线程上下文切换(Thread Context Switch) 因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码

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

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

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

多线程

2.2 常见方法 2.2.1 start与run 启动线程必须要调用start(),不然对性能没影响。start()不能多次调用 public class Test4{ public static void main(String[] args){ Thread t1 = new Thread("t1") { @Override public void run() { log.debug("running...."); } }; // t1.run(); t1.start(); } } 2.2.2 sleep与yield

sleep

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

yield

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

线程优先级

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

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

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

等待线程运行结束

t1.start(); t1.join();

应用之同步(案例1) 以调用方角度来讲,如果

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

打断sleep,wait,join的线程 eg:打断sleep的线程,会清空打断状态。

private static void test1() throws InterruptedException { Thread t1 = new Thread(() ->{ sleep(1); }, "t1"); t1.start(); sleep(0.5); t1.interrupt(); log.debug("打断状态:{}", t1.isInterrupted()); }

eg:打断正常运行的线程,不会清空打断状态。

两阶段终止模式 在一个线程T1中如何“优雅”终止T2,即给T2一个善后的机会。

错误思路如下:

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

public class Test3{ public static void main(String[] args){ TwoPhaseTermination tpt = new TwoPhaseTermination(); tpt.start(); Thread.sleep(3500); tpt.stop(); } } class TwoPhaseTermination{ private Thread monitor; // 启动监控线程 public void start(){ monitor = new Thread(() -> { while(true){ Thread current = Thread.currentThread(); if(current.isInterrupted()){ log.debug("料理后事"); break; } try{ Thread.sleep(1000); // 情况1 log.debug("执行监控记录"); // 情况2 } catch(InterruptedExcetion e){ e.printStackTrace(); // 重新设置打断标记 current.interrupt(); } } }); } // 停止监控线程 public void stop(){ monitor.intterupt(); } }

打断park线程,不会清空打断状态。 如果打断标记已经是true,则park会失效。

不推荐的方法:

stop() 停止线程运行suspend() 挂起(暂停)线程运行resume() 恢复线程运行 3. 主线程和守护线程

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

log.debug("开始运行..."); Thread t1 = new Thread(() -> { log.debug("开始运行..."); sleep(2); log.debug("运行结束..."); }, "daemon"); t1.setDaemon(true); t1.start(); sleep(1); log.debug("运行结束..."); 4. 线程状态 4.1 五种状态

这是从操作系统层面来描述的

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

这是从Java API层面来描述的。 根据Thread.State 枚举,分为六种状态。

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

JUC并发编程——Java线程(一)由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“JUC并发编程——Java线程(一)