多线程
概述
进程:进程就是正在内存中运行的程序。当一个程序进入到内存运行,就变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能。
线程:线程就是进程中的执行单元,负责当前进程中程序的执行。一个进程中至少有一个线程,一个进程也可以有多个线程。只有一个线程的进程称之为单线程程序,多个线程的进程称之为多线程程序。
总结:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
抢占式调度
理论上是优先级高的线程抢占到CPU的概率大。如果线程的优先级相同,那么会随机选择一个线程来执行,这就是java的抢占式调度。
人类感觉多个线程在同时执行,但是实际上,CPU使用抢占式调度在多个线程间进行着高速的切换、对于CPU的一个核而言,某一个时刻,只能执行一个线程。而CPU在多个线程间切换的速度非常快,感觉上是同时执行。
其实多个线程并不能提高程序的运行速度,但是能够提高程序的运行效率。
多线程的优点:
- 提高用户体验
- 提高CPU的利用率
计算机执行任务是在CPU中进行的。每一个核在同一时刻内只能执行一个进程。
线程在执行的过程中会和计算机硬件进行交互。线程在和计算机硬件交互的时候会暂时空置CPU,所以多线程可以提高CPU的利用率。
线程的顶级父类是Thread类
主线程
main方法中有一个线程,称之为主线程。
创建线程
创建线程方式一
步骤:
- 定义一个类继承
- 重写run方法
- 创建子类对象,就是线程对象
- 调用start方法,开启线程让线程执行
方式一
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
}
方式二(推荐)
步骤:
- 定义类实现Runnable接口
- 重写接口中的run()方法
- 创建Thread类对象
- 将Runnable接口的子对象作为参数传递给Thread类的构造方法
- 调用Thread类方的start方法开启线程
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
}
public static void main() {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
//调用start方法开启线程
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程正在执行");
}
}
通过 匿名内部类 & Lambda表达式 创建
public static void method1() {
new Thread(new Runnable() {
int count = 0;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
count++;
System.out.println(Thread.currentThread().getName() + "正在执行" + count);
}
}
}, "新线程").start();
}
public static void method2() {
new Thread(() -> {
int count = 0;
for (int i = 0; i < 100; i++) {
count++;
System.out.println(Thread.currentThread().getName() + "正在执行" + count);
}
}).start();
}
案例
电影院在售”毒液3″的票,有3个窗口在同时售卖100张票,票卖完为止。编写程序模拟3个窗口卖票过程。 实现方式一
public class Ticket implements Runnable {
static int ticket = 100;
public void run() {
while (true) {
if (ticket <= 0) {
System.out.println(Thread.currentThread().getName() + "卖完了");
break;
}
System.out.println(Thread.currentThread().getName() + "正在买票" + ticket--);
}
}
public static int getTicket() {
return ticket;
}
public static void main(String[] args) {
//创建Ticket对象
Ticket ticket = new Ticket();
//创建三个窗口线程
Thread thread1 = new Thread(ticket, "窗口1");
Thread thread3 = new Thread(ticket, "窗口3");
Thread thread2 = new Thread(ticket, "窗口2");
//start()启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
实现方式二
public class Ticket {
static int ticket = 100;
public static void saleTicket() {
while (true) {
if (ticket <= 0) {
System.out.println(Thread.currentThread().getName() + "卖完了");
break;
}
System.out.println(Thread.currentThread().getName() + "正在买票" + ticket--);
}
}
public static int getTicket() {
return ticket;
}
public static void main(String[] args) {
// //创建Ticket对象
// Ticket ticket = new Ticket();
// //创建三个窗口线程
// Thread thread1 = new Thread(ticket, "窗口1");
// Thread thread3 = new Thread(ticket, "窗口3");
// Thread thread2 = new Thread(ticket, "窗口2");
// //start()启动线程
// thread1.start();
// thread2.start();
// thread3.start();
new Thread(Ticket::saleTicket, "窗口1").start();
new Thread(Ticket::saleTicket, "窗口2").start();
new Thread(Ticket::saleTicket, "窗口3").start();
}
}
案例发现问题:
可能会出现
- 有重复的票
- 有0的票
- 有-1的票
出现这种情况,就称之为线程安全隐患。
如果有多个程序在同时运行,而这些线程可能会同时运行这段代码。程序每次运行的结果和单线程运行结果一样,而且其他变量的值也和预期的一模一样,就是线程安全的。如果不一样,就是出现了线程安全隐患。
思考:什么时候会出现线程安全隐患?
- 多线程
- 有共享资源
- 有修改操作
以上三个条件同时出现,就会有线程安全隐患。
创建线程方式三
实现Callable接口
实现Callable接口
使用步骤:
- 定义类实现Callable接口
- 创建实现类的对象,传入FutureTask类的构造方法中
- 创建Thread类对象,把FutureTask类的对象通过构造方法传入
- 执行start方法
案例 转账
假设:张三 有2000元
李四对张三转1000
王五对张三转1500
张三妻子取钱1200
方式一
import java.math.BigDecimal;
import java.util.concurrent.Callable;
public class AccountDemo implements Callable
<BigDecimal> {
// 张三的账户余额
private static BigDecimal balance = new BigDecimal("2000");
// 转账和取钱的金额
private BigDecimal money;
// false转账 true取钱
private boolean flag = false;
public AccountDemo(BigDecimal money, boolean flag) {
this.money = money;
this.flag = flag;
}
@Override
public BigDecimal call() throws Exception {
if (!flag) {
//转账
System.out.println(Thread.currentThread().getName() + "转账:" + money);
balance = balance.add(money);
} else {
//取钱
if (money.compareTo(balance) > 0) {
throw new RuntimeException("余额不足");
}
balance = balance.subtract(money);
System.out.println(Thread.currentThread().getName() + "取钱" + money);
}
return balance;
}
}
import java.math.BigDecimal;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class AddUserTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 张三 有2000元
* 李四对张三转1000
* 王五对张三转1500
* 张三妻子取钱1200
*/
BigDecimal money1 = new BigDecimal("1000.00");
BigDecimal money2 = new BigDecimal("1500.00");
BigDecimal money3 = new BigDecimal("1200.00");
AccountDemo lisiCun = new AccountDemo(money1, false);
AccountDemo wangwuCun = new AccountDemo(money2, false);
AccountDemo wifeQu = new AccountDemo(money3, true);
FutureTask
<BigDecimal> task1 = new FutureTask<>(lisiCun);
FutureTask
<BigDecimal> task2 = new FutureTask<>(wangwuCun);
FutureTask
<BigDecimal> task3 = new FutureTask<>(wifeQu);
Thread thread1 = new Thread(task1, "李四");
Thread thread2 = new Thread(task2, "王五");
Thread thread3 = new Thread(task3, "wife");
thread1.start();
thread2.start();
thread3.start();
System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());
}
}
方式二
package cn.javasm.demo;
import java.math.BigDecimal;
/**
* @author gfs
* @version 0.1
* @className AccountDemo2
* @descriptioin:
* @date 2024/10/28 16:12
* @since jdk11
*/
public class AccountDemo2 {
// 账户余额
private static BigDecimal balance = new BigDecimal("2000");
/**
* 存钱
* @return
*/
public static BigDecimal cunMoney(BigDecimal money) {
if (money == null || money.doubleValue() < 0) {
throw new NullPointerException("数据不合法");
}
System.out.println(Thread.currentThread().getName() + "转账:" + money);
balance = balance.add(money);
return balance;
}
/**
* 取钱
* @return
*/
public static BigDecimal quMoney(BigDecimal money) {
if (money == null || money.doubleValue() < 0) {
throw new NullPointerException("数据不合法");
}
if (money.compareTo(balance) > 0) {
throw new RuntimeException("余额不足");
}
balance = balance.subtract(money);
System.out.println(Thread.currentThread().getName() + "取了" + money);
return balance;
}
}
package cn.javasm.demo;
import java.math.BigDecimal;
/**
* @author gfs
* @version 0.1
* @className AccountDemo2
* @descriptioin:
* @date 2024/10/28 16:12
* @since jdk11
*/
public class AccountDemo2 {
// 账户余额
private static BigDecimal balance = new BigDecimal("2000");
/**
* 存钱
* @return
*/
public static BigDecimal cunMoney(BigDecimal money) {
if (money == null || money.doubleValue() < 0) {
throw new NullPointerException("数据不合法");
}
System.out.println(Thread.currentThread().getName() + "转账:" + money);
balance = balance.add(money);
return balance;
}
/**
* 取钱
* @return
*/
public static BigDecimal quMoney(BigDecimal money) {
if (money == null || money.doubleValue() < 0) {
throw new NullPointerException("数据不合法");
}
if (money.compareTo(balance) > 0) {
throw new RuntimeException("余额不足");
}
balance = balance.subtract(money);
System.out.println(Thread.currentThread().getName() + "取了" + money);
return balance;
}
}
线程安全问题
同步代码块
在代码块声明上,加上synchronized
synchronized(锁对象){
可能会产生线程安全隐患的代码
}
在同步代码块中,同一时刻只能有一个线程执行。
线程抢占锁对象,抢到后执行同步代码块中的代码,其他线程等待释放锁资源,执行代码块之后所有线程重新抢占锁资源。
锁对象可以是任意的对象,但是要求多个线程是用的同一个锁对象。
 {
可能会产生线程安全问题的代码;
}
- 同步方法的锁对象是this
- 静态同步方法的锁对象是 类名.class
同步:一段代码在同一时刻只允许一个线程执行
异步:一段代码在同一时刻允许多个线程执行
同步一定是线程安全的
线程安全不一定同步
异步不一定线程不安全
线程不安全一定是异步的
ArrayList和LinkedList、HashMap是异步线程不安全的
Hashtable、Vector是同步线程安全的
ConcurrentHashMap 是异步线程安全的,采用了分桶锁的机制
死锁问题
由于锁的嵌套导致锁之间相互锁死的现象。
案例一
public class TestDemo3 {
static Printer printer = new Printer();
static Scanner scanner = new Scanner();
public static void main(String[] args) {
//先打印在扫描
new Thread(() -> {
synchronized (printer) {
printer.print();
synchronized (scanner) {
scanner.scan();
}
}
}).start();
new Thread(() -> {
synchronized (scanner) {
scanner.scan();
synchronized (printer) {
printer.print();
}
}
}).start();
}
}
class Printer {
public void print() {
System.out.println("打印机正在打印");
}
}
class Scanner {
public void scan() {
System.out.println("扫描仪正在扫描");
}
}
等待唤醒机制
public class TestDemo9 {
public static void main(String[] args) {
Student student = new Student();
student.setName("孟美琪");
student.setGender("女生");
new Thread(new Ask(student)).start();
new Thread(new Change(student)).start();
}
}
// 问问题类
class Ask implements Runnable {
private Student student;
public Ask(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (!student.flag) {
try {
// 让当前线程陷入等待
student.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("老师,我是" + student.getName() + ",我是" + student.getGender() + "我要问问题");
student.flag = false;
// 唤醒线程
student.notify();
}
}
}
}
// 切换学生类
class Change implements Runnable {
private Student student;
public Change(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (student.flag) {
try {
student.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if ("王俊凯".equals(student.getName())) {
student.setName("孟美琪");
student.setGender("女生");
} else {
student.setName("王俊凯");
student.setGender("男生");
}
student.flag = true;
// 唤醒一个正在等待的线程
student.notify();
}
}
}
}
生产者消费者模式
案例
一个线程作为生产者,一个线程作为消费者。生产者每生产一次,消费者就消费一次。生产者每次生产的商品数量以及消费者每次消费的数量用随机数产生。每一次的生产的商品数量和上一次剩余的商品数量之和不能超过1000.
public class TestDemo {
public static void main(String[] args) {
Product product = new Product();
new Thread(new Producer(product)).start();
new Thread(new Consumer(product)).start();
}
}
class Product {
// 商品数量
private int count;
// 规定true是生产,false是消费
public boolean flag = true;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
// 生产者
class Producer implements Runnable {
private Product product;
public Producer(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
if (!product.flag) {
try {
product.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 生产商品
// 本次能生产的最大数量
int max = 1000 - product.getCount();
// 计算本次生产的实际数量
int count = (int) (Math.random() * (max + 1));
// 计算本次能提供的商品数量
product.setCount(product.getCount() + count);
System.out.println("本次生产的商品数量是:" + count + "个,本次提供的商品数量是" + product.getCount());
product.flag = false;
product.notify();
}
}
}
}
// 消费者
class Consumer implements Runnable {
private Product product;
public Consumer(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
if (product.flag) {
try {
product.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 计算本次消费的商品数量
int count = (int) (Math.random() * (product.getCount() + 1));
// 计算本次剩余的商品数量
product.setCount(product.getCount() - count);
System.out.println("本次消费的商品数量是:" + count + ",本次剩余的商品数量是" + product.getCount());
product.flag = true;
product.notify();
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
Product product = new Product();
new Thread(new Producer(product)).start();
new Thread(new Producer(product)).start();
new Thread(new Consumer(product)).start();
new Thread(new Consumer(product)).start();
}
}
class Product {
// 商品数量
private int count;
// 规定true是生产,false是消费
public boolean flag = true;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
// 生产者
class Producer implements Runnable {
private Product product;
public Producer(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
while (!product.flag) {
try {
product.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 生产商品
// 本次能生产的最大数量
int max = 1000 - product.getCount();
// 计算本次生产的实际数量
int count = (int) (Math.random() * (max + 1));
// 计算本次能提供的商品数量
product.setCount(product.getCount() + count);
System.out.println("本次生产的商品数量是:" + count + "个,本次提供的商品数量是" + product.getCount());
product.flag = false;
// 唤醒所有的线程
product.notifyAll();
}
}
}
}
// 消费者
class Consumer implements Runnable {
private Product product;
public Consumer(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
while (product.flag) {
try {
product.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 计算本次消费的商品数量
int count = (int) (Math.random() * (product.getCount() + 1));
// 计算本次剩余的商品数量
product.setCount(product.getCount() - count);
System.out.println("本次消费的商品数量是:" + count + ",本次剩余的商品数量是" + product.getCount());
product.flag = true;
// 唤醒所有的线程
product.notifyAll();
}
}
}
}
线程池
Executors[了解]
private static void demo4() {
// 创建一个可以延时的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
long start = System.currentTimeMillis();
scheduledExecutorService.schedule(() -> {
long end = System.currentTimeMillis();
System.out.println(end - start);
System.out.println("Hello Thread");
}, 5, TimeUnit.SECONDS);
}
private static void demo3() {
// 创建一个可以伸缩的线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
}
}
private static void demo2() {
// 执行线程池中线程的数量
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20; i++) {
executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
}
}
private static void demo1() {
// 创建只有一个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 不能并发 假设有10个任务,只有一个执行,其他9个都在等待
for (int i = 0; i < 10; i++) {
executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
}
// 异步任务执行完之后关闭线程池
// executorService.shutdown();
// 立即关闭线程池
executorService.shutdownNow();
}
ThreadPoolExecutor[掌握]
一般来说在实际情况下都是用创建ThreadPoolExecutor对象来使用线程池
private static void demo5() {
/**
* int corePoolSize, 核心线程数
* int maximumPoolSize, 允许的最大线程数量
* long keepAliveTime, 在执行的时间内回收线程
* TimeUnit unit, 时间单位
* BlockingQueue
<Runnable> workQueue, 工作队列
* ArrayBlockingQueue 有界队列
* LinkedBlockingQueue 无界队列
* PriorityBlockingQueue 优先队列
* ThreadFactory threadFactory, 创建线程的工厂
* RejectedExecutionHandler handler 拒绝策略 CallerRunsPolicy 去主线程执行
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 5, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
// 执行
threadPoolExecutor.execute(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
}
public class MyThreadPool {
private MyThreadPool() {
}
// 饿汉式
private static final MyThreadPool MY_THREAD_POOL = new MyThreadPool();
// 线程池
private static final ThreadPoolExecutor POOL_EXECUTOR;
private static final ScheduledThreadPoolExecutor SCHEDULED_THREAD_POOL_EXECUTOR;
static {
// 静态代码块
// 创建对象
POOL_EXECUTOR = new ThreadPoolExecutor(10, 100, 10, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
SCHEDULED_THREAD_POOL_EXECUTOR = new ScheduledThreadPoolExecutor(10, new ThreadPoolExecutor.CallerRunsPolicy());
}
public static MyThreadPool getInstance() {
return MY_THREAD_POOL;
}
// 异步执行任务
public void execute(Runnable runnable) {
POOL_EXECUTOR.execute(runnable);
}
// 延迟执行
public void delay(Runnable runnable, long time, TimeUnit timeUnit) {
SCHEDULED_THREAD_POOL_EXECUTOR.schedule(runnable, time, timeUnit);
}
// 异步执行任务
public
<V> void task(Callable<V> callable) {
POOL_EXECUTOR.submit(callable);
}
public void shutdown() {
SCHEDULED_THREAD_POOL_EXECUTOR.shutdown();
}
public void shutdownNow() {
SCHEDULED_THREAD_POOL_EXECUTOR.shutdownNow();
}
public void release() {
POOL_EXECUTOR.shutdown();
}
public void releaseNow() {
POOL_EXECUTOR.shutdownNow();
}
}
ThreadLocal
本质上是线程的一个映射
方法之间传递数据
public class TestDemo3 {
// key就是this
private static ThreadLocal
<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
String str = "hello e";
threadLocal.set(str);
a();
}
public static void a() {
b();
}
public static void b() {
c();
}
public static void c() {
d();
}
public static void d() {
e();
}
public static void e() {
System.out.println(threadLocal.get());
}
}
解决线程安全隐患
public class TestDemo4 {
public static ThreadLocal
<Printer> threadLocal = new ThreadLocal<>() {
// 匿名内部类
@Override
protected Printer initialValue() {
return new Printer();
}
};
public static void main(String[] args) {
new Thread(new GFS()).start();
new Thread(new BFM()).start();
}
}
class Printer {
public void print(String str) {
System.out.println("打印机在打印" + str);
}
}
class GFS implements Runnable {
@Override
public void run() {
Printer printer = TestDemo4.threadLocal.get();
printer.print(printer + "高富帅");
printer.print(printer + "高富帅");
printer.print(printer + "高富帅");
}
}
class BFM implements Runnable {
@Override
public void run() {
Printer printer = TestDemo4.threadLocal.get();
printer.print(printer + "白富美");
printer.print(printer + "白富美");
printer.print(printer + "白富美");
}
}
线程的优先级
java中采用抢占式调度:优先级相同的情况下,随机选择一个线程执行。如果线程的优先级大,抢到CPU资源的概率更高
线程的优先级是1-10,默认是5
public class TestDemo4 {
public static void main(String[] args) {
// 最大优先级
System.out.println(Thread.MAX_PRIORITY);
// 最小优先级
System.out.println(Thread.MIN_PRIORITY);
// 默认优先级
System.out.println(Thread.NORM_PRIORITY);
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}, "线程1");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}, "线程2");
// 设置线程的优先级
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.NORM_PRIORITY);
thread1.start();
thread2.start();
}
}
守护线程
java中线程分为守护线程和被守护线程。所有的被守护线程执行完毕后,守护线程也会结束。
例如 GC就是一个守护线程。
线程状态
源码:
public enum State {
// 创建成功还没有启动的线程
NEW,
// 正在java虚拟机中执行的线程
RUNNABLE,
// 受阻塞并等待某个锁的线程
BLOCKED,
// 无限期等待另一个线程唤醒的线程
WAITING,
// 等待指定时间的线程
TIMED_WAITING,
// 已经结束的线程
TERMINATED;
}
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 10; i > 0; i--) {
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread.State state = thread.getState();
System.out.println(state); // NEW
// 开启线程
thread.start();
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED){
state = thread.getState();
System.out.println(state);
}
}
总结:
wait和sleep的区别是什么
sleep需要指定休眠时间,到点自然醒。如果线程没有锁,那么会释放执行权。如果线程有锁,不释放执行权。这个方法是设计在Thread类上的方法,是一个静态方法
wait可以指定也可以不指定时间。如果不指定时间需要唤醒。释放执行权也释放锁。这个方法是设计在Object上的一个普通方法。
注意:使用wait和notify必须结合锁来使用,而且要使用同一个锁对象