java --- 多线程

目录

一、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类

java --- 多线程_第1张图片

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

多线程第二种实现方式:

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

java --- 多线程_第2张图片

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

线程第三种实现方式:

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

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

java --- 多线程_第3张图片

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 --- 多线程_第4张图片

三、线程安全问题

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

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

synchroize(){

}

java --- 多线程_第5张图片

3.1 同步代码块

java --- 多线程_第6张图片

3.2 同步方法

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

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

java --- 多线程_第7张图片

3.3 lock锁

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

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

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

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

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

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

java --- 多线程_第8张图片

四、死锁

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

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

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

常见方法:

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

例子:

java --- 多线程_第9张图片

生产者代码

//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 ) 创建有上限的线程池

java --- 多线程_第10张图片

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
	}
}

你可能感兴趣的:(java,java,开发语言)