一个Java的线生
java线程的使用
1. Java多线程概述
下面我们看下Java的多线程
- 作者: 博学谷狂野架构师
- GitHub:GitHub地址 (有我精心准备的130本电子书PDF)
只分享干货、不吹水,让我们一起加油!😄
1.1 java天生就是多线程的
一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。
1.1.1 代码案例
执行下面的代码
package chapter01; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; public class ThreadDemo { /** * 打印出java中所有的线程 * @param args */ public static void main(String[] args) { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); for (ThreadInfo info : threadInfos) { System.out.println("[" + info.getThreadId() + "]" + info.getThreadName()); } System.out.println(Thread.activeCount()); } }
执行后我们会发现打印了如下的线程信息,说明Java本身就是多线程的
[6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的
[5] Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给JVM信号的线程
[3] Finalizer // 调用对象finalize方法的线程
[2] Reference Handler//清除Reference的线程
[1] main //main线程,用户程序入口
1.2 线程的生命周期
Thread类提供了六种状态
1.2.1 新建状态(NEW)
当线程对象对创建后,即进入了新建状态,如:
Thread thread1 = new MyThread();
1.2.2 运行状态(RUNNABLE)
Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
1.2.3 阻塞状态(BLOCKED)
处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态
1.2.4 等待状态(WAITING)
进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
1.2.5 超时等待(TIMED_WAITING)
该状态不同于WAITING,它可以在指定的时间后自行返回。
1.2.6 终止状态(TERMINATED)
线程执行完了或者因异常退出了run()方法,该线程结束生命周
2. 线程的创建方式
创建线程的方式有两种
2.1 继承Thread类
我们可以通过继承Thread类来使用Java的多线程
package chapter01.create; /** * 创建一个线程并运行 */ public class threadTest extends Thread { @Override public void run() { System.out.println("线程运行"); } public static void main(String[] args) { threadTest threadCreate = new threadTest(); threadCreate.start(); } }
2.2 实现 Runnable 接口
实现Runnable 接口并交给Thread进行运行
package chapter01.create; public class RunnableTest implements Runnable { @Override public void run() { System.out.println("线程运行"); } public static void main(String[] args) { RunnableTest runnableTest = new RunnableTest(); Thread thread = new Thread(runnableTest); thread.start(); } }
2.3 Thread和Runnable的区别
Thread才是Java里对线程的唯一抽象,Runnable只是对任务(业务逻辑)的抽象,Thread可以接受任意一个Runnable的实例并执行。
2.3.1 注意事项
有些面试官会说实现线程的方式有三种 Thread、Runnable 以及Callable,但是按照java源码中Thread类中的注释说的实现类的防止只有两种,我们可以看下啊Thread类的源码
3. 线程的终止方式
下面我们看下线程的终止方式有哪些
3.1 线程自然终止
要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
3.2 stop终止
暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop(),但是这些API是过期的,也就是不建议使用的 。
3.2.1 为什么不建议使用
不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。
同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下,正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
3.3 中断终止
推荐使用中断的方式来终止线程
安全的中止则是其他线程通过调用某个线程A的**interrupt()**方法对其进行中断操作,,中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求,因为java里的线程是协作式的,不是抢占式的,线程通过检查自身的中断标志位是否被置为true来进行响应。
线程通过方法**isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()**来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。
如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。
3.3.1 代码案例
package chapter01.stop; /** * 使用Runable的中断 */ public class ThreadInterrupted implements Runnable { private int i = 0; @Override public void run() { while (!Thread.currentThread().isInterrupted()) { i++; System.out.println("线程正在运行"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (i > 10) { Thread.currentThread().interrupt(); } System.out.println(Thread.currentThread().isInterrupted()); } } public static void main(String[] args) { new Thread(new ThreadInterrupted()).start(); } }
3.3.2 注意事项
不建议自定义一个取消标志位来中止线程的运行。
因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,状态位如果跨线程改变状态必须使用volatile来保证可见性。
- 一般的阻塞方法,如sleep等本身就支持中断的检查。
- 检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断
3.4 状态位终止
状态位就是用一个变量来标识线程的运行状态,如果需要停止了就就改变状态位的状态,但是状态位一定要使用 volatile 关键字,否在可能造成多线程状态下的不可见
3.4.1 代码案例
3.4.1.1 使用volatile
使用volatile可以正常中断线程
package chapter01.stop; public class ThreadFlag implements Runnable { protected long i = 0; private volatile boolean flag = false; @Override public void run() { while (!flag) { i++; if (i > 100000) { flag = true; } System.out.println(i); } } public static void main(String[] args) throws InterruptedException { ThreadFlag threadFlag = new ThreadFlag(); new Thread(threadFlag).start(); } }
3.4.1.2 不使用volatile
不使用volatile,会导致线程不能正常中断
package chapter01.stop; public class ThreadInvisible implements Runnable { protected long i = 0; /** * 不加volatile 会造成多线程的变量不可见,判断不会停止 */ public boolean flag = false; @Override public void run() { while (!flag) { i++; } } public static void main(String[] args) throws InterruptedException { ThreadInvisible threadFlag = new ThreadInvisible(); new Thread(threadFlag).start(); Thread.sleep(1000); threadFlag.flag = true; } }
3. run和start的区别
Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。
**start()**方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用,如果重复调用会抛出异常。
而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。
4. 其他的线程相关方法
下面我们看下线程的其他方法有哪些
4.1 sleep方法
使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁,也不释放占用的资源。
也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据,注意该方法要捕捉异常。
例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
4.1.1 代码案例
package chapter01.method; import util.ThreadUtils; import java.util.concurrent.TimeUnit; public class ThreadSleep { public static void main(String[] args) { sleep1(); sleep2(); } public static void sleep1() { Thread thread = new Thread(() -> { System.out.println("xxxxxxxxxxxxxxxxxx"); ThreadUtils.sleep(1, TimeUnit.SECONDS); }); //thread.setPriority(100); thread.start(); } public static void sleep2() { Thread thread = new Thread(() -> { System.out.println("xxxxxxxxxxxxxxxxxx"); ThreadUtils.sleep(1, TimeUnit.SECONDS); }); //thread.set thread.start(); } }
4.2 join方法
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行 。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B
注意: t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程
4.2.1 代码案例
package chapter01.method; import util.ThreadUtils; import java.util.concurrent.TimeUnit; public class ThreadJoin { public static void main(String[] args) { Thread thread1 = new Thread(()->{ for(int i=0;i<10;i++) { System.out.println("111111111111111"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } }); Thread thread2 = new Thread(()->{ try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0;i<10;i++){ System.out.println("2222222222222"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } }); thread1.start(); thread2.setDaemon(true); thread2.start(); } }
4.3 yield方法
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
yield()是将线程从运行状态变更为就绪状态,不会变为等待/睡眠/阻塞状态,注意:yeid方法是不释放资源的。
4.3.1 代码案例
package chapter01.method; import util.ThreadUtils; import java.util.concurrent.TimeUnit; public class ThreadYield { public static void main(String[] args) { Thread thread1 = new Thread(()->{ // Thread.yield(); System.out.println("1111111111"); ThreadUtils.sleep(1, TimeUnit.SECONDS); }); Thread thread2 = new Thread(()->{ System.out.println("22222222222"); ThreadUtils.sleep(1, TimeUnit.SECONDS); }); thread1.start(); thread2.start(); } }
5 线程的优先级
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10
在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占,在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定
5.1 代码案例
package chapter01.priority; import util.ThreadUtils; import java.util.concurrent.TimeUnit; /** * 执行的时候优先级越高 越容易执行到该线程 */ public class ThreadPriority { public static void main(String[] args) { Thread thread1 = new Thread(() -> { while (true) { System.out.println("1111111111111111"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } }); Thread thread2 = new Thread(() -> { while (true) { System.out.println("22222222222222222"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } }); thread1.setPriority(1); thread2.setPriority(7); thread1.start(); thread2.start(); } }
6. 守护线程
Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。
这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出,可以通过调用Thread.setDaemon(true)
将线程设置为Daemon线程,我们一般用不上,比如垃圾回收线程就是Daemon线程
Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑,也可以理解为等程序的所有的用户线程结束后,守护线程也将结束。
注意:守护线程必须在start之前设置,否则会报错。
6.1 代码案例
package chapter01.daemon; import util.ThreadUtils; import java.util.concurrent.TimeUnit; public class ThreadDaemon { public static void main(String[] args) { Thread thread1 = new Thread(() -> { while (true) { System.out.println("1111111111111"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println("22222222222"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } }); /* Thread thread3 = new Thread(() -> { while (true) { System.out.println("3333333333333"); ThreadUtils.sleep(1, TimeUnit.SECONDS); } });*/ thread1.setDaemon(true); thread1.start(); thread2.start(); //thread3.start(); } }
本文由
传智教育博学谷狂野架构师
教研团队发布。如果本文对您有帮助,欢迎
关注
和点赞
;如果您有任何建议也可留言评论
或私信
,您的支持是我坚持创作的动力。转载请注明出处!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从源码角度深入解析Callable接口
摘要:从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小。 本文分享自华为云社区《一个Callable接口能有多少知识点?》,作者: 冰 河。 并发编程一直是程序员们比较头疼的,如何编写正确的并发程序相比其他程序来说,是一件比较困难的事情,并发编程中出现的 Bug 往往也是特别诡异的。 之所以说并发编程出现的 Bug 比较诡异,是因为在并发编程中,很多时候出现的 Bug 不一定能完美的复现出来,也就是说,并发编程的 Bug 是很难重现,很难追踪的。 Callable接口介绍 Callable接口是JDK1.5新增的泛型接口,在JDK1.8中,被声明为函数式接口,如下所示。 @FunctionalInterface public interface Callable<V> { V call() throws Exception; } 在JDK 1.8中只声明有一个方法的接口为函数式接口,函数式接口可以使用@FunctionalInterface注解修饰,也可以不使用@FunctionalInterface注解修饰...
- 下一篇
Kurator v0.3.0版本发布
摘要:2023年4月8日,Kurator正式发布v0.3.0版本。 本文分享自华为云社区《华为云 Kurator v0.3.0 版本发布!集群舰队助力分布式云统一管理》,作者:云容器大未来 。 2023年4月8日,Kurator正式发布v0.3.0版本。 Kurator 是华为云推出的分布式云原生开源套件,通过集成业界主流开源技术栈,帮助用户一站式构建专属的分布式云原生基础设施,助力企业业务跨云跨边、分布式化升级。Kurator v0.2 版本已具备管理多云基础设施和异构基础设施的核心能力,通过引入Cluster Operator组件,支持“AWS自建集群”和“本地数据中心集群”包括集群创建、清理等在内的生命周期管理特性。 在最新发布的v0.3.0版本中,Cluster Operator不仅分别对两类集群的生命周期管理能力进行了增强,也将v0.2.0版本中的多个API对象抽象成一个API对象cluster,方便用户使用。 同时,在 cluster 对象基础上,Kurator引入了舰队的概念。每个舰队代表一个物理集群组,方便Kurator未来进行统一的编排、调度,统一的流量治理以及统一的...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- MySQL8.0.19开启GTID主从同步CentOS8
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装Docker,最新的服务器搭配容器使用
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Linux系统CentOS6、CentOS7手动修改IP地址