22.Java多线程

news/2024/11/29 3:46:45/

Java多线程

一、进程和线程

进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。

二、线程的实现

在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。

  • 实现 Runnable 接口
public class Thrund implements Runnable {// 实现runnable接口private String name;public Thrund(String name) {this.name = name;}// 重写run方法@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(name + "运行,i=" + i) ;}}}public static void main(String[] args) {Thrund mt1=new Thrund("线程一");Thrund mt2=new Thrund("线程二");Thread t1=new Thread(mt1);Thread t2=new Thread(mt2);t1.start();t2.start();}

在这里插入图片描述

继承 Thread 类

public class Thrund extends Thread{  // 继承Thread类,作为线程的实现类 private String name;public Thrund(String name) {this.name = name;}// 重写run方法@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(name + "运行,i=" + i) ;}}}public static void main(String[] args) {Thrund t1=new Thrund("线程一");Thrund t2=new Thrund("线程二");t1.start();t2.start();}

在这里插入图片描述

从程序可以看出,现在的两个线程对象是交错运行的,哪个线程对象抢到了 CPU 资源,哪个线程就可以运行,所以程序每次的运行结果肯定是不一样的,在线程启动虽然调用的是 start() 方法,但实际上调用的却是 run() 方法定义的主体。

Thread 类和 Runnable 接口
Thread 类也是 Runnable 接口的子类,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法。在 Thread 类中的 run() 方法调用的是 Runnable 接口中的 run() 方法,也就是说此方法是由 Runnable 子类完成的,所以如果要通过继承 Thread 类实现多线程,则必须覆写 run()。
实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的,如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。

三、线程的状态

在这里插入图片描述

要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。下面分别介绍一下这几种状态:

  • 新建阶段
    在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread
    类的构造方法来实现,例如 “Thread thread=new Thread()”。
  • 就绪阶段 新建线程对象后,调用该线程的 start()
    方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。
  • 运行阶段 当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run()
    方法定义该线程的操作和功能
  • 阻塞阶段 一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU
    暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait()
    等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
  • 死亡阶段 线程调用 stop() 方法时或 run()
    方法执行结束后以及发生Error或Exception时,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

Java 程序每次运行至少启动几个线程?
答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。

四、线程的操作

  • 取得和设置线程的名称
public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "运行,i=" + i) ;}}
}
public static void main(String[] args) {Thrund t=new Thrund();new Thread(t).start();;new Thread(t,"线程一").start();}

在这里插入图片描述

线程的强制运行
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。

