目录

  • 前言
  • 一、线程的生命周期(重点)
  • 二、实现多线程的多种方式
    • 2.1 继承Thread类并重写run方法
    • 2.2 实现Runnable接口并重写run方法
  • 补充:另外两种线程创建方式

前言

最近读完了《深入理解Java虚拟机》大部分理论章节,感觉对JVM内部执行豁然开朗,并且发现并发编程和虚拟机工作也密不可分,强推先读一读JVM,或者读我归纳的几篇JVM文章,现在再系统读一读多线程、并发这块的书籍,以前也学过多线程,不过没有系统看书,图书馆选了一本看目录还不错的《Java高并发编程详解:多线程与架构设计》汪文君 著。网上都推荐《实战Java高并发程序设计》葛一鸣著,我也找到了对应pdf版本,先看第一本,如果觉得不全,再看第二本,配合一起看,不过大多内容都大同小异,不多说,一起啃书吧!!!


后续补充:还是看《实战Java高并发程序设计》,第一本后面讲的确实不太好

一、线程的生命周期(重点)

记住五大基本状态转换过程:
NEW(创建状态)
RUNNABLE(可运行状态/就绪状态)
RUNNING(运行状态)
BLOCKED(阻塞状态)
TERMINATED(终止状态)

面试:一个线程创建到消亡的过程(考你对线程生命周期的理解)
首先,一个线程对象被new后,就创建出一个线程Thread对象,注意此时线程并没有运行或者启动,只是一个Thread对象而已,处于创建状态(NEW),通过调用start方法,线程对象才变为就绪状态(RUNNABLE)的就绪线程,注意start()只是让线程就绪,并没有执行,它在等待CPU调度,没有CPU调度它是不会运行的,等到CPU轮转调度时,线程分到CPU的时间片,然后就会真正运行,也就是运行态(RUNNING),要知道CPU是按照时间片轮转的方式调用线程的,这就涉及一些操作系统的知识,每个线程会分到CPU一定长度的时间片,时间片用完,就要让出CPU给其他线程执行,又切换变为RUNNABLE状态,等到再轮转到之前没执行完的线程,然后线程才会继续执行。运行态的线程还可以调用yield()方法让出CPU资源,和其他线程一样再等待CPU调度,存在让出后马上就又得到CPU调度情况。在RUNNING态的线程可以直接进入TERMINATED状态,生命周期就结束了,比如调用stop()方法,但官方不推荐使用。另外,运行态调用sleep()方法、wait()方法或join()方法可以使得线程进入阻塞状态,只不过称呼不一样,如果按五大基本状态划分,则阻塞状态又可细分为三种情况:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态,等待时间结束,或者用notify()和notifyAll()可以让线程回到就绪状态

2.同步阻塞 :线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞: 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或者超时.,或者I/O处理完毕时,线程重新转入就绪状态。

下面图捋一下整个创建到消亡过程,说明五个状态之间的转换(创建、就绪、运行、阻塞、终止)。

注:其中暂停线程的suspend()和恢复线程的resume()已经弃用,所以不讲。yield()方法是放弃当前CPU资源,但放弃后,一定几率马上又获得CPU时间片,可以理解为线程A让出CPU,然后和其他线程一起等CPU调度,属于让出后跟大家同一起跑线公平竞争一样。

注:有些可能会说6种状态,注意这里说基本状态,6种状态你可以认为是一种扩展,将阻塞状态中的等待导致阻塞部分再细分出来,细分成等待状态,不过综合查阅资料,这个5大基本状态承认度、规范度还是最高的。

二、实现多线程的多种方式

下面根据一个案例来讲这两种实现,并进行对比,当然创建多线程不止这两种,还可以用Callable或者线程池,但下面两种是最常见的,其中用Runnable接口最常用。

案例:银行排队叫号窗口4个,用户会被叫去每个窗口办理业务,假设最多一天受理50笔业务,我们来写这个程序

2.1 继承Thread类并重写run方法

