java --- 多线程

发布时间:2023年12月20日

目录

一、java多线程的三种实现方式

1.1? 多线程的第一种实现方式:继承Thread类

?1.2? 多线程的第二种实现方式:Runnable接口

1.3 多线程的第三种实现方式:Callable接口和Future接口

1.3? 多线程三种实现方式的对比

二、线程常用的成员方法

2.1? 设置/获取线程name、sleep线程

2.2 线程优先级

2.3 守护线程(备胎线程)

三、线程安全问题

3.1 同步代码块

3.2 同步方法

3.3 lock锁

四、死锁

五、生产者消费者模型(等待唤醒机制)

六、线程池

6.1 实现方法:

6.2 自定义线程池


在之前,已经学习了C++的多线程编程,现在一起来看一下java是怎么实现的吧

Linux之多线程概念&线程控制_linux 线程执行到某一函数时,强制另一个线程启动-CSDN博客

一、java多线程的三种实现方式

  1. 继承Thread类的方式进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用Callable接口Future接口的方式进行实现

1.1? 多线程的第一种实现方式:继承Thread类

?1.2? 多线程的第二种实现方式:Runnable接口

多线程第二种实现方式:

  1. 自己手动定义一个类去实现Runnable接口
  2. ?重写里面的run方法
  3. 创建自己的类的对象。
  4. ?创建一个Thread类的对象,并开启线程。

1.3 多线程的第三种实现方式:Callable接口和Future接口

线程第三种实现方式:

  1. 创建一个类MyCallable实现Callable接口。
  2. 重写里面的call方法。( 返回值表示多线程运行结果 )
  3. 创建MyCallable的对象。( 表示多线程要执行的任务 )
  4. 创建FutureTask的对象。( 作用管理多线程运行的结果 )
  5. 创建Thread类的对象,并启动线程。( 表示线程 )?

特点:?可以获取到多线程运行的结果。

1.3? 多线程三种实现方式的对比

优点缺点
继承Thread类变成比较简单,可以直接使用Thread类中的方法可以扩展性较差,不能再继承其他的类
实现Runnable扩展性强,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法
实现Callable接口

二、线程常用的成员方法

方法名称说明
String?getName?( )返回此线程的名称
void?setName?( String name )设置线程的名字(构造方法也可以设置名字)
static?Thread?currentThread?( )获取当前线程的对象
static?void?sleep?( long time )让线程休眠指定的时间,单位为毫秒
setPriority?(int newPriority )设置线程的优先级
final int?getPriority?( )获取线程的优先级
final void?setDaemon?( boolean on )设置为守护线程
public static void?yield?( )出让线程 / 礼让线程
public static void?join?( )插入线程 / 插队线程

2.1? 设置/获取线程name、sleep线程

public class ThreadDeom {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();

        //在不给线程设置名字的时候,线程也是有默认的名字的
        //System.out.println(mt1.getName());
        mt1.setName("火车");
        mt2.setName("飞机");

        //mt1.start();
        //mt2.start();

        //获取的就是main线程的名字
        System.out.println(Thread.currentThread().getName());

        System.out.println("hhhhhhhhhhhhhh");
        Thread.sleep(1000);//按Alt + Enter直接选择抛出异常
        System.out.println("hhhhhhhhhhhhhh");
    }
}

2.2 线程优先级

  • 抢占式调度:CPU执行每一条的线程的时机和执行时间都是不确定的。
  • 非抢占式调度:所有的线程轮流进行,执行时间是差不多的。
System.out.println(mt1.getPriority());
        System.out.println(mt2.getPriority());
        
        mt1.setPriority(10);
        
        mt2.setPriority(1);

2.3 守护线程(备胎线程)

final void?setDaemon?( boolean on )

三、线程安全问题

Java的线程不安全和C++的是一样的,都是因为多个线程同时访问同一个临界资源

解决办法也是引入锁,Java的锁是synchronized

synchroize(){

}

3.1 同步代码块

3.2 同步方法

就是把synchronized关键字加到方法上

  • 特点1? :?同步方法是锁住方法里面的所有代码
  • 特点2 :?锁对象不能自己指定。?

3.3 lock锁

这个和C++的使用方法几乎是一样的

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

Lock中提供了获得锁和释放锁的方法:

成员方法说明
void?lock?( )获得锁
void?unlock?( )释放锁

Lock是接口不能直接实例化,这里采用它的实现类 ReentrantLock 实例化。

