进程概述:

  在这之前,有必要了解一下什么是进程?

  在一个操作系统中,每个独立的执行的程序都可称为一个进程,也就是“正在运行的程序”。如图所示:

线程概述:

  如上所述,每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看做程序的执行的一条条线索,被称为线程。操作系统中的每一个进程都至少存在一个线程。

多线程的概念:

  多线程是指一个应用程序中有许多条并发执行的线索,每条线索都被称作一个线程,他们会交替执行,彼此间进行通信。

  多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。

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类来说,有这如下显著的好处:

  1. 适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码,数据有效的分离,很好的体现了面向对象的设计思想。
  2. 可以避免由于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多线程--两种实现方式的更多相关文章

  1. python 多线程两种实现方式,Python多线程下的_strptime问题,

    python 多线程两种实现方式 原创 Linux操作系统 作者:杨奇龙 时间:2014-06-08 20:24:26  44021  0 目前python 提供了几种多线程实现方式 thread,t ...

  2. java的两种同步方式, Synchronized与ReentrantLock的区别

    java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock. 相似点: 这两种同步方式有很多相似之处,它们都是加锁 ...

  3. JAVA多线程三种实现方式

    JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...

  4. Java 多线程 三种实现方式

    Java多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...

  5. 【Python】python 多线程两种实现方式

    目前python 提供了几种多线程实现方式 thread,threading,multithreading ,其中thread模块比较底层,而threading模块是对thread做了一些包装,可以更 ...

  6. Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...

  7. Java HashMap两种遍历方式

    第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Ma ...

  8. JAVA - 多线程 两种方法的比较

    一.继承Thread类 实现方法: (1).首先定义一个类去继承Thread父类,重写父类中的run()方法.在run()方法中加入具体的任务代码或处理逻辑.(2).直接创建一个ThreadDemo2 ...

  9. java多线程 —— 两种实际应用场景模拟

    最近做的偏向并发了,因为以后消息会众多,所以,jms等多个线程操作数据的时候,对共享变量,这些要很注意,以防止发生线程不安全的情况. (一) 先说说第一个,模拟对信息的发送和接收.场景是这样的: 就像 ...

随机推荐

  1. linux yum install

    作为一名新手,学习Linux已经一个月了,其间遇到了不少问题,而今天笔者遇到的问题是 #yum install pam-devel #This system is not registered to ...

  2. 请问如何用LoadRunner进行测试。

    1.建立测试计划,确定测试标准和测试范围 2.设计典型场景的测试用例,覆盖常用业务流程和不常用的业务流程等 3.根据测试用例,开发自动测试脚本和场景: 录制测试脚本:新建一个脚本(Web/HTML协议 ...

  3. JavaScript 函数节流和函数去抖

    概念 函数防抖(debounce) 当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间 函数节流(throttle) 预先设定一个执行周期,当调用动作的时刻大于等于执 ...

  4. 在onelogin中使用OpenId Connect Implicit Flow

    目录 简介 OpenId Implicit Flow 创建onelogin的配置 页面的运行和请求流程 关键代码 总结 简介 onelogin支持多种OpenId Connect的连接模式,上一篇文章 ...

  5. 树莓派(4B)新手入门教程

    前期准备 必要物料 树莓派4B 主机 Type-C 电源 内存卡(8G+) 一般建议一步到位64G 系统镜像 镜像写入工具 下载地址 镜像下载 官方下载地址: https://www.raspberr ...

  6. 【函数分享】每日PHP函数分享(2021-1-8)

    explode() 使用一个字符串分割另一个字符串. array explode( string $delimiter , string $string [, int $limit ]) 参数描述de ...

  7. 一文带你学会AQS和并发工具类的关系

    1. 存在的意义   AQS(AbstractQueuedSynchronizer)是JAVA中众多锁以及并发工具的基础,其底层采用乐观锁,大量使用了CAS操作, 并且在冲突时,采用自旋方式重试,以实 ...

  8. python_元组(tuple)

    #tuple(),元组不可以修改,不能对其进行增加或删除操作,元组是有序的 #1.定义 tu_1 = () #定义一个空元组 tu_2 = (1,2,'alex',[3,4],(5,6,7),True ...

  9. FlatBuffers使用小结

    最近做一个Android APP,由于离线业务需求,需要在启动APP时候同步大量数据到APP上,遇到了JSON性能瓶颈.从下方的图片中可以看出,当使用 json 传输数据,在解析json的时候会产生大 ...

  10. IDEA 常用的一些 (就几个) 快捷键

    快捷键 说明 Ctrl + P 提示类参数 Ctrl + Q 提示类的属性和方法包名 Ctrl + D 复制一行到下一行 Ctrl + F 查找 Ctrl + R 替换 Ctrl + Z 撤销 Ctr ...