Java多线程与并发编程
1.必须知道的概念
1.1程序、进程与线程
- 程序是静态的概念,windows下通常指exe文件
- 进程是动态的概念,是程序在运行状态,进程说明程序在内存中的边界(进程与进程之间彼此隔离互不影响)
- 线程是进程内的一个"基本任务",每个线程都有自己的功能,是CPU分配与调度的基本单位
1.2并发和并行
- 并发 : 同一时间做的事情来回交替(多个线程之间交替运行 时间片)
- 并行 : 真正的从物理层面上并行交替(多核CPU 基于线程执行)
1.3同步和异步
1.4临界区
- 临界区用来表示一种公共资源与数据共享,可以被多个线程使用
- 同一时间只能有一个线程访问临界区(阻塞状态)其他资源必须等待
1.5 死锁、饥饿、活锁
1.6线程安全
- 在拥有共享数据的多条线程并行执行的程序中,线程安全的代码吗会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况(多线程和单线程的执行结果保持一致)
2.Java内存模型
Java Memory Model
2.1 示例代码
package com.san.employee.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author ZhangSan_Plus
* @version 1.0
* @className Employee
* @description TODO
* @date 2021/3/15 14:18
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private String name;
private Integer age;
private Department department;
public void sayJoke(String context) {
System.out.println(this.getName() + "说:" + context);
}
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("张三");
employee.setAge(19);
Department department = new Department();
department.setDName("技术研发部");
employee.setDepartment(department);
employee.sayJoke("一言不合就开车");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Department {
private String dName;
}
2.2 执行过程
- 加载类(ClassLoader)
- 方法区(静态区)
- Employee类结构
- Department类结构
- Employee.main()
- 方法区(静态区)
- 启动main
- 创一个线程(栈区域)
- 栈帧-main() Employee employeeDepartment department
- employee=new Employee
- 在堆(Heap)中加入[Employee name age department ]
- 栈帧中的Employee 持有该堆中Employee的引用
- employee.setName(“张三”);
- 创建setName栈帧
- 在静态区中加入"张三"
- 并使堆中的name属性引用到方法区中的"张三" 字符串的地址
- 执行完毕后setName在栈中消失
- employee.setAge(“19”);
- 创建setAge栈帧
- 在静态区中加入19
- 并使堆中的age属性引用到方法区中的 19 字符串的地址
- 执行完毕后setAge在栈中消失
- department = new Department();
- 在堆(Heap)中加入[Department dName ]
- 栈帧中的Department持有该堆中Department的引用
- department .setDName(“技术研发部”);
- 创建setDName栈帧
- 在静态区中加入"技术研发部"
- 并使堆中的dName 属性引用到方法区中的 “技术研发部” 字符串的地址
- 执行完毕后setDName在栈中消失
- employee.setDepartment(department);
- department 引用Department对象的地址
- employee.sayJoke(“一言不合就开车”);
- 创建sayJoke栈帧
- 在静态区中加入"一言不合就开车"
- sayJoke 加入 "一言不合就开车"的引用
- 创建getName栈帧 ->进行赋值
- 创建println栈帧
- 在静态区中生成一个"张三说:一言不合就开车"的字符串
- println对"张三说:一言不合就开车"进行引用
- println 栈帧被释放
- getName栈帧被释放
- sayJoke栈帧被释放
- main方法栈帧消失
- 栈销毁->线程销毁
3.创建线程的三种方式
创建线程中的三种方式
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 使用Callable和Future创建线程
3.1 继承Thread类
package com.san.employee.match;
import lombok.SneakyThrows;
import java.util.Random;
/**
* @author ZhangSan_Plus
* @version 1.0
* @className Match1
* @description TODO
* @date 2021/3/15 14:55
**/
public class Match1 {
public static void main(String[] args) {
Runner runner = new Runner();
runner.setName("张三");
Runner runner1 = new Runner();
runner1.setName("刘翔");
Runner runner2 = new Runner();
runner2.setName("路飞");
runner.start();
runner1.start();
runner2.start();
}
}
class Runner extends Thread {
@SneakyThrows(value = {InterruptedException.class})
@Override
public void run() {
Integer speed = new Random().nextInt(100);
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(this.getName() + ": 已前进" + (i * speed) + "米" + "===(" + speed + "米/秒)");
}
}
}
上述代码中会创建几个线程呢?
答:5个线程(main,张三,刘翔,路飞,垃圾回收)
3.2 实现Runnable接口
package com.san.employee.match;
import lombok.SneakyThrows;
import java.util.Random;
/**
* @author ZhangSan_Plus
* @version 1.0
* @className Match2
* @description TODO
* @date 2021/3/15 16:02
**/
public class Match2 {
public static void main(String[] args) {
Runner2 runner = new Runner2();
Thread thread = new Thread(runner);
thread.setName("张三");
Runner2 runner1 = new Runner2();
Thread thread1 = new Thread(runner1);
thread1.setName("刘翔");
Runner2 runner2 = new Runner2();
Thread thread2 = new Thread(runner2);
thread2.setName("路飞");
thread.start();
thread1.start();
thread2.start();
}
}
class Runner2 implements Runnable {
@SneakyThrows(value = {InterruptedException.class})
@Override
public void run() {
Integer speed = new Random().nextInt(100);
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": 已前进" + (i * speed) + "米" + "===(" + speed + "米/秒)");
}
}
}
3.3 实现Callable接口
并发工具包
- JDK1.5以后专门为我们提供了一个并发工具包java.util.concurrent
- java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建快。创建concurrent的目的就是要实现Collection框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高类的线程安全、可伸缩性、性能、可读写性和可靠性
package com.san.employee.match;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author ZhangSan_Plus
* @version 1.0
* @className Match3
* @description TODO
* @date 2021/3/15 16:40
**/
public class Match3 {
@SneakyThrows(value = {Exception.class})
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(3,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Runner3 runner3 = new Runner3("张三");
Runner3 runner3x = new Runner3("李四");
Runner3 runner3xx = new Runner3("王五");
//线程池自动分配线程 Future对象用于接收线程内部call方法的返回值
Future<Integer> result1 = executorService.submit(runner3);
Future<Integer> result2 = executorService.submit(runner3x);
Future<Integer> result3 = executorService.submit(runner3xx);
Thread.sleep(3000);
System.out.println("张三累计跑了" + result1.get() + "米");
System.out.println("张三累计跑了" + result2.get() + "米");
System.out.println("张三累计跑了" + result3.get() + "米");
executorService.shutdown();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Runner3 implements Callable<Integer> {
private String name;
public static void main(String[] args) {
}
@Override
public Integer call() throws Exception {
Integer speed = new Random().nextInt(100);
Integer distinct = 0;
for (int i = 0; i < 10; i++) {
Thread.sleep(10);
distinct = i * speed;
System.out.println(this.getName() + " 已前进 " + distinct + "米(" + speed + "/秒)");
}
return distinct;
}
}
注:这里不推荐使用ExecutorService executorService1 = Executors.newFixedThreadPool(3);
这种方式来创建线程
可能会出现
- FixedThreadPool和SingleThreadExecutor => 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而引起OOM异常
- CachedThreadPool => 允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而引起OOM异常
3.4创建线程三种方式的对比
4. 代码中的同步机制
- synchronized(同步锁)关键字的作用就是利用一个对象设置一个锁lock,在多线程并发访问的时候,同时只允许一个线程可以获得这个锁,执行特定的代码。执行后释放锁,继续由其他线程争抢
4.1 Synchronize的使用场景
- Synchronize可以使用在以下三种场景,对应不同锁对象
- Synchronize代码块- 任意对象即可
- Synchronize 方法- this当前对象
- Synchronize 静态方法 -该类的字节码对象
package com.san.employee.test;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.Random;
import java.util.concurrent.ThreadFactory;
/**
* @author ZhangSan_Plus
* @version 1.0
* @className SyncSample
* @description TODO
* @date 2021/3/16 13:44
**/
public class SyncSample {
public static void main(String[] args) {
Couplet c = new Couplet();
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
for (int i = 0; i < 10000; i++) {
namedThreadFactory.newThread(() -> {
int r = new Random().nextInt(2);
if (r % 2 == 0) {
Couplet.first();
} else {
Couplet.second();
}
}).start();
}
}
}
class Couplet {
public synchronized static void first() {
System.out.printf("举");
System.out.printf("头");
System.out.printf("望");
System.out.printf("明");
System.out.printf("月");
System.out.println();
}
public static void second() {
synchronized (Couplet.class) {
System.out.printf("低");
System.out.printf("头");
System.out.printf("思");
System.out.printf("故");
System.out.printf("乡");
System.out.println();
}
}
}
5.线程的五种状态
- 新建(new)
- 就绪(ready)
- 运行中(running)
- 阻塞(blocked)
- 死亡(dead)
6.死锁的产生
6.1 死锁产生的原因
- 死锁是在多线程情况下最严重的问题,在多线程对公共资源(文件、数据)等进行操作时,彼此不释放自己的资源,而去试图操作其他线程的资源,而形成交叉引用,就会产生死锁
- 解决死锁最根本的建议是:
- 尽量减少公共资源的引用,用完马上释放
- 用完马上释放公共资源
- 减少Syncrhoized使用,采用"副本"方式代替
本文作者:张三Blog
本文链接:https://www.cnblogs.com/zhangsan-plus/p/16503280.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步