当JVM启动时,它将为JVM的内部任务如垃圾收集等创建后台线程,并创建主线程来运行main方法。

那勾勾就先用main线程来创建一个线程。

初始Thread

publicstaticvoidmain(String[]args){ Threadthread=newThread(()->{ Thread currentThread = Thread.currentThread(); System.out.println("线程的名字为:" currentThread.getName()); System.out.println("线程的优先级为:" currentThread.getPriority()); System.out.println("线程的分组为:" currentThread.getThreadGroup()); System.out.println("线程的状态为:" currentThread.getState()); System.out.println("线程的ID为:" currentThread.getId()); System.out.println("do something....."); }); thread.start(); try{ TimeUnit.SECONDS.sleep(1); }catch(InterruptedExceptione){ e.printStackTrace(); } Thread currentThread = Thread.currentThread(); System.out.println("main线程的名字为:" currentThread.getName()); System.out.println("main线程的优先级为:" currentThread.getPriority()); System.out.println("main线程的分组为:" currentThread.getThreadGroup()); System.out.println("main线程的ID为:" currentThread.getId()); }

代码要自己动手敲,结果自己运行查看,知识才是自己的。

Thread类原理分析

在java.lang包下jdk提供了线程类Thread,先找到它看它的架构图。

多线程基础知识:多线程基础知识(1)

Thread实现了接口,而且构造的时候还可以传入Runnable。那就看Runnable接口做了啥:

@FunctionalInterface publicinterfaceRunnable{ public abstract void run(); }

Runnable接口提供一个抽象的run方法,而且官网的解释翻译过来大概是这样的:一个对象实现了Runnable接口就可以被用来作为线程使用,run方法可以做你任何想做的事情,它是线程运行时的执行单元。

由此可以知道Thread类实现了Runnable可以当作线程来用了,Thread重写的run方法源码如下:

@Override public void run() { if (target != null) { target.run(); } }

这个重写是一个很巧妙的设计,如果传入的Runnable不为空,就运行run方法,为空等于你啥都不想运行,那就啥也不做了。

Thread类虽然实现了Runnable,但是它对run方法并没干啥,需要具体的实例去写入run方法,很巧妙的运用了模板设计方法。

不管我们是通过继承Thread类还是实现Runnable接口的方法来重写run方法,都是为了能给Thread传入一个真正想运行的执行逻辑。

在很长的一段时间勾勾都以为实现Runnbale接口是可以创建线程的,现在明白创建线程的方式只有构建Thread实例,而实现线程的执行单元则有两种方式:重写Thread的run方法和实现Runnable接口实现run方法。

那是不是代表我写了run方法,线程就创建好了呢?想啥呢?

run本身只是一段代码逻辑,没有线程含义,new Thread也只是创建一个对象,也是没有线程含义的,真正赋予thread对象线程含义的方法是start。我们通过源码看一下start方法做了什么:

privatevolatileintthreadStatus=0; publicsynchronizedvoidstart(){ // thread处于new的状态时threadStatus为0,启动后这个状态就不是0了 // 也就是说一个线程不能启动多次,只能启动一次 if (threadStatus != 0) thrownewIllegalThreadStateException(); group.add(this); boolean started = false; try { // start方法主要的逻辑是start0方法,这个是一个native方法 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } }catch(Throwableignore){ } } }

勾勾在第一次看完这个方法时那是一脸懵呀,start你干啥了,后来终于凭借聪明才智找到了官方对start方法的解释:

Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.

翻译成中国话就是你调用start方法启动线程的时候,是JVM虚拟机帮你执行的run方法,你什么都不用管,只管告诉我run方法你要做什么,全部交给JVM搞定。

线程的生命周期

每个线程都有自己的局部变量表、程序计数器,以及生命周期等。

线程的生命周期就是线程什么时候生,什么时候死,跟我们人类没有啥区别。

多线程基础知识:多线程基础知识(2)

线程的生命周期可以分为5个阶段:

NEW:新建状态。当我们用关键字new创建一个Thread对象时,此时它并不处于执行状态,这就跟我们newString一个意思,在未调用start方法之前该线程根本不存在。因此在没有调用start方法时,线程的状态为NEW,这是一个很短暂的状态。

RUNNABLE:就绪状态。一个new状态的线程进入RUNNABLE只能通过调用start方法,此时线程才真正的存在,等待CPU的调度。RUNNABLE的线程只能意外地终止或者进入RUNNING状态。

RUNNING:运行状态。一旦CPU通过轮询或者其他方式从任务可执行队列中选中了线程,此时它真正地开始执行自己的逻辑代码。

处于该状态的线程可以发生如下的状态切换。

BLOCKED:阻塞状态。BLOCKED状态要么直接进入TERMINATED终止状态,要么阻塞结束进入RUNNABLE状态,也即是只有RUNNABLE状态能直接进入RUNNING状态。

TERMINATED:终止状态。代表着线程的真正结束,该状态不会切换到任何其他状态。

进入TERMINATED状态的情况:

勾勾之前潜伏在一个群里一直没说过话,直到有一天一个小朋友问怎么结束线程,场景就是我运行到了一个地方这个线程就不要了,他想显示地去杀掉这个线程,这个问题把潜伏已久的勾勾炸出来了,我当时只要他再从头背一下线程的状态,理解一下生命周期,你只要啥都不干了线程会自动结束的。

线程的API

线程类提供了很多方法,可以改变线程的名字,优先级,是否守护,分组等,勾勾不需多介绍,只需记住一点,线程任何属性的改变都必须在线程启动之前。

勾勾重点学习了改变线程状态的方法,这在以后的源码阅读和工作中都是比较重要的知识。

sleep():当前运行的线程调用sleep()方法进入指定毫秒数的休眠,暂停执行,虽然可以指定时间精确到纳秒,但是最终还是要以系统的定时器和调度器的精度为准。休眠是不会放弃monitor锁的持有权。

public static void main(String[] args) { new Thread(()->{ System.out.println("运行的线程为:" Thread.currentThread().getName()); try { //当前线程休眠500毫秒 TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } },"Thread-A").start(); }

TimeUnit枚举对sleep做了很好的封装,因此使用Thread.sleep()的地方都可以用TimeUnit来代替。

sleep方法是一个可中断的方法,需要捕获中断异常。

yield():当前运行的线程调用yield()方法会提醒调度器表示愿意让出CPU资源,当前线程会进入RUNNABLE状态,因为CPU下次的调度可能也是当前线程,这个方法用的比较少。

public class TestYield { public static void main(String[] args) { //创建并启动两个线程 IntStream.range(0, 2).mapToObj(TestYield::creatThread).forEach(Thread::start); } private static Thread creatThread(int num){ return new Thread(()->{ if (num == 0){ //第一个线程主动让出cpu,这只是一种建议,如果cpu比较空闲,会忽略 Thread.yield(); } System.out.println(num); }); } }

wait():当前运行的线程调用对象的wait()方法进入阻塞状态,等待其他线程唤醒或者等待超时自动唤醒。

notify():当前运行的线程调用notify()方法唤醒的等待队列中阻塞的线程,具体唤醒等待队列中的第一个线程还是任意一个没有明确的说明。

notifyAll():当前线程调用notifyAll()方法唤醒的等待队列中所有被阻塞的线程。

private static List<String> list = new ArrayList<>(10); private static Object object = new Object(); public static void main(String[] args) { Thread threadB = new Thread(()->{ while (true){ synchronized (object) { if (list.size() != 5) { try { //线程一直阻塞等待,直到其他其他线程的通知 object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程B收到通知,开始处理业务"); break; } } }); Thread threadA = new Thread(()->{ synchronized (object){ for (int i = 0; i < 10; i ) { list.add(String.valueOf(i)); System.out.println("线程A插入元素i=" i); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //list有5个元素时通知其他线程,只是唤醒其他线程,没有放弃锁 if ( i == 4){ object.notify(); } } } }); System.out.println("启动线程B"); threadB.start(); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("启动线程A"); threadA.start(); }

join():当前运行的线程调用其它线程的join()方法,使当前线程进入阻塞状态,直到join的线程运行结束或者等待超时唤醒。这个方法可以理解为A主动让B插队。

public class TestJoin { public static void main(String[] args) { //创建并启动2个线程 List<Thread> list = IntStream.range(1,3).mapToObj(TestJoin::creatThread).collect(Collectors.toList()); list.forEach(Thread::start); for (Thread thread : list){ try { //main线程调用两个线程的yield方法,即两个线程插队在main之前执行 //可以尝试注销此方法看结果即明白 thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 1; i <=10 ; i ) { System.out.println(Thread.currentThread().getName() "&&" i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } private static Thread creatThread(int seq){ return new Thread(()->{ for (int i = 1; i <=10 ; i ) { System.out.println(Thread.currentThread().getName() "&&" i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }

interrupt():当前运行的线程调用被阻塞线程的interrupt()方法会打断线程的阻塞,打断一个线程并不等于线程生命周期的结束,仅仅是打断了当前线程的阻塞状态。wait/sleep/join方法都可以通过interrupt方法打断阻塞,因此都称为可中断方法。一旦线程在阻塞的情况下被打断都会抛出InterruptedException的异常,这个异常就是一个信号通知当前线程被打断了。

勾勾在学习的时候对这里误解了好久,认为中断时抛出异常是不能中断的,因为在平时开发中觉着抛出异常是错误的表现,但是阻塞方法抛出的中断异常是一个信号,代表线程可以中断。

public static void main(String[] args) { Thread thread = new Thread(()->{ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { System.out.println("线程被中断了"); } }); thread.start(); shortSleep(1); //false,此时没有断 System.out.println("线程中断状态:" thread.isInterrupted()); //执行打断方法 thread.interrupt(); //短暂休眠,如果不休眠,结果会不一致,可以测试一下 shortSleep(1); //false,sleep方法可中断,捕获异常后,恢复了interrupt标识位 System.out.println("线程中断状态:" thread.isInterrupted()); } private static void shortSleep(long time){ try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } }

interrupt方法平时开发中用的比较少,但是在源码中用的特别多,线程进入阻塞状态就存在一定的可能性进入永远阻塞,而中断方法是解决这个问题的最好方案。这是一个很重要的API,在后面的学习中,勾勾还会重点理解。

今天就学到这里了!收工!

这是勾勾最早开始写的第一篇文章,同步了 !

,