当前位置:首页 > Java技术 > JAVA多线程程序开发基础知识

JAVA多线程程序开发基础知识

2022年11月06日 20:44:46Java技术14

JAVA多线程基础

概念-程序、进程与多任务

程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。 
进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。 
多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。

线程

线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。 
简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。 
操作系统给每个线程分配不同的CPU时间片,在某一时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行。

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

创建多线程

每个Java程序启动后,虚拟机将自动创建一个主线程 
可以通过以下两种方式自定义线程类: 
创建 java.lang.Thread 类的子类,重写该类的 run方 法 
创建 java.lang.Runnable接 口的实现类,实现接口中的 run 方法

继承 Thread 类

Thread:代表一个线程类

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

Thread 类 
Thread类中的重要方法: 
run方法:包括线程运行时执行的代码,通常在子类中重写它。 
start方法:启动一个新的线程,然后虚拟机调用新线程的run方法 
Thread 类代码示例:

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

线程执行流程

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

创建多线程

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

问题:要定义的线程类已经显式继承了一个其他的类怎么办? 
答:实现Runnable接口

Runnable 接口

Runnable 接口中只有一个未实现的 run 方法,实现该接口的类必须重写该方法。 
Runnable 接口与 Thread 类之间的区别 
Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写 
Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类 
Runnable 接口适合于资源的共享

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

 

线程的生命周期

线程的生命周期: 
- 指线程从创建到启动,直至运行结束 
- 可以通过调用 Thread 类的相关方法影响线程的运行状态

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

线程的运行状态 
1、 新建(New) 
当创建了一个Thread对象时,该对象就处于“新建状态” 
没有启动,因此无法运行 
2、 可执行(Runnable) 
其他线程调用了处于新建状态线程的start方法,该线程对象将转换到“可执行状态” 
线程拥有获得CPU控制权的机会,处在等待调度阶段。 
3、 运行(Running) 
处在“可执行状态”的线程对象一旦获得了 CPU 控制权,就会转换到“执行状态” 
在“执行状态”下,线程状态占用 CPU 时间片段,执行run 方法中的代码 
处在“执行状态”下的线程可以调用 yield 方法,该方法用于主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

4、 阻塞(Blocking) 
线程在“执行状态”下由于受某种条件的影响会被迫出让CPU控制权,进入“阻塞状态”。 
进入阻塞状态的三种情况 
- 调用sleep方法 
1、 public void sleep(long millis) 
2、 Thread类的sleep方法用于让当前线程暂时休眠一段时间 
参数 millis 的单位是毫秒

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

 

- 调用join方法 
处在“执行状态”的线程如果调用了其他线程的 join 方法,将被挂起进入“阻塞状态” 
目标线程执行完毕后才会解除阻塞,回到 “可执行状态”

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

执行I/O操作 
线程在执行过程中如果因为访问外部资源(等待用户键盘输入、访问网络)时发生了阻塞,也会导致当前线程进入“阻塞状态”。 
4.1、 解除阻塞 
睡眠状态超时 
调用 join 后等待其他线程执行完毕 
I/O 操作执行完毕 
调用阻塞线程的 interrupt 方法(线程睡眠时,调用该线程的interrupt方法会抛出InterruptedException)

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

5、死亡(Dead) 
死亡状态(Dead):处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。 
已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException 
可以使用 Thread 类的 isAlive 方法判断线程是否活着

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

线程调度

线程调度 
按照特定机制为线程分配 CPU 时间片段的行为 
Java程序运行时,由 Java 虚拟机负责线程的调度 
线程调度的实现方式 
分时调度模型:让所有线程轮流获得CPU的控制权,并且为每个线程平均分配CPU时间片段 
抢占式调度模型:选择优先级相对较高的线程执行,如果所有线程的优先级相同,则随机选择一个线程执行 。Java虚拟机采用此种调度模型。

线程的优先级

Thread类提供了获取和设置线程优先级的方法 
getPriority:获取当前线程的优先级 
setPriority:设置当前线程的优先级 
Java语言为线程类设置了10个优先级,分别使用1~10内的整数表示 ,整数值越大代表优先级越高。每个线程都有一个默认的优先级,主线程的默认优先级是5。 
Thread类定义的三个常量分别代表了几个常用的优先级: 
MAX_PRIORITY::代表了最高优先级10 
MIN_PRIORITY::代表了最低优先级1 
NORM_PRIORITY::代表了正常优先级5 
setPriority 不一定起作用,在不同的操作系统、不同的 JVM 上,效果也可能不同。操作系统也不能保证设置了优先级的线程就一定会先运行或得到更多的CPU时间。 
在实际使用中,不建议使用该方法

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

线程同步

问题:通过多线程解决售票问题。 
非线程安全示例: 
TicketWindow2.java