构造方法说明
ReentrantLock ( )创建一个? ReentrantLock 的实例

四、死锁

java的死锁和C++一样的原因

Linux 多线程安全之----死锁问题_linux 多个 mutex 锁住一个资源出现死锁-CSDN博客

五、生产者消费者模型(等待唤醒机制)

常见方法:

成员方法说明
void?wait?( )?当前线程等待,直到被其他线程唤醒
void?notify?( )所及唤醒单个线程
void?notifyAll?( )唤醒所有线程

例子:

生产者代码

//Desk.java
public class Desk {
	// 作用: 控制生产者和消费者的执行
	
	//判断桌子上是否有面条: 0:没有 ; 1:有
	public static int foodFlag = 0;
	
	//定义总个数
	public static int count = 10;
	
	//锁对象
	public static Object lock = new Object();
}
 
 
//Foodie.java
public class Foodie extends Thread {
	@Override
 
	public void run() {
 
		// 1.循环
		while (true) {
			// 同步代码块
			synchronized (Desk.lock) {
				if (Desk.count == 0) {
					break;
				} else {
					// 先判断桌子上是否有面条
					if (Desk.foodFlag == 0) {
						// 没有:等待
						try {
							Desk.lock.wait(); // 让当前线程与锁进行绑定
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					} else {
						// 把吃的总数- 1
					Desk.count--;
						// 有: 开吃
					System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗");
						// 吃完之后:唤醒厨师继续做
					Desk.lock.notifyAll();
 
						// 修改桌子的状态
					Desk.foodFlag = 0;
					}
 
				}
			}
		}
	}
}

消费者代码

//Desk.java
public class Desk {
	// 作用: 控制生产者和消费者的执行
	
	//判断桌子上是否有面条: 0:没有 ; 1:有
	public static int foodFlag = 0;
	
	//定义总个数
	public static int count = 10;
	
	//锁对象
	public static Object lock = new Object();
}
 
 
//Foodie.java
public class Foodie extends Thread {
	@Override
 
	public void run() {
 
		// 1.循环
		while (true) {
			// 同步代码块
			synchronized (Desk.lock) {
				if (Desk.count == 0) {
					break;
				} else {
					// 先判断桌子上是否有面条
					if (Desk.foodFlag == 0) {
						// 没有:等待
						try {
							Desk.lock.wait(); // 让当前线程与锁进行绑定
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					} else {
						// 把吃的总数- 1
					Desk.count--;
						// 有: 开吃
					System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗");
						// 吃完之后:唤醒厨师继续做
					Desk.lock.notifyAll();
 
						// 修改桌子的状态
					Desk.foodFlag = 0;
					}
 
				}
			}
		}
	}
}

六、线程池

之前多线程编程的弊端还是挺大的:

弊端一:用到线程的时候就要创建弊端二:用完之后线程消失

因此,我们我们引入线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。

  1. 创建一个池子,池子中是空的。
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子;下次再次提交任务时,不需要创建新的的线程,直接复用已有的线程即可。
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。

6.1 实现方法:

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

方法名称说明
public static ExecutorService?newCachedThreadPool?( )

创建一个没有上限的线程池

public static ExecutorService?newFixedThreadPool?( int nThreads )创建有上限的线程池

6.2 自定义线程池

  • 核心元素一:核心线程的数量不能小于0
  • 核心元素二:线程池中最大线程的数量(最大数量>=核心线程数量)
  • 核心元素三:空闲时间(值)(不能小于0
  • 核心元素四:空闲时间(单位)(用TimeUnit指定
  • 核心元素五:堵塞队列(不能为null
  • 核心元素六:创建线程的方式(不能为null
  • 核心元素七:要执行的任务过多时的解决方案(不能为null

不断的提交任务,会有以下三个临界点:

  1. 当核心线程满时,再提交任务就会排队。
  2. 当核心线程满、队列满时,再来任务就会创建临时线程
  3. 核心线程、队列、临时线程都满,再来任务会被拒绝

6.3 最大并行数

CPU密集型运算

(读取文件操作比较少)

I/O密集型运算

(读取文件操作比较多)

查看最大并行数

public class MyThreadPoolDemo {
	public static void main(String[] args) {
		//向Java虚拟机返回可用处理器的数目
		int count = Runtime.getRuntime().availableProcessors();
		System.out.println(count); //12
	}
}
文章来源:https://blog.csdn.net/flyingcloud6/article/details/135062272
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。