public class Main {public static void main(String[] args) {//线程1(窗口1)new ServiceWindow("一号窗口").start();//线程2(窗口2)new ServiceWindow("二号窗口").start();//线程3(窗口3)new ServiceWindow("三号窗口").start();//线程4(窗口4)new ServiceWindow("四号窗口").start();}
}
//继承Thread
public class ServiceWindow extends Thread{private String name;//柜台名称private int count = 1;//叫号private static final int MAX = 50;//最多50笔业务ServiceWindow(){}ServiceWindow(String name){this.name = name;}//重写run方法,底层启动线程是调用底层的run方法执行的@Overridepublic void run(){while (count < MAX){System.out.println(name + " -> 当前叫号:" + (count++));}}
}

效果

为什么会出现这种重复叫同一个号的情况?因为我们创建了四个叫号线程,Thread实现方式,线程之间资源不共享,这就出现多线程并发访问的不安全问题,有可能存在覆盖情况,并且count++自增也不是原子操作,然后导致输出的值重复,出现“脏读”,可以看下我的这篇关于JVM内存区域的文章,原子操作可见该文章。

改进1:加static修饰

count加了staic修饰,那么就属于静态变量,静态变量和常量是会加载到方法区的,而方法区是线程共享的,所以count就被四个线程共享,就不会重复叫号,如下,没有重复的

简单补充上面JVM相关知识,JVM内存区域(运行时数据区)分5大块,堆、方法区、虚拟机栈、本地方法栈、程序计数器,其中方法区和堆是线程共享,虚拟机栈、本地方法栈、程序计数器是线程私有的。

但是static虽然能解决,做到了共享资源,不过static修饰的变量生命周期很长,如果有很多这种变量都用static修饰,那么方法区本来就不大,很容易满。所以我们需要再改进

改进2:用Runnable接口实现共享资源

2.2 实现Runnable接口并重写run方法

public class Main {public static void main(String[] args) {ServiceWindowRunnable task = new ServiceWindowRunnable();new Thread(task,"一号窗口").start();//这里给线程实现接口和取名字,Thread.currentThread().getName()可以获取名字new Thread(task,"二号窗口").start();new Thread(task,"三号窗口").start();new Thread(task,"四号窗口").start();}
}
public class ServiceWindowRunnable implements Runnable{private int count = 1;//叫号,不用static修饰private static final int MAX = 50;//最多50笔业务@Overridepublic void run() {while (count < MAX){System.out.println(Thread.currentThread().getName() + " -> 当前叫号:" + (count++));}}
}

四个线程共享Runnable接口的资源,这样就既能不重复叫,也不会像static修饰一样生命周期太长而让方法区满, 实现Runnable接口的ServiceWindowRunnable类会创建在堆内存,堆内存是最大的,并且有垃圾回收机制,所以不用担心满的问题。故以后需要共享都用Runnable实现,这也是相比用继承Thread,Runnable最常用的原因,能用Runnable就不用Thread

现在实现线程用lambda表达式,简单、快捷,给出一个简单案例给大家参考理解

public class Main{public static void main(String[] args) {//重写run方法,lanbda表达式写法()->{...}//@Overide public void run(){...}英文字全省略,只留()->{},一个表示带的参数,一个表示方法体new Thread(()->{for (int i = 0 ; i < 10 ; i++){System.out.print(i+" ");}},"一号窗口").start();}
}


补充:另外两种线程创建方式

1、FutureTask + Callable

public class test1 {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask futureTask = new FutureTask(new Callable() {@Overridepublic String call() throws Exception {TimeUnit.SECONDS.sleep(1);System.out.println("call方法执行了");return "call方法返回值";}});futureTask.run();System.out.println("获取返回值: " + futureTask.get());//get方法用于获取任务完成的返回值FutureTask futureTask1 = new FutureTask(new Callable() {@Overridepublic String call() throws Exception {TimeUnit.SECONDS.sleep(1);System.out.println("call方法执行了1");return "call方法返回值1";}});futureTask1.run();System.out.println("获取返回值1: " + futureTask1.get());}
}


这种方式相比Runnable来说,有两个不同:1、有返回值 2、会抛出异常

同时,futureTask先执行,那么其他的就会阻塞,如此处futureTask1就会阻塞,不管futureTask 中设置sleep多久,futureTask1都要等它执行完才会执行,大家可以自己运行看看。另外get方法可以获取到返回值

2、线程池中的Executors工具类
可以参考我这篇文章:线程池详解

线程生命周期与创建线程的多种方式相关推荐

  1. java 关闭守护线程_Java并发编程之线程生命周期、守护线程、优先级、关闭和join、sleep、yield、interrupt...