复制内容到剪贴板
  1. /** 
  2.  * 售票窗口 
  3.  */  
  4. public class TicketWindow2 implements Runnable {  
  5.     //票数  
  6.     int ticketNum = 10;  
  7.     private  boolean isNext(){  
  8.             //是否售完标识  
  9.             boolean f = false;  
  10.             if (ticketNum > 0) {  
  11.                 //每次少一张票  
  12.                 ticketNum--;  
  13.                 f = true;  
  14.             }  
  15.             try {  
  16.                 /** 
  17.                  * Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9 
  18.                  * 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出 
  19.                  * 窗口1 剩余 8 张票... 
  20.                  * 窗口2 剩余 8 张票... 
  21.                  */  
  22.                 Thread.currentThread().sleep(100);  
  23.             } catch (InterruptedException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.             if (ticketNum == 0)  
  27.                 f = false;  
  28.             if (!f)  
  29.                 System.out.println(Thread.currentThread().getName() + " 票已售完 ");  
  30.             else  
  31.                 System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");  
  32.             return f;  
  33.   
  34.     }  
  35.     @Override  
  36.     public void run() {  
  37.         for (;ticketNum > 0 ;){  
  38.             //执行售票  
  39.             isNext();  
  40.         }  
  41.     }  
  42. }  

测试方法

复制内容到剪贴板
  1. public static void main(String[] a ){  
  2. TicketWindow2 tw2 = new TicketWindow2();  
  3.   
  4.         Thread t1 = new Thread(tw2,"窗口1");  
  5.         Thread t2 = new Thread(tw2,"窗口2");  
  6.   
  7.         t2.start();  
  8.         t1.start();  
  9.     }  

结果:

窗口1 剩余 8 张票… 
窗口2 剩余 8 张票… 
窗口1 剩余 6 张票… 
窗口2 剩余 6 张票… 
窗口1 剩余 4 张票… 
窗口2 剩余 3 张票… 
窗口1 剩余 2 张票… 
窗口2 剩余 1 张票… 
窗口1 票已售完 
窗口2 票已售完

结果已反映了一切问题。多窗口售票是不安全的有问题的。那如何解决这些问题???

线程安全

多线程应用程序同时访问共享对象时,由于线程间相互抢占CPU的控制权,造成一个线程夹在另一个线程的执行过程中运行,所以可能导致错误的执行结果。

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

Synchronized 关键字

为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。 
1、 synchronized关键字 
确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念 
2、 使用 synchronized 关键字 
修饰方法:被“synchronized”关键字修饰的方法称为”同步方法” 
当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问

复制内容到剪贴板
  1. //定义同步方法  
  2.  public synchronized void methd(){  
  3.     //方法实现  
  4.  }  

JAVA多线程程序开发基础知识 _ JavaClub全栈架构师技术笔记

3、 使用 ”synchronized” 关键字:修饰部分代码,如果只希望同步部分代码行,可以使用“同步块”

复制内容到剪贴板
  1. //同步块  
  2.  synchronized(obj){  
  3.     //被同步的代码块  
  4.  }  

同步块的作用与同步方法一样,只是控制范围有所区别

修改示例

重新修改售票的例子,将isNext方法改为同步方法

复制内容到剪贴板
  1. //票数  
  2.   int ticketNum = 100;  
  3.   /** 
  4.    * 同步方法 
  5.    */  
  6.   private synchronized boolean isNext(){  
  7.       /** 
  8.        * 同步代码块 
  9.        */  
  10.         synchronized (this) {  
  11.       boolean f = false;  
  12.       if (ticketNum > 0) {  
  13.           ticketNum--;  
  14.           f = true;  
  15.       }  
  16.       try {  
  17.           /** 
  18.            * Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9 
  19.            * 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出 
  20.            * 窗口1 剩余 8 张票... 
  21.            * 窗口2 剩余 8 张票... 
  22.            */  
  23.           Thread.currentThread().sleep(100);  
  24.       } catch (InterruptedException e) {  
  25.           e.printStackTrace();  
  26.       }  
  27.   
  28.       if (ticketNum == 0)  
  29.           f = false;  
  30.   
  31.       if (!f)  
  32.           System.out.println(Thread.currentThread().getName() + " 票已售完 ");  
  33.       else  
  34.           System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");  
  35.       return f;  
  36.         }  
  37.   }  

测试: 
为了实现多线程的结果 我模拟了4个售票窗口,100张票 
测试方法

复制内容到剪贴板
  1. public static void main(String[] a ){  
  2. /** 
  3.          * 这里可以看出 TicketWindow2 为线程共享参照对象, 
  4.          * 也就是说,如果需要同步的话,那synchronize(参照对象)中的参照对象即为:TicketWindow2 
  5.          */  
  6.         TicketWindow2 tw2 = new TicketWindow2();  
  7.   
  8.         Thread t1 = new Thread(tw2,"窗口1");  
  9.         Thread t2 = new Thread(tw2,"窗口2");  
  10.   
  11.         Thread t3 = new Thread(tw2,"窗口3");  
  12.         Thread t4 = new Thread(tw2,"窗口4");  
  13.   
  14.         t2.start();  
  15.         t1.start();  
  16.         t4.start();  
  17.         t3.start();  
  18.     }  

结果:

窗口2 剩余 99 张票… 
窗口2 剩余 98 张票… 
… 
窗口2 剩余 84 张票… 
窗口3 剩余 83 张票… 
窗口3 剩余 82 张票… 
… 
窗口3 剩余 20 张票… 
窗口4 剩余 19 张票… 
… 
窗口4 剩余 1 张票… 
窗口4 票已售完 
窗口1 票已售完 
窗口3 票已售完 
窗口2 票已售完

线程通信

wait()方法: 
中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。 
notify()方法: 
唤醒由于使用这个同步方法而处于等待线程的 某一个结束等待 
notifyall()方法: 
唤醒所有由于使用这个同步方法而处于等待的线程结束等待

练习

通过交叉打印,练习线程通信方法 
PrintWords.java

复制内容到剪贴板
  1. public class PrintWords implements Runnable{  
  2.     private boolean a=true,b=false;  
  3.   
  4.     private char abc = 'a';  
  5.   
  6.     private synchronized void doPrint(){  
  7.         if (abc <= 'z') {  
  8.             String name = Thread.currentThread().getName();  
  9.             System.out.println("Thread- " + name + "   :  " + abc);  
  10.             abc++;  
  11.             //等下个线程进来就可以换新上个等待的线程  
  12.             notifyAll();  
  13.             try {  
  14.                 //当前线程等待  
  15.                 wait();  
  16.             } catch (InterruptedException e) {  
  17.                 e.printStackTrace();  
  18.             }  
  19.         }  
  20.         if (abc >= 'z'){  
  21.             notifyAll();  
  22.         }  
  23.     }  
  24.   
  25.     @Override  
  26.     public void run() {  
  27.         while (abc <= 'z') {  
  28.             doPrint();  
  29.         }  
  30.     }  
  31.   
  32.     public static void main(String[] a){  
  33.         Runnable p = new PrintWords();  
  34.   
  35.         Thread ta = new Thread(p,"a");  
  36.         Thread tb = new Thread(p,"b");  
  37.   
  38.         ta.start();  
  39.         tb.start();  
  40.   
  41.     }  
  42. }  

测试结果

Thread- a : a 
Thread- b : b 
Thread- a : c 
Thread- b : d 
… 
Thread- a : k 
Thread- b : l 
… 
Thread- b : z

作者:中琦2513
来源链接:https://blog.csdn.net/zhongqi2513/article/details/51030942

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/68224.html

分享给朋友:

“JAVA多线程程序开发基础知识” 的相关文章

Java空指针异常解决java.lang.NullPointerException解决心得

Java空指针异常解决java.lang.NullPointerException解决心得

今天做课设的时候运行程序报出以下错误 java.lang.NullPointerException 首先要理解的是此错误并不会在 程序中报错,只会在运行的时候报错。 是由于某个参数(集合,数组等数据)可能出现一个null值而导致后面的程序不能运行时...

在JAVA 中将堆与栈分开的原因

栈是运行时的单位,而堆是存储的单位。 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么 放、放在哪儿。 注意:在Java中一个线程就会相应有一个线程栈与之对应 栈因为是运行单位,因此里面存储的信息都是跟...

Java实现1到n的倒数的累加和

Java实现1到n的倒数的累加和

从键盘读入一个数,然后进行运算 实现代码: public static void main(String[] args) { Scanner in=new Scanner(System.in); int n ; n=in....

我对java String的理解 及 源码浅析

我对java String的理解 及 源码浅析

摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! -泥沙砖瓦浆木匠 一...

[Java 泥水匠] Java Components 之二:算法篇之项目实践中的位运算符(有你不懂的哦)

[Java 泥水匠] Java Components 之二:算法篇之项目实践中的位运算符(有你不懂的哦)

作者:泥沙砖瓦浆木匠 网站:http://blog.csdn.net/jeffli1993 个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。 交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=...

Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较

Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较

Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 上一篇总结了下ArrayList 、LinkedList和Vector比较,今天泥瓦匠总结下Hash 、LinkedList和Vector比较。其实大家都是...

java高级

java高级

  Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。下...

Java 单例模式详解

java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。   单例模式有一下特点:   1、单例类只能有一个实例。   2、单例类必须自己自己创建自己的唯一实例。   3、单例类必须给所有其他对象提供这一实例。 概念:  jav...

实现一个Java五子棋

实现一个Java五子棋

五子棋手把手教你写: 写在前面的话: 回想起从前初学代码的五子棋简直写的不像样子。今天闲来无事就写了个五子棋的小程序。 如果有需要可以从github上下载:github地址:https://github.com/GodofOrange/gobang.git 一来...

java中的内存模型

java中的内存模型

概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面,有时候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型。究竟什么...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。