22.Java多线程

news2024/11/24 23:00:58

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方法
	@Override
	public 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方法
	@Override
	public 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方法
	@Override
	public 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方法
	@Override
	public 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 block
					e.printStackTrace();
				}
			}
			System.out.println("Main线程"+i);
		}
	}

在这里插入图片描述

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

public class Thrund implements Runnable {// 实现runnable接口
	// 重写run方法
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.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方法
	@Override
	public void run() {

		System.out.println("1、进入run()方法");
		try {
			Thread.sleep(100000);//线程休眠10秒钟
			System.out.println("2、线程已完成休眠");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.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方法
	@Override
	public 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方法
	@Override
	public 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方法
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.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张
	@Override
	public 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 block
						e.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张
	@Override
	public 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 block
				e.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 {
	        @Override
	        public 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 {
	        @Override
	        public 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.coloradmin.cn/o/489461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Python接口自动化测试post请求和get请求,获取请求返回值

目录 引言 请求接口为Post时&#xff0c;传参方法 获取接口请求响应数据 引言 我们在做python接口自动化测试时&#xff0c;接口的请求方法有get,post等&#xff1b;get和post请求传参&#xff0c;和获取接口响应数据的方法&#xff1b; 请求接口为Post时&#xff0c;传参方法…

C++系列二:数据类型

C数据类型 1. 原始类型2. 复合类型3. 类型转换3.1 隐式类型转换3.2 显式类型转换 4. 总结&#xff08;手稿版&#xff09; 1. 原始类型 C 中的原始类型包括整型&#xff08;integral types&#xff09;、浮点型&#xff08;floating-point types&#xff09;、字符型&#xff…

涨薪60%,从小厂逆袭,坐上美团技术专家(面经+心得)

前言 大多数情况下&#xff0c;程序员的个人技能成长速度&#xff0c;远远大于公司规模或业务的成长速度。所以&#xff0c;跳槽成为了这个行业里最常见的一个词汇。 实际上&#xff0c;跳槽的目的无非是为了涨薪或是职业发展&#xff0c;我也不例外。普通本科毕业后&#xf…

计算机网络基础知识(一)计算机发展史、网络设备、网络结构及拓扑

文章目录 01 | 网络设备02 | 网络结构 && 拓扑 网络发展史可以追溯到20世纪60年代&#xff0c;当时美国国防部高级研究计划署&#xff08;ARPA&#xff09;启动了一个名为 ARPANET 的项目&#xff0c;旨在建立军事目的的分布式通信网络&#xff0c;使得网络中的任何一台…

【redis】redis红锁Redlock算法和底层源码分析

【redis】redis红锁Redlock算法和底层源码分析 文章目录 【redis】redis红锁Redlock算法和底层源码分析前言一、当前代码为8.0版&#xff0c;接上一步分布式锁的主要考点lock加锁关键逻辑unlock解锁关键逻辑 二、redis分布式锁-Redlock红锁主页说明:目前所写的分布式锁还有什么…

c++自学笔记(陆续更新)

本笔记为从菜鸟教程边学边记录的笔记---》C 教程 | 菜鸟教程 面向对象程序设计 封装&#xff08;Encapsulation&#xff09;&#xff1a;封装是将数据和方法组合在一起&#xff0c;对外部隐藏实现细节&#xff0c;只公开对外提供的接口。这样可以提高安全性、可靠性和灵活性。…

C语言入门教程||C语言 头文件||C语言 强制类型转换

C语言 头文件 头文件是扩展名为 .h 的文件&#xff0c;包含了 C 函数声明和宏定义&#xff0c;被多个源文件中引用共享。有两种类型的头文件&#xff1a;程序员编写的头文件和编译器自带的头文件。 在程序中要使用头文件&#xff0c;需要使用 C 预处理指令 #include 来引用它…

USART串口接收

文章目录 运行环境&#xff1a;1.1 串口接收代码分析1)开启接收中断和空闲中断2)接收存储变量声明和定义3)中断处理函数 2.1实验效果 运行环境&#xff1a; ubuntu18.04.melodic 宏基暗影骑士笔记本 stm32f427IIH6 stlink 9-24v可调电源 usb转串口 杜邦线转4pin 1.1 串口接收…

Python | 人脸识别+活体检测+背景模糊+关键点检测系统(Face_Recognition+dlib+OpenCV+MediaPipe+PyQt)

本博客为人脸识别系统项目简介 项目GitHub完整源代码地址&#xff1a; 一、运行环境 本系统能够运行在基于PC操作系统Windows环境下&#xff0c;要求Windows操作系统安装Python 3.9 及以上环境&#xff0c;且已安装MySQL数据库。 Python3.9 安装&#xff1a;Python 3.9安装教程…

【UE】坦克开火

1. 添加开火的操作映射 2. 创建一个actor蓝图类&#xff0c;添加一个静态网格体组件 添加发射物移动组件 设置初始速度和最大速度 发射物重力范围设为0.05 添加音频组件 设置音效 3. 打开炮管的静态网格体 在插槽管理器中创建插槽 将创建的插槽放到炮口位置 4. 打开“BP_BaseT…

B-Tree (多路查找树)分析-20230503

B-Tree (多路查找树)学习-20230503 前言 B-树是一类多路查询树&#xff0c;它主要用于文件系统和某些数据库的索引&#xff0c;如果采用二叉平衡树访问文件里面的数据&#xff0c;最坏情况下&#xff0c;磁头可能需要进行O(h)次对磁盘的读写&#xff0c;其中h为树的高度&…

探究Android插件化开发的新思路——Shadow插件化框架

Shadow插件化框架是什么&#xff1f; Shadow是一种Android App的插件化框架&#xff0c;它利用类似于ClassLoader的机制来实现应用程序中的模块化&#xff0c;并让这些模块可以在运行时灵活地进行加载和卸载。Shadow框架主张将一个大型的Android App拆分成多个小模块&#xff…

提升V-Ray渲染效率的五个实用技巧!

Chaos V-Ray是一个 3D渲染插件&#xff0c;可用于所有主要的 3D设计和 CAD程序。V-Ray可以和 3ds Max、Cinema 4D、Houdini、Maya、Nuke、Revit、Rhino、SketchUp和 Unreal无缝协作。艺术家和设计师也可以通过使用 V-Ray的实时光线来追踪探索和分享自己的项目&#xff0c;并渲染…

SpringBoot调取OpenAi接口实现ChatGpt功能

很高兴和大家分享我实现的一个小项目&#xff0c;利用 Spring Boot 实现了一个 ChatGpt 对话系统。在本文中&#xff0c;我将详细介绍这个项目的实现步骤&#xff0c;以及代码实现。 什么是 ChatGpt ChatGpt 是一种基于 GPT 技术的对话系统&#xff0c;能够生成连贯、流畅、…

V-Ray怎么快速渲染_渲染加速小技巧

很多小伙伴在使用V-Ray渲染器的时候都想要更加快速的出图&#xff0c;今天Renderbus瑞云渲染就给大家分享一下V-Ray渲染加速的小技巧。 在了解渲染加速技巧之前&#xff0c;我们首先要了解渲染制作主要受两个因素影响——图像质量和渲染时间&#xff0c;高图像质量是以长渲染时…

k8s集群搭建(3主2从)

目录 kubeadm 和二进制安装 k8s 适用场景分析 多 master 节点高可用架构图 集群环境准备 部署过程 修改主机内核参数&#xff08;所有节点&#xff09; 配置阿里云的repo源&#xff08;所有节点&#xff09; 配置国内安装 docker 和 containerd 的阿里云的 repo 源 配置…

day11 TCP连接管理与UDP协议

目录 ​编辑 连接的建立——”三次握手” 连接的释放——“四次挥手” 保活计时器 用户数据报协议 UDP​编辑 连接的建立——”三次握手” TCP 建立连接的过程叫做握手。 采用三报文握手&#xff1a;在客户和服务器之间交换三个 TCP 报文段&#xff0c;以防止已失效的连接…

SPSS如何进行多重响应分析之案例实训?

文章目录 0.引言1.多重响应变量频率分析2.对多重响应变量集进行交叉表分析3.使用表过程研究多重响应变量集 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记…

对模式的迷信,大部分是幻觉和妄想

对模式的迷信&#xff0c;大部分是幻觉和妄想 往往有严重的投机心理 郑翔洲&#xff0c;所谓模式设计专家 还是有点虚&#xff0c;仅供一点参考 说苹果、说华为、说小米这些总结都是事后诸葛亮 趣讲大白话&#xff1a;商业模式被妖魔化 【趣讲信息科技153期】 ****************…

从零构建等保三级|安全狗一站式等保解决方案助用户快速过等保

一 政策法规下的等级保护 “没有网络安全就没有国家安全&#xff0c;没有信息化就没有现代化。” 在2017年6月1日&#xff0c;我国开始全面实行《网络安全法》&#xff0c;其中第二十一条规定了“国家实行网络安全等级保护制度”。此外&#xff0c;在2019年5月13日&#xff…