    Java并发编程中,其中一个难点是对线程生命周期的理解,和多种线程控制方法.线程沟通方法的灵活运用.这些方法和概念之间彼此联系紧密,共同构成了Java并发编程基石之一. Java线程的生命周期 Jav ...

  2. 13.7 线程生命周期状态图、线程常用的方法。

    package cn.chen.samplethread; import java.lang.*; /* 线程生命周期状态图.线程常用的方法.线程的生命周期:创建状态.等待就绪态.运行状态.阻塞状态. ...

  3. iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用(上)

    2017-07-08 remember17 Cocoa开发者社区 目的 本文主要是分享iOS多线程的相关内容,为了更系统的讲解,将分为以下7个方面来展开描述. 多线程的基本概念 线程的状态与生命周期 ...

  4. 线程生命周期以及线程创建的三种方式

    1. 线程生命周期 线程生命周期图 新建状态(New) 当线程对象创建后,即进入新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable) 当调用线程对象的sta ...

  5. 这么说线程生命周期是不是简单了点?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | 公众号「日拱一兵」 为什么要了解线程的生命周期 ...

  6. 【JAVA多线程学习笔记】(1)实现线程的方式 线程生命周期 操作线程的方法

    文章目录 两种方式实现线程 继承Thread类 模拟银行叫号的程序 Runnable接口 代码1:(与swing相结合创建gui程序) Thread类的⼏个常⽤⽅法 线程生命周期 操作线程的方法 代码 ...

  7. java线程基础巩固---线程生命周期以及start方法源码剖析

    上篇中介绍了如何启动一个线程,通过调用start()方法才能创建并使用新线程,并且这个start()是非阻塞的,调用之后立马就返回的,实际上它是线程生命周期环节中的一种,所以这里阐述一下线程的一个完整 ...

  8. java threadstatus_Thread之一:线程生命周期及六种状态

    一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程各重要知识点.掌握了上图中的各知识点,Java中的多线程也就基本上掌 ...

  9. [原创]java WEB学习笔记94:Hibernate学习之路---session 的管理,Session 对象的生命周期与本地线程绑定...

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  10. java resume过时方法_面试官没想到,一个 Java 线程生命周期,我可以扯半小时

    面试官:你不是精通 Java 并发吗?从基础的 Java 线程生命周期开始讲讲吧. 好的,面试官.吧啦啦啦... 如果要说 Java 线程的生命周期的话,那我觉得就要先说说操作系统的线程生命周期 因为 ...

最新文章

  1. Flask-uploads 简单使用
  2. 算法--------字符串中的第一个唯一字符(Java 版本)
  3. 【Android 界面效果10】Android中View,ViewGroup,Window之间的关系
  4. oracle ocp笔记(1)
  5. mysql索引空间配置_加入空间mysql索引
  6. ES6 WeakSet数据结构 与Set十分相似
  7. python3读取ini文件_python3配置文件ini读取方法
  8. c字符串中包含双引号_必须知道的C语言知识细节:单引号和双引号正确用法
  9. 【C++ STL学习之七】STL算法之find和find_if
  10. 坊间八卦 | 关于 Oracle 中国区裁员的是是非非
  11. python存储序列_python序列类型及一些操作
  12. C语言和设计模式(之模板模式)
  13. 无法在Web服务器上启动调试。您不具备调试此应用程序的权限,此项目的URL位于Internet区域。...
  14. 前端开发入门到实战:纯CSS实现数据上报和HTML验证
  15. UE接入过程(LTE和NR)
  16. 首席新媒体运营商学院黎想:裂变活动要避免40个坑!
  17. 解决JBX常见问题最权威的办法(来自borland)
  18. 【转载】Ununtu源
  19. Nacos的连接拒接丶解决方案
  20. 手机测试相关基础知识

热门文章

  1. 初识OFDM(七):OFDM中的信道估计
  2. python 等差素数列
  3. java屠龙_倚天屠龙之江湖神器
  4. 计算机还原取消,如何取消开机一键还原F11选项?
  5. TP-LINK-TL-WR703N刷Breed用Openwrt固件挂MP288打印机服务共享手机打印服务
  6. 程序员简历项目经验怎么写?
  7. C# Revit二次开发
  8. 学术答辩PPT模板分享
  9. qq悄悄话查看器-分析报告
  10. word解除限制编辑(亲测有效)