Java多线程--两种实现方式
进程概述:
在这之前,有必要了解一下什么是进程?
在一个操作系统中,每个独立的执行的程序都可称为一个进程,也就是“正在运行的程序”。如图所示:

线程概述:
如上所述,每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看做程序的执行的一条条线索,被称为线程。操作系统中的每一个进程都至少存在一个线程。
多线程的概念:
多线程是指一个应用程序中有许多条并发执行的线索,每条线索都被称作一个线程,他们会交替执行,彼此间进行通信。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。
Java中线程实现的方式
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。
继承Thread类创建多线程
1 @SpringBootTest
2 public class Example {
3 public static void main(String[] args) {
4 // 实例化对象
5 MyThread myThread = new MyThread();
6 // 调用线程主体
7 new Thread(myThread,"线程A").start();
8 new Thread(myThread,"线程B").start();
9 }
10 }
11 // 继承Thread类
12 class MyThread extends Thread{
13 //覆写run()方法,作为线程的操作主体
14 public void run(){
15 for (int i = 0; i <5 ; i++) {
16 Thread th = Thread.currentThread();//获取当前线程
17 String name = th.getName();//获取当前线程的名字
18 System.out.println(name +"运行:i = "+i);
19 }
20 }
21 }
程序运行结果:

实现Runnable接口创建多线程
1 @SpringBootTest
2 public class Example {
3 public static void main(String[] args) {
4 // 实例化对象
5 MyThread myThread = new MyThread();
6 // 调用线程主体
7 new Thread(myThread,"线程A").start();
8 new Thread(myThread,"线程B").start();
9 }
10 }
11 // 实现Runnable接口,作为线程的实现类
12 class MyThread implements Runnable{
13 //重写run()方法,作为线程的操作主体
14 public void run(){
15 for (int i = 0; i <5 ; i++) {
16 Thread th = Thread.currentThread();//获取当前线程
17 String name = th.getName();//获取当前线程的名字
18 System.out.println(name +"运行:i = "+i);
19 }
20 }
21 }
程序运行结果:

从程序可以看出,现在的两个线程对象是交错运行的,哪个线程对象抢到了 CPU 资源,哪个线程就可以运行,所以程序每次的运行结果肯定是不一样的,在线程启动虽然调用的是 start() 方法,但实际上调用的却是 run() 方法定义的主体。
两种实现多线程方式的对比
虽说百度答案都千篇一律,但没有比自己动手验证更令人印象深刻,毕竟:实践是检验真理的唯一标准!
假设售票厅有四个窗口可发售某日某次列车的100张车票,这时,100张车票可以看做共享资源,四个售票窗口要创建四个线程。
继承Thread类
1 @SpringBootTest
2 public class Example {
3 public static void main(String[] args) {
4 // 实例化对象
5 MyThread myThread1 = new MyThread();
6 MyThread myThread2 = new MyThread();
7 MyThread myThread3 = new MyThread();
8 MyThread myThread4 = new MyThread();
9 // 调用线程主体
10 myThread1.setName("窗口一");
11 myThread2.setName("窗口二");
12 myThread3.setName("窗口三");
13 myThread4.setName("窗口四");
14 myThread1.start();
15 myThread2.start();
16 myThread3.start();
17 myThread4.start();
18
19 }
20 }
21
22 // 继承Thread类
23 class MyThread extends Thread{
24 private int tickets = 100;
25 //覆写run()方法,作为线程的操作主体
26 public void run(){
27 while (true){ //通过死循环打印语句
28 if (tickets > 0) {
29 Thread th = Thread.currentThread();//获取当前线程
30 String name = th.getName();//获取当前线程的名字
31 System.out.println(name + ":正在发售第" + tickets-- + "张票!");
32 }
33 }
34 }
35 }
程序运行结果:

从运行结果可以看出,每张票都被打印了四次,四个线程没有共享100张票,而是各自售出了100张。
现实中铁路系统中的票资源是共享的,因此上面的运行结果显然不合理。为了保证资源共享,在程序中只能创建一个售票对象,然后开启多个线程去运行同一个售票对象的售票方法,简单来说就是四个线程运行同一个售票程序,这时候就需要通过Runnable接口来实现。
实现Runnable接口
1 @SpringBootTest
2 public class Example {
3 public static void main(String[] args) {
4 // 实例化对象
5 MyThread myThread = new MyThread();
6 // 调用线程主体
7 new Thread(myThread,"窗口一").start();
8 new Thread(myThread,"窗口二").start();
9 new Thread(myThread,"窗口三").start();
10 new Thread(myThread,"窗口四").start();
11 }
12 }
13 // 实现Runnable接口,作为线程的实现类
14 class MyThread implements Runnable{
15 private int tickets = 100;
16 //重写run()方法,作为线程的操作主体
17 public void run(){
18 while (true){ //通过死循环打印语句
19 if (tickets > 0) {
20 Thread th = Thread.currentThread();//获取当前线程
21 String name = th.getName();//获取当前线程的名字
22 System.out.println(name + ":正在发售第" + tickets-- + "张票!");
23 }
24 }
25 }
26 }
只创建了一个MyThread 对象,然后创建了四个线程,在每个线程上面都去调用MyThread 对象中的run()方法,这样就可以确保四个线程访问的是同一个tickets变量,共享100张车票。
程序运行结果:
通过对比可以看出,实现Runnable接口相对于继承Thread类来说,有这如下显著的好处:
- 适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码,数据有效的分离,很好的体现了面向对象的设计思想。
- 可以避免由于java单继承带来的局限性。由于一个类不能同时有两个父类,使用一个已经继承了某一个类的子类创建线程,所以不能用继承Thread类的方式,那么就只能采用实现Runnable接口的方式。
后台线程
在上面的案例中,当main()方法创建并启动四个新的线程后,main()方法中的代码执行完毕,这时方法结束,main线程也就随之结束了。实际上,虽然main线程结束了,但整个java程序却没有随之结束,仍会执行售票的代码。对于java程序来说,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程中只有后台线程运行,这个进程就会结束,前台线程和后台线程是一种相对的概念,新创建的线程默认都是前台线程,如果某个线程对象在启动之前调用了setDaemon语句,这个线程就变成了一个后台线程。
1 @SpringBootTest
2 //创建DamonThread类,实现Runnable接口
3 class DamonThread implements Runnable{
4
5 //实现接口中的run()方法
6 @Override
7 public void run() {
8 while (true){
9 System.out.println(Thread.currentThread().getName() + "正在运行!");
10 }
11 }
12 }
13 public class Example {
14 public static void main(String[] args) {
15 System.out.println("main线程是后台线程吗?" +Thread.currentThread().isDaemon());
16 //创建一个DamonThread对象dt
17 DamonThread dt = new DamonThread();
18 //创建线程thread共享dt资源
19 Thread thread = new Thread(dt,"后台线程");
20 //判断是否为后台线程
21 System.out.println("thread默认是后台线程吗?" + thread.isDaemon());
22 //将线程thread设置为后台线程
23 thread.setDaemon(true);
24 //开启线程
25 thread.start();
26 for (int i = 0; i < 5; i++) {
27 System.out.println(i);
28 }
29 }
30 }
程序运行结果:

当开启线程thead后,会执行死循环中的打印语句,将线程thread设置为后台线程后,前台线程就会死亡,JVM会通知后台线程。后台线程从接收指令到做出响应,需要一定的时间,因此,打印了几次“后台线程正在运行!”语句后,后台线程也结束了,由此说明进程中只有后台线程时,进程就会结束。
注意:要将某个线程设置为后台线程,必须在该线程启动之前,也就是说setDaemon()方法必须在start()方法调用之前,否则会引发异常。
Java多线程--两种实现方式的更多相关文章
- python 多线程两种实现方式,Python多线程下的_strptime问题,
python 多线程两种实现方式 原创 Linux操作系统 作者:杨奇龙 时间:2014-06-08 20:24:26 44021 0 目前python 提供了几种多线程实现方式 thread,t ...
- java的两种同步方式, Synchronized与ReentrantLock的区别
java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock. 相似点: 这两种同步方式有很多相似之处,它们都是加锁 ...
- JAVA多线程三种实现方式
JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...
- Java 多线程 三种实现方式
Java多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...
- 【Python】python 多线程两种实现方式
目前python 提供了几种多线程实现方式 thread,threading,multithreading ,其中thread模块比较底层,而threading模块是对thread做了一些包装,可以更 ...
- Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier
Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...
- Java HashMap两种遍历方式
第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Ma ...
- JAVA - 多线程 两种方法的比较
一.继承Thread类 实现方法: (1).首先定义一个类去继承Thread父类,重写父类中的run()方法.在run()方法中加入具体的任务代码或处理逻辑.(2).直接创建一个ThreadDemo2 ...
- java多线程 —— 两种实际应用场景模拟
最近做的偏向并发了,因为以后消息会众多,所以,jms等多个线程操作数据的时候,对共享变量,这些要很注意,以防止发生线程不安全的情况. (一) 先说说第一个,模拟对信息的发送和接收.场景是这样的: 就像 ...
随机推荐
- JPDA 远程调试方法记录
一.JPDA概念 JPDA(Java platform debugger architecture)是java平台调试架构的简称,由java虚拟机后端和调试平台前端组成. 1.java虚拟机提供了ja ...
- 操作系统微内核和Dubbo微内核,有何不同?
你好,我是 yes. 在之前的文章已经提到了 RPC 的核心,想必一个 RPC 通信大致的流程和基本原理已经清晰了. 这篇文章借着 Dubbo 来说说微内核这种设计思想,不会扯到 Dubbo 某个具体 ...
- PP主数据-物料主数据
一.PP物料主数据:PP的物料主数据,是对应到系统的组织架构的,不同的组织层次,都有各自的主数据需要创建. (1),一般数据:一般数据是在集团层面的主数据,主要包括:物料编码.物料描述.辅助计量单位以 ...
- python对离散数据进行编码
机器学习中会遇到一些离散型数据,无法带入模型进行训练,所以要对其进行编码,常用的编码方式有两种: 1.特征不具备大小意义的直接独热编码(one-hot encoding) 2.特征有大小意义的采用映射 ...
- 什么情况下调用doGet()和doPost()?
默认情况是调用doGet()方法,JSP页面中的Form表单的method属性设置为post的时候,调用的为doPost()方法: 为get的时候,调用deGet()方法.
- 【Go】四舍五入在go语言中为何如此困难
四舍五入是一个非常常见的功能,在流行语言标准库中往往存在 Round 的功能,它最少支持常用的 Round half up 算法. 而在 Go 语言中这似乎成为了难题,在 stackoverflow ...
- JVM笔记——类加载
1.在java代码中,类型(如class enum interface)的加载.连接.初始化过程都是在程序运行期完成的.这个特性,使得本为静态语言的java,拥有了动态语言的某些特征 加载:查找并加载 ...
- 如何创建 mapbox 精灵图
前面文章介绍了如何在本地发布OSM数据,并使用 maputnik 自定义 mapbox 格式的地图样式. 在使用 maputnik 配图时,如果想要使用自己的图片作为地图符号,就需要制作精灵图. ma ...
- linux security module机制
linux security module机制 概要 Hook机制,linux MAC的通用框架,可以使用SElinux, AppArmor,等作为不同安全框架的实现
- ES6 Set.Map.Symbol数据结构
一.ES6 Set数据结构 ES6新推出了Set数据结构,它与数组很类似,Set内部的成员不允许重复,每一个值在Set中都是唯一的,如果有重复的值出现会自动去重(也可以理解为忽略掉),返回的是集合对象 ...