public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "运行,i=" + i) ;}}
}public static void main(String[] args) {Thrund t=new Thrund();Thread t1=new Thread(t);t1.start();for (int i = 0; i < 10; i++) {if(i>3) {try {t1.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("Main线程"+i);}}

在这里插入图片描述

线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。

public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行,i=" + i) ;}}
}public static void main(String[] args) {Thrund t=new Thrund();Thread t1=new Thread(t);t1.start();}

中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。

public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {System.out.println("1、进入run()方法");try {Thread.sleep(100000);//线程休眠10秒钟System.out.println("2、线程已完成休眠");} catch (InterruptedException e) {// TODO Auto-generated catch blockSystem.out.println("3、异常情况线程被终止");return;}System.out.println("4、run()正常结束");}
}
public static void main(String[] args) {Thrund t=new Thrund();Thread t1=new Thread(t);t1.start();try {Thread.sleep(20000);} catch (InterruptedException e) {System.out.println("5、线程发生异常");}t1.interrupt();}

在这里插入图片描述

后台线程
在 Java 程序中,只要前台有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行,要想实现这样的操作,直接使用 setDaemon() 方法即可。

public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {while (true) {System.out.println(Thread.currentThread().getName() + "在运行。");}}
}
public static void main(String[] args) {Thrund t=new Thrund();Thread t1=new Thread(t);t1.setDaemon(true);t1.start();
}

在线程类 Thrund 中,尽管 run() 方法中是死循环的方式,但是程序依然可以执行完,因为方法中死循环的线程操作已经设置成后台运行。

线程的优先级

  • 在 Java 的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程就有可能会先被执行。
public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "运行,i = " + i); // 取得当前线程的名字}}
}
public static void main(String[] args) {Thread t1=new Thread(new Thrund(),"线程一");Thread t2=new Thread(new Thrund(),"线程二");Thread t3=new Thread(new Thrund(),"线程三");t1.setPriority(Thread.MAX_PRIORITY);//最高等级t2.setPriority(Thread.MIN_PRIORITY);//最低等级t3.setPriority(Thread.NORM_PRIORITY);//中等等级t1.start();t2.start();t3.start();}

在这里插入图片描述

从程序的运行结果中可以观察到,线程将根据其优先级的大小来决定哪个线程会先运行,但是需要注意并非优先级越高就一定会先执行,哪个线程先执行将由 CPU 的调度决定。

线程的礼让

  • 在线程操作中,也可以使用 yield() 方法将一个线程的操作暂时让给其他线程执行。
public class Thrund implements Runnable {// 实现runnable接口// 重写run方法@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行,i = " + i); // 取得当前线程的名字if(i==2) {System.out.print("线程礼让:");Thread.currentThread().yield();}}}
}
public static void main(String[] args) {Thread t1=new Thread(new Thrund(),"线程一");Thread t2=new Thread(new Thrund(),"线程二");t1.start();t2.start();}

在这里插入图片描述

五、同步和死锁

一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。

  • 同步代码块
public class Thrund implements Runnable {// 实现runnable接口private int piaoshu=5;//基础票数5张@Overridepublic void run() {for (int i = 0; i < 100; i++) {synchronized (this) {//同步当前对象if(piaoshu>0) {try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("卖票:piaoshu="+piaoshu--);}}}}}
public static void main(String[] args) {Thrund t=new Thrund();Thread t1=new Thread(t,"线程一");Thread t2=new Thread(t,"线程二");t1.start();t2.start();}

在这里插入图片描述
同步方法
除了可以将需要的代码设置成同步代码块外,也可以使用 synchronized 关键字将一个方法声明为同步方法。

public class Thrund implements Runnable {// 实现runnable接口private int piaoshu=5;//基础票数5张@Overridepublic void run() {for (int i = 0; i < 100; i++) {this.sopt();}}private synchronized void sopt() {if(piaoshu>0) {try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("卖票:piaoshu="+piaoshu--);}}}
public static void main(String[] args) {Thrund t=new Thrund();Thread t1=new Thread(t,"线程一");Thread t2=new Thread(t,"线程二");t1.start();t2.start();}

在这里插入图片描述

线程的挂起与唤醒
在使用 wait 和 notify 之前,我们需要先了解对象的控制权(monitor)。在 Java 中任何一个时刻,对象的控制权只能被一个线程拥有。无论是执行对象的 wait、notify 还是 notifyAll 方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。所以我们通过同步synchronized 代码块来保证我们对对象的控制权。

public class ThrundTest {private final Object flag = new Object();public static void main(String[] args) {ThrundTest threadTest = new ThrundTest();ThreadA threadA = threadTest.new ThreadA();threadA.start();ThreadB threadB = threadTest.new ThreadB();threadB.start();}class ThreadA extends Thread {@Overridepublic void run() {synchronized (flag) {for (int i = 1; i <= 100; i += 2) {flag.notify();System.out.println(i); // 奇数try {flag.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}class ThreadB extends Thread {@Overridepublic void run() {synchronized (flag) {for (int i = 2; i <= 100; i += 2) {flag.notify();System.out.println(i); // 偶数if (i == 100) {// 当输出了最后一个数字的时候,不能再wait了break;}try {flag.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}

六、线程安全和线程非安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
线程安全是通过线程同步控制来实现的,也就是synchronized关键字。
非线程安全是指多线程操作同一个对象可能会出现问题。而线程安全则是多线程操作同一个对象不会有问题。
线程安全必须要使用很多synchronized关键字来同步控制,所以必然会导致性能的降低。
所以在使用的时候,如果是多个线程操作同一个对象,那么使用线程安全的类;否则,就使用效率更高的类。
非线程安全!=不安全


http://www.ppmy.cn/news/61647.html

相关文章

Java 面试必问的线程池原理总结

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"&#xff01; 作者&#xff1a;大能老师 | 慕课网讲师 Java 线程池原理总结 &#xff08;一&#xff09;什么是线程池 线程池做的工作主要是控制运行…

MySQL基础(十四)视图

1. 常见的数据库对象 对象描述表(TABLE)表是存储数据的逻辑单元&#xff0c;以行和列的形式存在&#xff0c;列就是字段&#xff0c;行就是记录数据字典就是系统表&#xff0c;存放数据库相关信息的表。系统表的数据通常由数据库系统维护&#xff0c;程序员通常不应该修改&…

【软考高项笔记】第3章 信息系统治理(针对甲方)3.1 IT治理

第3章 信息系统治理&#xff08;针对甲方&#xff09; 3.1 IT治理 不同于管理&#xff0c;角度更高3.1.1 IT治理基础 目标价值 与业务目标一致 有效利用信息与数据资源 风险管理 管理层次 最高管理层 &#xff08;定目标&#xff0c;战略&#xff09; 执行管理层 &#xff08…

记一次产线打印json导致的redis连接超时

服务在中午十一点上线后&#xff0c;服务每分钟发出三到四次redis连接超时告警。错误信息为&#xff1a; Dial err:dial tcp: lookup xxxxx: i/o timeout 排查过程 先是检查redis机器的情况&#xff0c;redis写入并发数较大&#xff0c;缓存中保留了一小时大概400w条数据。red…

Python进阶篇(五)-- 邮件客户端实现与电子邮件发送

1 SMTP 通过完成本实验&#xff0c;我们将更加了解SMTP协议。还将学到使用Python实现标准协议的经验。 主要任务是开发一个简单的邮件客户端&#xff0c;将邮件发送给任意收件人。客户端将需要连接到邮件服务器&#xff0c;使用SMTP协议与邮件服务器进行对话&#xff0c;并向邮…

SQL Server存储架构(1)——页(Page)、区间(Extent)及分配单位(Allocation Unit)

3. 存储架构 所谓存储架构,这里是指SQL Server数据库磁盘空间组织、管理和使用相关的逻辑设计及实现等方面内容。更具体一点,就是讲SQL Server数据库是如何组织、分配、管理和使用磁盘文件内的存储空间的。本书1.2节中我们已经讲过,SQL Server数据库由多种设计及实现各不相…

SPSS如何进行信度分析之案例实训?

文章目录 0.引言1.信度分析2.多维刻度分析 0.引言 因科研等多场景需要进行数据统计分析&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对信度分析进行阐述。 1.信度分析 &a…

项目管理:项目进度跟踪的好处有哪些?

项目进度跟踪主要针对项目计划、任务和项目成员三个方面&#xff0c;即为了了解整个项目计划完成情况、了解项目的实际进展情况、解成员工作完成情况。 项目跟踪可以证明计划是否可执行&#xff0c;可以说明计划是否可以被完成。 在项目执行过程中&#xff0c;我们也可以通过跟…