Java-day14(多线程)

2023-09-15 18:00:00

多线程

0.基本概念

程序:为完成特定任务,用某种编程语言编写的一组指令的集合(静态

进程:程序的一次执行过程,或正在执行的一个程序(动态过程)

线程:程序内部的一条执行路径,若某个程序支持同一时间执行多个线程,即支持多线程

1.多线程的创建和使用

继承Thread类创建多线程
  • 一个线程只能执行一次start()
  • 不能通过Thread类的run方法去启动一个线程

例:

package thead;  

//1.创建继承于Thread的子类
class SubThread extends Thread{
	//2.重写Thread类的run方法,方法内实现此子类线程要完成的功能
	public void run() {
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i); 
		}
	}
}
public class test1 {

	public static void main(String[] args) {
		//4.创建子类对象
		SubThread s = new SubThread();
		//5.调用start()方法,启动线程;再调用相应的run方法
		s.start();
		
		//主线程执行
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		
	}
}

实现Runnable接口创建多线程
package thead; 
//创建多线程的方式二:通过实现的方式
//1.创建实现Runnable接口的类
class PrintNum implements Runnable{
	//2.重写run的方法
	public void run() {
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}
public class test4 {

	public static void main(String[] args) {
		//3.创建一个Runnable接口的实现类的对象
		PrintNum p = new PrintNum();
		//4.将此对象作为形参传递给Thread类的构造器中
		Thread t1 = new Thread(p);//想启动多线程,必须调用start()
		t1.start();
		
		Thread t2 = new Thread(p);
		t2.start();
		

	}

}

继承Thread类与实现Runnable接口的区别

  • 1.都继承了Runnable接口(Thread类实现Runnable接口)
  • 2.实现的方式优于继承的方式(避免Java单线程的局限性;如果多个线程要操作同一份资源(或数据),更合适使用实现的方式)

案例:模拟火车站售票,开启三个窗口,总票数100张

继承

package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window extends Thread{
	static int ticket = 100; //总票数
	public void run() {		
		while(true) {
			if(ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
			}else {
				break;
			}
		}
		
	}
}
public class test1 {

	public static void main(String[] args) {
		Window w1 = new Window();
		Window w2 = new Window();
		Window w3 = new Window();
		
		w1.setName("1号");
		w2.setName("2号");
		w3.setName("3号");
		
		w1.start();
		w2.start();
		w3.start();
	}
}

实现

package project; 
//模拟火车站售票,开启三个窗口,总票数100张
class Window1 implements Runnable{
	int ticket = 100; //总票数
	public void run() {		
		while(true) {
			if(ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
			}else {
				break;
			}
		}	
	}
}
public class test2 {

	public static void main(String[] args) {
		Window1 w = new Window1();
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("1号");
		t2.setName("2号");
		t3.setName("3号");
		
		t1.start();
		t2.start();
		t3.start();

	}
}
多线程的优点
  • 1.提高应用程序的响应,对图形化界面更有意义,可增强用户体验
  • 2.提供计算机CPU利用率
  • 3.改善程序结构,将复杂的进程分为多个线程,独立运行,利于理解和修改
Thread类的主要方法与配置线程优先级

在这里插入图片描述
在这里插入图片描述

package thead;          
/*
 * 线程的主要方法
 * 1.start():启动线程并执行相应的run()方法
 * 2.run():子线程要执行的代码放入run()方法中
 * 3.currentThread():静态的,调取当前的线程
 * 4.getName():获取线程的名字
 * 5.setName():设置线程名
 * 6.yield():调用此方法的线程释放当前CPU的执行权
 * 7.join():在A线程中调用B线程的join()方法,当执行到此方法时,A停止,B执行,等B全部执行完后,A再继续执行剩下的任务。
 * 8.isAlive():检测线程是否存活
 * 9.sleep(long l):显式的让当前线程睡眠l毫秒
 * 10.线程通信:wait()  notify()  notifyAll()后续介绍
 * 11.线程优先级(默认5,最大10,最小1):只是提高了抢到的CPU执行权的概率
 * 		getPriority():返回线程的优先级
 * 		setPriority(int newPriority):改变线程的优先级
 */

class SubThread1 extends Thread{
	public void run() {
		
		for(int i = 1;i <= 100;i++) {
//			try {   
//				Thread.currentThread().sleep(1000);
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
			System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
		}
	}
}
public class test2 {

	public static void main(String[] args) {
		SubThread1 s1 = new SubThread1();
		s1.setName("线程1号");
		s1.setPriority(Thread.MAX_PRIORITY);
		s1.start();
		
		Thread.currentThread().setName("======主线程=====");
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
//			if(i % 10 == 0) {
//				Thread.currentThread().yield();
//			}
//			if(i == 10) {
//			try { 
//				s1.join();
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//		}
			
		}
		System.out.println(s1.isAlive());
		
	}
}

