Author:老九
计算机专业
可控之事 沉重冷静 不可控之事 乐观面对
85180586@qq.com
☺️
❓ ❤️ ☕️ ❗️
————————————————
版权声明:本文为CSDN博主「浦上青天」的原创文章
第五天 第六天复习
线程安全问题阻塞队列定时器
定时器构成
构造方法快捷键 线程池
核心操作
线程总结
知识点
线程安全问题单例模式:某个类,不应该有多个实例,此时就可以使用单例模式
实现方式:1.懒汉模式2.饿汉模式
饿汉模式:在类加载的时候立刻实例化对象(实例化时机早)
懒汉模式:首次调用getInstance的时候再实例化对象(效率更高)
阻塞队列1.饿汉模式,是线程安全的,多线程调用getInstance只涉及读操作
2.懒汉模式,在实例化对象之前是线程不安全的(涉及到多线程修改),一旦实例化之后,又是线程安全的
懒汉模式改进方式:
1.加锁(读取判断和new实例操作是原子的)
2.双重if(避免在实例化之后,再去调用getInstance频繁触发不必要的加锁操作)
3.volatile(在instance实例之前加)
可以用来实现生产者消费者模型
阻塞队列是一个先进先出的队列
出队列的时候,如果发现队列空了,也会阻塞,直到有其他线程调用入队列操作,让队列中有元素,才能继续出队列
入队列的时候如果发现队列满了,就会阻塞,直到有其他线程调用出队列操作让队列中有空位的时候,才能继续入队列
public class ThreadDemo24 {
static class BlockingQueue {
private int[] array = new int[1000];
private int head = 0;
private int tail = 0;
//head 和 tail 构成一个前闭后开区间
//区分空还是满
private int size = 0;
//阻塞版本入队列
public void put(int value) throws InterruptedException {
synchronized (this) {
if (size == array.length) {
wait();
}
array[tail] = value;
tail++;
if (tail == array.length) {
tail = 0;
}
size++;
notify();
}
}
public int take() throws InterruptedException {
int ret = -1;
synchronized (this) {
if (size == 0) {
wait();
}
ret = array[head];
head++;
if (head == array.length) {
head = 0;
}
size--;
notify();
}
return ret;
}
}
}
定时器这两个wait不可能同时被调用
定时器构成多线程编程中一个重要/常用组件
好比一个闹钟,有些逻辑,并不想立刻执行,而是等一定的时间之后,再来执行
例如,用户服务器,浏览器内部,淘宝服务器都有定时器,就跟闹钟是一样的,如果定时器时间到了,就不等了
1.使用一个类Task来描述“一段逻辑”(一个要执行的任务),同时也要记录这个任务在啥时候执行
阻塞优先队列:既支持阻塞的特性,又支持按优先级“先进先出”
本质上是一个“堆”
2.使用一个阻塞优先队列来组织若干个Task
3.还需要一个扫描线程,扫描线程要循环的检测队首元素需要执行,如果需要执行的话,就执行这个任务
java.util.concurrent 简称juc,包含很多并发编程的包
wait()死等,一直等到notify的通知过来
wait(time),等待是有上限,如果有notify就被提前唤醒,或者跟sleep一样
import javafx.concurrent.Worker;
import java.sql.Time;
import java.util.concurrent.PriorityBlockingQueue;
public class ThreadDemo25 {
//优先队列中的元素必须是可比较的
//1.让Task 实现Comparable接口
//2.让优先队列构造的时候,传入一个比较器对象(Comparator)
static class Task implements Comparable {
//Runnable 中有一个run方法,就可以借助这个run方法,来描述要执行的具体的任务
private Runnable command;
//time表示啥时候来执行command,是一个绝对时间
private long time;
//构造方法的参数表示,多少毫秒之后执行,(相对时间)
public Task(Runnable command, long after) {
this.command = command;
this.time = System.currentTimeMillis() + after;
}
//执行任务的具体逻辑
public void run() {
command.run();
}
@Override
public int compareTo(Task o) {
return (int) (this.time - o.time);
}
}
static class Worker extends Thread {
private PriorityBlockingQueue queue = null;
private Object mailBox = null;
public Worker(PriorityBlockingQueue queue, Object mailBox) {
this.queue = queue;
this.mailBox = mailBox;
}
@Override
public void run() {
while (true) {
//1.取出队首元素,检查时间是否到了
try {
Task task = queue.take();
long curTime = System.currentTimeMillis();
if (task.time > curTime) {
//时间还没到
queue.put(task);
synchronized (mailBox)
{
mailBox.wait(task.time-curTime);
}
} else {
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
static class Timer {
//为了避免忙等,需要使用wait方法
//使用一个单独的对象来辅助进行wait
//用this也可以
private Object mailBox = new Object();
//定时器的基本构成,有三个部分
//1.用一个类来描述 “任务”
//2.使用阻塞优先队列来组织若干个任务,让队首元素就是时间最早的任务,
//如果队首元素时间未到,那么其他元素也肯定不能执行
private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
//3.用一个线程循环扫描当前的阻塞队列的队首元素,如果时间到
//就执行指定的任务
public Timer() {
//创建线程
Worker worker = new Worker(queue,mailBox);
worker.start();
}
//4.还需要提供一个方法,让调用者能把任务给“安排”进来
public void schedule(Runnable command, long after) {
Task task = new Task(command, after);
queue.put(task);
synchronized (mailBox)
{
mailBox.notify();
}
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hehe");
timer.schedule(this, 2000);
}
}, 2000);
}
}
}
构造方法快捷键
ALT+INSERT
线程池核心操作已经包含了一些线程,让我们直接去使用
避免了频繁创建/销毁线程的开销
execute:把一个任务加到线程池中
shutdown:销毁线程池中的所有线程
线程池的组成部分
1.先有一个类,表示 工作线程
2.还得有一个类,来描述具体线程要做的工作是啥(借助runnable就可以表示
3.还需要有一个数据结构来组织若干个任务,BlockingQueue)
4.还需要一个数据结构,来组织若干个线程List
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.linkedBlockingQueue;
public class ThreadDemo26 {
//使用这个类来描述当前的工作线程是啥样的
static class Worker extends Thread
{
private int id = 0;
private BlockingQueue queue = null;
public Worker(BlockingQueue queue,int id) {
this.queue = queue;
this.id = id;
}
@Override
public void run() {
try{
while(!Thread.currentThread().isInterrupted())
{
Runnable command = queue.take();
System.out.println("thread"+id+"running...");
command.run();
}
} catch (InterruptedException e) {
System.out.println("线程被终止");
}
}
}
static class MyThreadPool
{
//这个阻塞队列用于组织若干个任务
private BlockingQueue queue = new linkedBlockingQueue<>();
//这个List用来组织若干个工作线程
private List workers = new ArrayList<>();
//一个线程池内部应该有多少个线程,需要根据实际情况确定
private static final int maxWorkerCount = 10;
//实现execute方法 和 shutdown方法
public void execute(Runnable command) throws InterruptedException {
//也是使用延时加载的方式创建线程
//当线程池中线程数目比较少,就新创建线程来作为工作线程
//如果线程数目已经比较多了,就不用新建线程了
if(workers.size()
线程总结
1.进程和线程的基本概念和区别
2.线程控制
a线程创建
b线程终止
c线程等待
d获取线程实例
e线程休眠
3.线程状态
4.线程安全【重中之重】
基本概念:多线程执行某个逻辑出现了逻辑错误
出现的原因:
1.抢占式执行(万恶之源)
2.修改操作不是原子的
3.多线程修改同一个变量
4.内存可见性(volatile,一个线程读,一个线程写,其中读操作被优化成直接取CPU寄存器数据,当写线程进行修改的时候,读线程无法获取到最新的值)
5.指令重排序
解决方案:
最主要的方案:
加锁(原子性)
对象等待集(避免出现线程饿死问题)
wait本质上就是当前线程对应的PCB移动到阻塞队列中,直到notify唤醒的时候再把PCB移动回来
wait内部做了三件事:
1.释放锁
2.等待通知
3.收到通知后尝试重新获取锁
先赞后看,养成习惯!!!^ _ ^♥♥♥
每天都更新知识点哦!!!
码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘记关注我哦!



