多线程
多线程
1. 相关名词解释
1.1 程序和进程
程序:一系列代码指令的集合,软件属于程序的表现方式之一。是一种静态的表现形式。
进程:进行中的应用程序,属于一种动态的表现形式。
进程拥有独立的内存资源和CPU运算资源等等。
1.2 线程
线程属于CPU调度执行、运算的最小单位。
1.3 进程和线程的关系
线程是包含在进程之中的,一个进程至少包含一个线程,否则将无法执行。
1.4 线程的数量
线程不是越多越好,要结合实际的硬件环境,合适的才是最好的。
2. 线程的执行
单核心CPU下,多个线程随机轮流交替执行,因为切换的频率非常快,所以宏观上是同时执行的,微观是轮流交替执行的。
3. 并发和并行
并发:同时发生,轮流交替执行。
并行:真正意义上的同时执行。
4. 创建线程方式
调用start()方法 和 调用run()方法的区别? 调用start()方法会开启新的线程
调用run()方法 不会开启新的线程
4.1 继承Thread类
创建线程方式1:继承Thread类 重写run方法
run方法内为当前线程要执行的代码
/**
* @author WHD
* @description TODO
* @date 2023/11/28 10:58
* 创建线程方式1:继承Thread类 重写run方法
* run方法内为当前线程要执行的代码
*
* 调用start()方法 和 调用run()方法的区别?
* 调用start()方法会开启新的线程
* 调用run()方法 不会开启新的线程
*
*/
public class MyThread1 extends Thread{
@Override
public void run() {
for(int i = 0;i < 20;i++){
System.out.println(Thread.currentThread().getName() + "线程执行了" + i);
}
}
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
myThread1.setName("线程A");
myThread1.start();
MyThread1 myThread11 = new MyThread1();
myThread11.setName("-----线程B-----");
myThread11.start();
}
}
4.2 实现Runnable接口
创建线程方式2:实现Runnable接口 重写run方法
/**
* @author WHD
* @description TODO
* @date 2023/11/28 11:04
* 创建线程方式2:实现Runnable接口 重写run方法
*/
public class MyThread2 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 20;i++){
System.out.println(Thread.currentThread().getName() + "线程执行了" + i);
}
}
public static void main(String[] args) {
MyThread2 runnable = new MyThread2();
Thread thread = new Thread(runnable);
thread.setName("___线程A___");
thread.start();
Thread thread1 = new Thread(runnable);
thread1.setName("******线程B******");
thread1.start();
}
}
5. 线程的状态
线程的状态:创建 就绪 运行 阻塞 运行 死亡
/**
* @author WHD
* @description TODO
* @date 2023/11/28 14:00
* 线程的状态:创建 就绪 运行 阻塞 运行 死亡
*/
public class TestThreadStatus extends Thread{
@Override
public void run() { // 运行
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
Thread.sleep(3000); // 阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0;i <10;i++){
System.out.println(Thread.currentThread().getName() + "===" + i);
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
} // 死亡
public static void main(String[] args) {
TestThreadStatus thread = new TestThreadStatus(); // 创建
thread.setName("线程A");
thread.start(); // 就绪
}
}
6. 线程调度常用的方法
6.1 线程优先级
线程优先级:1最低 10最高 默认为5 优先级高的线程获取CPU资源的概率较大 但并不能保证一定会优先执行
setPriority(int newPriority) 设置优先级 可以传入1~10之间的整数
也可以写静态常量 MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY
getPriority() 获取优先级
/**
* @author WHD
* @description TODO
* @date 2023/11/28 14:10
* 线程优先级:1最低 10最高 默认为5 优先级高的线程获取CPU资源的概率较大 但并不能保证一定会优先执行
*
* setPriority(int newPriority) 设置优先级 可以传入1~10之间的整数
* 也可以写静态常量 MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY
* getPriority() 获取优先级
*/
public class TestThreadPriority extends Thread{
@Override
public void run() {
for (int i = 1;i <= 10;i++){
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
public static void main(String[] args) {
TestThreadPriority th1 = new TestThreadPriority();
TestThreadPriority th2 = new TestThreadPriority();
th1.setPriority(MAX_PRIORITY); // 10
th2.setPriority(1); // MIN_PRIORITY
th1.setName("线程A");
th2.setName("*****线程B*****");
th1.start();
th2.start();
}
}
6.2 线程休眠
sleep(long millis) 休眠指定的时间 时间过后自动恢复继续执行
/**
* @author WHD
* @description TODO
* @date 2023/11/28 14:00
*
*/
public class TestThreadStatus extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
Thread.sleep(3000); // 休眠3秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0;i <10;i++){
System.out.println(Thread.currentThread().getName() + "===" + i);
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
public static void main(String[] args) {
TestThreadStatus thread = new TestThreadStatus();
thread.setName("线程A");
thread.start();
}
}
6.3 线程插队
线程插队:等待插队线程执行完毕 或者 执行指定的时间 之后 被插队线程再执行
join() 等待这个线程死亡。
join(long millis) 等待这个线程死亡最多 millis毫秒。
join(long millis, int nanos) 等待最多 millis毫秒加上 nanos纳秒这个线程死亡。
/**
* @author WHD
* @description TODO
* @date 2023/11/28 14:19
* 线程插队:等待插队线程执行完毕 或者 执行指定的时间 之后 被插队线程再执行
* join() 等待这个线程死亡。
* join(long millis) 等待这个线程死亡最多 millis毫秒。
* join(long millis, int nanos) 等待最多 millis毫秒加上 nanos纳秒这个线程死亡。
*/
public class TestThreadJoin extends Thread{
@Override
public void run() {
for(int i = 1;i <= 50;i++){
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestThreadJoin th1 = new TestThreadJoin();
th1.setName("------线程A------");
th1.start();
for(int i = 1;i < 20;i++){
System.out.println(Thread.currentThread().getName() + "****" + i);
Thread.sleep(20); // 每次执行休眠200毫秒
if(i == 10){
th1.join(200,5686); // 让th1线程插队 直到 th1线程执行完毕
}
}
}
}
6.4 线程礼让
线程的礼让: 表示当前线程向调度器发出信息,愿意做出让步,但是调度器可以忽略这一点。
static yield()
/**
* @author WHD
* @description TODO
* @date 2023/11/28 14:31
* 线程的礼让: 表示当前线程向调度器发出信息,愿意做出让步,但是调度器可以忽略这一点。
*
* static yield()
*/
public class TestThreadYield extends Thread{
@Override
public void run() {
for(int i = 1;i <= 20;i++){
if(i == 5){
System.out.println("线程礼让");
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
public static void main(String[] args) {
TestThreadYield th1 = new TestThreadYield();
TestThreadYield th2 = new TestThreadYield();
th1.setName("线程A");
th2.setName("****线程B****");
th1.start();
th2.start();
}
}
7. 同步关键字
多个并发线程访问同一资源的同步代码块时 同一时刻只能有一个线程进入synchronized(this)同步代码块 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
synchronized 关键字 同步锁
可以用于
修饰代码块 : 表示同一时间只能有一个线程执行此代码块
修饰方法 : 表示同一时间只能有一个线程执行此方法
关于同步代码块中书写的this关键字:
this表示当前对象 如果需要同步效果 那么必须保证多个线程 锁定是同一个对象才可以
否则将不会有同步效果 因为 Runnable实现类只需要创建一个对象 可以传入不同的多个线程对象中
所以直接写this比较方便 相当于多个线程对象 共享同一个 Runnable实现类对象
同步代码块实现线程安全:
/**
* @author WHD
* @description TODO
* @date 2023/11/28 15:35
* 使用多线程模拟多人抢票 : 三个人抢10张票
*
* synchronized 关键字 同步锁
* 可以用于
* 修饰代码块 : 表示同一时间只能有一个线程执行此代码块
* 修饰方法 : 表示同一时间只能有一个线程执行此方法
*
* 关于同步代码块中书写的this关键字:
* this表示当前对象 如果需要同步效果 那么必须保证多个线程 锁定是同一个对象才可以
* 否则将不会有同步效果 因为 Runnable实现类只需要创建一个对象 可以传入不同的多个线程对象中
* 所以直接写this比较方便 相当于多个线程对象 共享同一个 Runnable实现类对象
*
*
*/
public class BuyTicket2 implements Runnable{
int ticketCount = 10;
@Override
public void run() {
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
if(ticketCount == 0){
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第"+ ( 10 - ticketCount) +"张票,还剩余"+ ticketCount +"张票");
}
}
System.out.println("票已售完");
}
public static void main(String[] args) {
BuyTicket2 runnable = new BuyTicket2();
Thread th1 = new Thread(runnable,"赵四");
Thread th2 = new Thread(runnable,"大拿");
Thread th3 = new Thread(runnable,"小宝");
th1.start();
th2.start();
th3.start();
}
}
同步方法实现线程安全:
/**
* @author WHD
* @description TODO
* @date 2023/11/28 15:35
* 使用多线程模拟多人抢票 : 三个人抢10张票
* <p>
* synchronized 关键字 同步锁
* 可以用于
* 修饰代码块 : 表示同一时间只能有一个线程执行此代码块
* 修饰方法 : 表示同一时间只能有一个线程执行此方法
*/
public class BuyTicket3 implements Runnable {
int ticketCount = 10;
@Override
public synchronized void run() {
while (ticketCount > 0) {
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
}
System.out.println("票已售完");
}
public static void main(String[] args) {
BuyTicket3 runnable = new BuyTicket3();
Thread th1 = new Thread(runnable, "赵四");
Thread th2 = new Thread(runnable, "大拿");
Thread th3 = new Thread(runnable, "小宝");
th1.start();
th2.start();
th3.start();
}
}
8. 线程的通信-生产者消费者模式
线程之间的通信:生产者消费者模式
线程通信:多个线程之间数据的传输 称之为线程通信
生产者消费者模式:不属于设计模式 属于线程通信过程中非常常见的一个场景 一种现象
具体要求:
1.生产什么 消费什么
2.持续生产 持续消费 (没有生产 不能消费)
3.保证产品的完整性 (不能消费半个产品 或者 残缺的产品)
4.不能重复消费同一个产品
Object类中的两个方法 wait()方法 和 notify()方法
wait()方法 让当前线程等待 会释放锁
notify() 方法唤醒等待的线程
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:08
* 台式电脑类 产品类
* 属性:主机 显示器
*/
public class Computer {
private String mainFrame;
private String screen;
private boolean flag; // false 表示可以生产 不能消费 true 可以消费 不能生产
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getMainFrame() {
return mainFrame;
}
public void setMainFrame(String mainFrame) {
this.mainFrame = mainFrame;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public Computer() {
}
public Computer(String mainFrame, String screen) {
this.mainFrame = mainFrame;
this.screen = screen;
}
@Override
public String toString() {
return "Computer{" +
"mainFrame='" + mainFrame + '\'' +
", screen='" + screen + '\'' +
'}';
}
}
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:09
* 消费者线程
*/
public class Consumer extends Thread{
private Computer computer;
public Consumer(Computer computer) {
this.computer = computer;
}
@Override
public void run() {
for(int i = 1;i <= 20;i++){
synchronized (computer){
if(!computer.isFlag()){
try {
computer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(computer.getMainFrame() + "==" + computer.getScreen());
computer.setFlag(false);
computer.notify();
}
}
}
}
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:09
* 生产者 线程
*/
public class Producer extends Thread{
private Computer computer;
public Producer(Computer computer) {
this.computer = computer;
}
@Override
public void run() {
for(int i = 1;i <= 20;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (computer){
// 先判断是否需要等待
if(computer.isFlag()){
try {
computer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i % 2 == 0){
computer.setMainFrame(i + "号联想主机");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
computer.setScreen(i + "号联想显示器");
}else{
computer.setMainFrame(i + "号华硕主机");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
computer.setScreen(i + "号华硕显示器");
}
computer.setFlag(true);
computer.notify();
}
}
}
}
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:01
*
* 线程之间的通信:生产者消费者模式
*
* 线程通信:多个线程之间数据的传输 称之为线程通信
*
* 生产者消费者模式:不属于设计模式 属于线程通信过程中非常常见的一个场景 一种现象
*
* 具体要求:
* 1.生产什么 消费什么
* 2.持续生产 持续消费 (没有生产 不能消费)
* 3.保证产品的完整性 (不能消费半个产品 或者 残缺的产品)
* 4.不能重复消费同一个产品
*
* Object类中的两个方法 wait()方法 和 notify()方法
*
* wait()方法 让当前线程等待 会释放锁
* notify() 方法唤醒等待的线程
*
*/
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
Producer producer = new Producer(computer);
Consumer consumer = new Consumer(computer);
producer.start();
consumer.start();
}
}
队列实现生产者消费者模式
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:47
*/
public class Computer {
private String mainFrame;
private String screen;
public String getMainFrame() {
return mainFrame;
}
public void setMainFrame(String mainFrame) {
this.mainFrame = mainFrame;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public Computer() {
}
public Computer(String mainFrame, String screen) {
this.mainFrame = mainFrame;
this.screen = screen;
}
@Override
public String toString() {
return "Computer{" +
"mainFrame='" + mainFrame + '\'' +
", screen='" + screen + '\'' +
'}';
}
}
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:50
*/
public class Consumer extends Thread{
private ArrayBlockingQueue<Computer> queue;
public Consumer(ArrayBlockingQueue<Computer> queue) {
this.queue = queue;
}
@Override
public void run() {
for (int i = 1;i <= 20;i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:48
* 生产者线程
*/
public class Producer extends Thread{
private ArrayBlockingQueue<Computer> queue;
public Producer(ArrayBlockingQueue<Computer> queue) {
this.queue = queue;
}
@Override
public void run() {
for(int i = 1;i <= 20;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
Computer computer = new Computer();
if(i % 2 == 0){
computer.setMainFrame(i + "号联想主机");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
computer.setScreen(i + "号联想显示器");
System.out.println("生产了第" + i +"号联想电脑");
}else{
computer.setMainFrame(i + "号华硕主机");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
computer.setScreen(i + "号华硕显示器");
System.out.println("生产了第" + i +"号华硕电脑");
}
try {
queue.put(computer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author WHD
* @description TODO
* @date 2023/11/29 9:52
*/
public class Test {
public static void main(String[] args) {
ArrayBlockingQueue<Computer> queue = new ArrayBlockingQueue<>(10);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
producer.start();
consumer.start();
}
}
9.wait和sleep的区别?
面试题:wait()方法 和 sleep()方法的区别?
1.所属类不同:
wait方法属于Object sleep方法属于Thread类
2.是否会释放锁对象不同:
sleep不会释放锁对象 属于获取了CPU资源 持续占用资源 在释放锁对象之前 其他线程不能再次获取同一个锁 对象
wait会释放锁对象 在当前线程wait期间 其他线程可以再次获取同一个锁对象
3.使用方式不同:
wait方法必须结合同步代码快使用 否则报异常 IllegalMonitorStateException
sleep方法可以单独使用
4.状态不同:
调用wait方法 当前线程会进入WAITING状态
调用sleep方法 当前线程会进入 TIMED_WAITING
5.唤醒方式:
sleep方法属于自动唤醒
无参wait方法需要调用notify或者notifyAll方法唤醒
import java.time.LocalDateTime;
/**
* @author WHD
* @description TODO
* @date 2023/11/29 10:43
* 面试题:wait()方法 和 sleep()方法的区别?
* 1.所属类不同:
* wait方法属于Object sleep方法属于Thread类
* 2.是否会释放锁对象不同:
* sleep不会释放锁对象 属于获取了CPU资源 持续占用资源 在释放锁对象之前 其他线程不能再次获取同一个锁对象
* wait会释放锁对象 在当前线程wait期间 其他线程可以再次获取同一个锁对象
* 3.使用方式不同:
* wait方法必须结合同步代码快使用 否则报异常 IllegalMonitorStateException
* sleep方法可以单独使用
* 4.状态不同:
* 调用wait方法 当前线程会进入WAITING状态
* 调用sleep方法 当前线程会进入 TIMED_WAITING
*
*/
public class TestWaitSleep {
public static void main(String[] args) throws InterruptedException {
// 锁对象
Object obj = new Object();
new Thread(new Runnable() {
@Override
public void run() {
// synchronized (obj){
System.out.println(Thread.currentThread().getName() + "获得了锁对象");
try {
obj.wait(2000);
// Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "释放锁对象");
// }
}
}).start();
// 主线程休眠200毫秒
Thread.sleep(200);
synchronized (obj){
System.out.println(Thread.currentThread().getName() + "获取了锁对象" + LocalDateTime.now());
}
System.out.println(Thread.currentThread().getName() + "释放锁对象" + LocalDateTime.now());
}
}
调用wait和sleep会进入不同的状态
/**
* @author WHD
* @description TODO
* @date 2023/11/29 10:58
*/
public class TestThreadState {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (this){
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
System.out.println("sleep()方法进入状态:" + t1.getState());
System.out.println("wait()方法进入状态:" + t2.getState());
}
}
10.其他创建线程的方式
10.1 Callable接口方式
回顾我们之前创建线程的两种方式(继承Thread和实现Runnable接口),存在如下问题:
因为Thread也实现了Runnable接口 所以 这两种方式 本质是相同的 不同点在于 一个是继承父类 一个是实现接口
因为这两种方式都是重写run方法 所以
1.不能有返回值 因为run方法定义为void
2.不能声明任何异常 因为run方法没有声明任何异常
Callable 接口是JDK1.5新增的接口 可以给线程添加返回值
FutureTask构造方法支持传入一个Callable接口实现类
FutureTask类 实现类 RunnableFuture接口
RunnableFuture接口 继承自 Runnable接口
所以FutureTask对象就是Runnable实现类 可以作为参数传入到Thread对象中
import com.atguigu.test1.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @author WHD
* @description TODO
* @date 2023/11/29 11:06
* 回顾我们之前创建线程的两种方式(继承Thread和实现Runnable接口),存在如下问题:
* 因为Thread也实现了Runnable接口 所以 这两种方式 本质是相同的 不同点在于 一个是继承父类 一个是实现接口
* 因为这两种方式都是重写run方法 所以
* 1.不能有返回值 因为run方法定义为void
* 2.不能声明任何异常 因为run方法没有声明任何异常
*
*
* Callable 接口是JDK1.5新增的接口 可以给线程添加返回值
*
* FutureTask构造方法支持传入一个Callable接口实现类
*
* FutureTask接口 继承自 RunnableFuture接口
* RunnableFuture接口 继承自 Runnable接口
* 所以FutureTask对象就是Runnable实现类 可以作为参数传入到Thread对象中
*
*
*/
public class TestCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1;i <= 10;i++){
sum += i;
}
System.out.println("call方法最终返回值为: " +sum);
return sum;
}
public static void main(String[] args) throws Exception {
TestCallable testCallable = new TestCallable();
FutureTask<Integer> task = new FutureTask<>(testCallable);
Thread thread = new Thread(task);
thread.start();
Integer result = task.get();
System.out.println("result = " + result);
}
}
10.2 线程池方式
回顾我们之前创建线程的三种方式,存在如下问题:
1.在多线程场景中,会频繁的创建以及销毁线程对象,浪费系统资源
2.之前创建的线程不能进行统一管理
3.不能执行定时任务
线程池 : 线程池属于对多个线程对象统一管理的一个容器,线程池中的线程对象,使用完毕之后,根据当前线程池设定的机制,不会理解被垃圾收集器回收,而是归还到池中,等待下次使用的时候,继续取出使用。
java.util.concurrent.Executors 类 线程池工具类
newCachedThreadPool() 根据需求创建多个线程对象的线程池
newFixedThreadPool(int nThreads) 根据指定的线程数量创建线程池
newScheduledThreadPool(int corePoolSize)创建一个开源执行定时任务的线程池
newSingleThreadExecutor() 创建只包含一个线程对象的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author WHD
* @description TODO
* @date 2023/11/29 14:10
* 回顾我们之前创建线程的三种方式,存在如下问题:
* 1.在多线程场景中,会频繁的创建以及销毁线程对象,浪费系统资源
* 2.之前创建的线程不能进行统一管理
* 3.不能执行定时任务
*
*
* 线程池 : 线程池属于对多个线程对象统一管理的一个容器,线程池中的线程对象,使用完毕之后,根据当前线程池设定的
* 机制,不会理解被垃圾收集器回收,而是归还到池中,等待下次使用的时候,继续取出使用。
*
* java.util.concurrent.Executors 类 线程池工具类
*
* newCachedThreadPool() 根据需求创建多个线程对象的线程池
* newFixedThreadPool(int nThreads) 根据指定的线程数量创建线程池
* newScheduledThreadPool(int corePoolSize)创建一个开源执行定时任务的线程池
* newSingleThreadExecutor() 创建只包含一个线程对象的线程池
*
*/
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService es1 = Executors.newCachedThreadPool();
es1.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
});
es1.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
});
ExecutorService es2 = Executors.newFixedThreadPool(5);
es2.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
});
es2.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
});
ScheduledExecutorService es3 = Executors.newScheduledThreadPool(10);
es3.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "延迟执行了");
}
}, 5, TimeUnit.SECONDS);
ExecutorService es4 = Executors.newSingleThreadExecutor();
es4.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
});
}
}
11.懒汉单例线程安全问题
之前编写的懒汉单例模式适用于单线程场景。
如果是在多线程场景下,需要做出如下改进,使用双重检查机制实现。
第一重检查:是为了避免无效的排队,浪费时间。
第二重检查:是为了线程安全
/**
* @author WHD
* @description TODO
* @date 2023/11/29 14:38
* 懒汉单例
*/
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance1() throws InterruptedException {
// 逻辑代码1
// 逻辑代码2
// 逻辑代码3
if(instance == null){
Thread.sleep(1000);
instance = new LazySingleton();
}
return instance;
}
public static LazySingleton getInstance2() throws InterruptedException {
// 逻辑代码1
// 逻辑代码2
// 逻辑代码3
// 当任何类被类加载器加载 JVM会自动创建此类的对应 Class对象 Class<LazySingleton>
// 此对象只会被创建一次 所以只有一份 可以保证多个线程获取的是同一个锁对象
synchronized (LazySingleton.class) {
if(instance == null){
Thread.sleep(1000);
instance = new LazySingleton();
}
}
return instance;
}
/**
* 双重检查懒汉单例
* @return
* @throws InterruptedException
*/
public static LazySingleton getInstance3() throws InterruptedException {
// 逻辑代码1
// 逻辑代码2
// 逻辑代码3
// 当任何类被类加载器加载 JVM会自动创建此类的对应 Class对象 Class<LazySingleton>
// 此对象只会被创建一次 所以只有一份 可以保证多个线程获取的是同一个锁对象
if(instance == null){
synchronized (LazySingleton.class) {
if(instance == null){
Thread.sleep(1000);
instance = new LazySingleton();
}
}
}
return instance;
}
}
12.锁介绍
Lock接口实现线程安全抢票
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author WHD
* @description TODO
* @date 2023/11/28 15:35
* 使用多线程模拟多人抢票 : 三个人抢10张票
* 使用Lock接口 以及 其实现类 ReentrantLock(可重入锁实现线程安全)
*/
public class BuyTicket implements Runnable {
int ticketCount = 10;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();
if (ticketCount == 0) {
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
System.out.println("票已售完");
}
public static void main(String[] args) {
BuyTicket runnable = new BuyTicket();
Thread th1 = new Thread(runnable, "赵四");
Thread th2 = new Thread(runnable, "大拿");
Thread th3 = new Thread(runnable, "小宝");
th1.start();
th2.start();
th3.start();
}
}
锁的介绍
乐观锁 : 自旋锁 (无锁 CAS Compare And Swap)
悲观锁 : synchronized
轻量级锁
重量级锁 : synchronized
公平锁
非公平锁 : synchronized
可重入锁:同一个线程对象重复的获得同一个锁对象 不应该产生死锁 synchronized属于可重入锁
死锁:死锁是因为逻辑错误导致的多个线程竞争同一个资源 僵持不下
/**
* @author WHD
* @description TODO
* @date 2023/11/29 15:30
* 锁的介绍
* 乐观锁 : 自旋锁 (无锁 CAS Compare And Swap)
* 悲观锁 : synchronized
*
* 轻量级锁
* 重量级锁 : synchronized
*
* 公平锁
* 非公平锁 : synchronized
*
* 可重入锁:同一个线程对象重复的获得同一个锁对象 不应该产生死锁 synchronized属于可重入锁
* 死锁:死锁是因为逻辑错误导致的多个线程竞争同一个资源 僵持不下
*/
public class Note {
public void m1(){
synchronized (this){
System.out.println("同步代码块1");
synchronized (this){
System.out.println("同步代码块2");
}
}
}
public static void main(String[] args) {
Note note = new Note();
note.m1();
}
}
自实现可重入锁
/**
* @author WHD
* @description TODO
* @date 2023/11/29 15:50
*/
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void m1() throws InterruptedException {
System.out.println("m1 method start");
lock.lock();
m2();
lock.unlock();
System.out.println("m1 method end");
}
public static void m2() throws InterruptedException {
System.out.println("m2 method start");
lock.lock();
lock.unlock();
System.out.println("m2 method end");
}
public static void main(String[] args) throws InterruptedException {
m1();
}
}
class ReentrantLock{
private boolean isLocked; // 是否上锁
private int lockedCount; // 上锁次数
private Thread currentLockedThread; // 当前上锁对象
public synchronized void lock() throws InterruptedException {
Thread thread = Thread.currentThread();
if(isLocked && currentLockedThread != thread){
this.wait();
}
isLocked = true;
lockedCount++;
currentLockedThread = thread;
}
public synchronized void unlock(){
Thread thread = Thread.currentThread();
if(currentLockedThread == thread){
lockedCount--;
if(lockedCount == 0){
notify();
isLocked = false;
}
}
}
}
自实现不可重入锁
/**
* @author WHD
* @description TODO
* @date 2023/11/29 15:44
*/
public class TestUnReentrantLock {
private static UnReentrantLock lock = new UnReentrantLock();
public static void m1() throws InterruptedException {
System.out.println("m1 method start");
lock.lock();
m2();
lock.unlock();
System.out.println("m1 method end");
}
public static void m2() throws InterruptedException {
System.out.println("m2 method start");
lock.lock();
lock.unlock();
System.out.println("m2 method end");
}
public static void main(String[] args) throws InterruptedException {
m1();
}
}
class UnReentrantLock{
private boolean isLocked;
public synchronized void lock() throws InterruptedException {
if(isLocked){
this.wait();
}
isLocked = true;
}
public synchronized void unlock(){
this.notify();
isLocked = false;
}
}