练习
在这里插入图片描述

package thead;       

class SuThread1 extends Thread{
	//1-100奇数
	public void run() {
		for(int i = 1;i <= 100;i = i + 2) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
		}
	}
}

class SuThread2 extends Thread{
	//1-100偶数
	public void run() {
		for(int i = 2;i <= 100;i = i + 2) {
			System.out.println(Thread.currentThread().getName()+ " : "  + i); 
		}
	}
}

class test3{
	public static void main(String[] args) {
		SuThread1 st1 = new SuThread1();
		SuThread2 st2 = new SuThread2();
		st1.setName("子线程1号");
		st1.start();
		st2.setName("子线程2号");
		st2.start();
		
//		//继承Thread类的匿名类的对象
//		new Thread() {
//			public void run() {
//				for(int i = 2;i <= 100;i = i + 2) {
//					System.out.println(Thread.currentThread().getName()+ " : "  + i); 
//				}
//			}
//		}.start();
	}
}

2.线程的生命周期

生命周期中通常要经历的5种状态
新建:当一个Thread类或子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备运行的条件
运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成全部工作或线程被提前强制中止
在这里插入图片描述

3.线程的同步(重点)

在上面列举的火车售票案例中存在售错票,重票等的情况,存在线程安全问题
原因:由于线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享的数据存在安全问题
解决:必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作
Java利用线程同步来解决线程安全问题

方法一:同步代码块
synchronized(同步监视器){
	//需要同步的代码(即为操作共享数据的代码);
	}
  • 1.同步监视器:由一个任意类的对象来充当。哪个线程获此监视器,谁就执行括号中被同步的代码。俗称:锁
  • 2.共享数据:多线程共同操作的同一个数据(变量)
  • 要求:所有的线程必须共用同一把锁

例:

package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window2 implements Runnable{
	int ticket = 100; //总票数(共享数据)
	Object obj = new Object();
	public void run() {		
		while(true) {
			synchronized(obj){
			if(ticket > 0) {
				try {
					Thread.currentThread().sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
			}else {
				break;
			}
		}
	}
	}
}
public class test4 {

	public static void main(String[] args) {
		Window2 w = new Window2();
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("1号");
		t2.setName("2号");
		t3.setName("3号");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

注:在实现方式中可以用this表示当前对象(同步监视器),若在继承的方式中慎用this

方法二:同步方法
public synchronized void show() {
	//需要同步的代码(即为操作共享数据的代码);
	}

例:

package project;  
class Window3 implements Runnable{
	int ticket = 100; 
	public void run() {		
		while(true) {
			show();
		}
	}
	public synchronized void show() {	
		if(ticket > 0) {
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
		}
		
	}
}
public class test3 {
	public static void main(String[] args) { 
		Window3 w = new Window3();
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("1号");
		t2.setName("2号");
		t3.setName("3号");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
互斥锁

在这里插入图片描述

单例之懒汉式的线程安全

例:

package thead; 
/* 单例之懒汉式的线程安全:使用同步机制 */
//对于静态方法而言,使用当前类本身充当锁
class Singletion{
	private Singletion() {
		
	}
	private static Singletion instance = null;
	public static Singletion getInstance() {
		if(instance == null) {
			synchronized(Singletion.class) {
				if(instance == null) {
					instance = new Singletion();
				}
			}
		}
		return instance;
	}
}
public class test5 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Singletion s1 = Singletion.getInstance();
		Singletion s2 = Singletion.getInstance();
		System.out.println(s1 == s2);
	}
}
  • 线程同步的弊端:同一时间只能有一个线程访问共享数据,效率变低

在这里插入图片描述
在这里插入图片描述

练习
在这里插入图片描述

package project;
//线程安全练习
//存在多线程,存在共享数据,需同步
class  Account{
	double balance;//余额
	public Account() {
		
	}
	//存取
	public synchronized void deposit(double amt) {
		balance += amt;
		try {
			Thread.currentThread().sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + balance );
	}
}
class Customer extends Thread{
	Account account;
	public Customer(Account account) {
		this.account = account;
	}
	
	public void run() {
		for(int i = 0;i < 3;i++) {
		account.deposit(1000);
		}
	}
}

public class test5{
public static void main(String[] args) {
	Account acct = new Account();
	Customer c1 = new Customer(acct);
	Customer c2 = new Customer(acct);
	
	c1.setName("甲");
	c2.setName("乙");
	
	c1.start();
	c2.start();
	
	
}
}
线程死锁

在这里插入图片描述
例:

package thead;
/* 死锁:处理线程同步时容易出现 */
//一个线程抓住一半锁,另一个线程抓住一半锁,互不相让
public class test6 {
	static StringBuffer sb1 = new StringBuffer();
	static StringBuffer sb2 = new StringBuffer();

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				synchronized(sb1) {
					try {
					Thread.currentThread().sleep(10);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
				}
				sb1.append("A");
				synchronized(sb2){
					sb2.append("B");
					System.out.println(sb1);
					System.out.println(sb2);
				}
			}
		}.start();
	
	new Thread() {
		public void run() {
			synchronized(sb2) {
				try {
				Thread.currentThread().sleep(10);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
			}
			sb1.append("C");
			synchronized(sb1){
				sb2.append("D");
				System.out.println(sb1);
				System.out.println(sb2);
				}
			
			}		
		}.start();
}
}

4.线程的通信

wait()
让当前线程挂起并放弃CPU,同步资源,使用别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问

notify()
唤醒正在排队等待同步资源的线程中优先级最高者结束等待

ontifyAll()
唤醒正在排队等待资源的所有线程结束等待

以上三个方法(Java.lang.Object提供)只有在synchronized()方法或synchronized代码块中才能使用,否则会报异常

例1:

package thead;  
/* 两个线程交替打印1~100的数 */
//线程通信:wait(),让当前线程停止;notify(),notifyAll()唤醒wait()停止的1个或多个线程
class PrintfNum implements Runnable{
	int num = 1;
	public void run() {
		while(true) {
			synchronized (this) {//解决线程安全问题
				notify();
				if (num <= 100) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} //使隐藏的线程安全问题暴露处理
					System.out.println(Thread.currentThread().getName() + ":" + num);
					num += 1;
				} else {
					break;
				}
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
public class test7 {

	public static void main(String[] args) {
		PrintfNum p = new PrintfNum();
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(p);
		
		t1.setName("A");
		t2.setName("B");
		
		t1.start();
		t2.start();
		

	}

}
生产者/消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走。店员一次只能持有一定数量的产品(如:20).如果生产者试图多生产会被叫停,如果店中有空位才会通知生产者生产;如果店中没有产品,店员会告诉消费者等一下,有产品后在通知消费者取走产品。
:可能会出现的问题:

  • 1.生产者比消费者快,消费者会漏掉一些数据没有取到

  • 2.消费者比生产者快,消费者会取到相同的数据

package thead; 
//生产者/消费者问题
//多线程:生产者,消费者;共享数据:产品数量;存在数据通信


//店员
class Clerk{
	int product;     
	public synchronized void addProduct() {
		if(product < 20) {
			product += 1;
			System.out.println(Thread.currentThread().getName() + ":生产到第" + product + "产品了!" );
			notify();
		}else
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	public synchronized void consumProduct() {
		if(product <= 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			System.out.println(Thread.currentThread().getName() + ":消费到第" + product + "产品了!" );
			product -= 1;
			notify();
		}
	}
}
//生产者
class Producer implements Runnable{
	Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}
	
	public void run() {
		System.out.println("生产者开始生产产品");
		while(true) {
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	}
}

//消费者
class Customer implements Runnable{
Clerk clerk;
	
	public Customer(Clerk clerk) {
		this.clerk = clerk;
	}
	
	public void run() {
		System.out.println("消费者开始消费产品");
		while(true) {
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.consumProduct();
		}
	}
}


public class test8 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Clerk clerk = new Clerk();
		Producer producer = new Producer(clerk);
		Customer customer = new Customer(clerk);
		Thread t1 = new Thread(producer);//一个生产者线程
		Thread t3 = new Thread(producer);//一个生产者线程
		Thread t2 = new Thread(customer);//一个生产者线程
		
		t1.setName("生产1号");
		t2.setName("消费者");
		t3.setName("生产2号");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

练习
在上次练习的基础上,实现交替存款

package project;
//线程安全练习
//存在多线程,存在共享数据,需同步
//扩展实现交替打印

class  Account{
	double balance;//余额
	public Account() {
		
	}
	//存取
	public synchronized void deposit(double amt) {
		notify();
		balance += amt;
		try {
			Thread.currentThread().sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + balance );
		try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
class Customer extends Thread{
	Account account;
	public Customer(Account account) {
		this.account = account;
	}
	
	public void run() {
		for(int i = 0;i < 3;i++) {
		account.deposit(1000);
		}
	}
}

public class test5{
public static void main(String[] args) {
	Account acct = new Account();
	Customer c1 = new Customer(acct);
	Customer c2 = new Customer(acct);
	
	c1.setName("甲");
	c2.setName("乙");
	
	c1.start();
	c2.start();
	
	
}
}

感谢大家的支持,关注,评论,点赞!
参考资料:
尚硅谷宋红康20天搞定Java基础下部

更多推荐

算法基础之二分查找

原题链接一、二分查找中的mid+1和mid-1的问题二分查找中的边界问题处理不好很容易导致死循环和计算错误的问题,以题目数的范围为例。题目大意​二分查找重复数第一次出现的位置和最后一次出现的位置。数学含义​第一次位置即找到一个长度最大的>=X区间的左边界​最后一次位置即找到一个长度最大的>=X区间的右边界注意找的目标是

大数据——Spark SQL

1、SparkSQL是什么SparkSQL是Spark中用于处理结构化数据的一个模块,前身是Shark,但本身继承了前身Hive兼容和内存列存储的一些优点。SparkSQL具有以下四个特点:综合性(Integrated):Spark中可以加入SQL查询,也可以使用DataFrameAPI,其中API提供了多种语言选择,

[C++随笔录] vector模拟实现

vector模拟实现基本结构天选之子构造拷贝构造析构operator=空间reserveresizesize&&capacity增insertpush_back删erasepop_back查&&改swapoperator[]源码基本结构//可以是不同类型,用类模板template<classT>classvector{

产品解读 | 分布式多模数据库:KaiwuDB

1.KaiwuDB是什么?KaiwuDB是由浪潮创新研发的一款分布式、多模融合,支持原生AI的数据库产品,拥有“就地计算”等核心技术,具备高速写入、极速查询、SQL支持、随需压缩、智能预计算、订阅发布、集群部署等特性,具有稳定安全、高可用、易运维等特点。2.KaiwuDB设计理念在当今数据爆炸的时代,企业和组织面临着如

进程间通信

#include<unistd.h>intpipe(intpipefd[2]);功能:创建一个匿名管道,用于进程间通信参数:-intpipefd[2]:传出参数pipefd[0]对应的是管道的读端pipefd[0]对应的是管道的写端返回值:成功返回0,失败返回-1管道默认是阻塞的,如果管道中没有数据,read阻塞,如果

聊聊API安全的重要性及治理思路

在应用程序开发过程中,API是一个会被经常提及的东西,它的全称是ApplicationProgrammingInterface(应用程序接口),一般指的是WebAPI,即:采用HTTP通信协议的API或者是Web应用程序对外提供的API。API从狭义上可以理解为是一种服务能力,调用方可以利用API很便捷的得到一组相关数

竞赛选题 基于深度学习的植物识别算法 - cnn opencv python

文章目录0前言1课题背景2具体实现3数据收集和处理3MobileNetV2网络4损失函数softmax交叉熵4.1softmax函数4.2交叉熵损失函数5优化器SGD6最后0前言🔥优质竞赛项目系列,今天要分享的是🚩**基于深度学习的植物识别算法**该项目较为新颖,适合作为竞赛课题方向,学长非常推荐!🥇学长这里给一

阿里云无影云电脑角色AliyunServiceRoleForGws什么意思?

阿里云无影云电脑服务关联角色是指角色名称:AliyunServiceRoleForGws,并赋予角色权限策略:AliyunServiceRolePolicyForGws的过程,简单来说,就是允许无影云电脑服务访问您VPC、CEN和NAS中的资源,使用该权限查询实例,管理无影云电脑办公网络。创建无影云电脑之前需要先为无影

ELK 企业级日志分析系统

目录1、ELK概述1.1ELK简介1.2为什么要使用ELK1.3完整日志系统基本特征1.4ELK的工作原理2、ELKElasticsearch集群部署(在Node1、Node2节点上操作)2.1环境准备2.2部署Elasticsearch软件2.3安装Elasticsearch-head插件3、ELKLogstash部

【QT】day2

1.完善登录框点击登录按钮后,判断账号(admin)和密码(123456)是否一致,如果匹配失败,则弹出错误对话框,文本内容“账号密码不匹配,是否重新登录”,给定两个按钮ok和cancel,点击ok后,会清除密码框中的内容,继续进行登录;如果点击cancel按钮,则关闭界面。如果账号和密码匹配,则弹出信息对话框,给出提

spring 拦截器

Spring拦截器是在处理请求的过程中,可以在特定的时机对请求进行一些处理,比如记录日志、进行权限校验、统计请求时间等。实现步骤:创建一个拦截器类,实现HandlerInterceptor接口,并重写其方法。在Spring配置文件中添加拦截器配置,可以配置拦截的URL,也可以对所有URL进行拦截。在拦截器的方法中编写拦

热文推荐