目录
案例一(线程安全的单例模式):
实现一个线程安全的单例模式
关键要点:
1. 合适的位置加锁
2. 双重 if 判定,(是否要加锁,是否要创建实例)
3. volatile
- 饿汉模式(使用private staitc在类加载的时候创建实例)线程安全
- 懒汉模式(在调用getInstance的时候才真正创建实例)线程不安全
// 单例模式完全写法
class SingletonDataSource {
// 懒汉模式
private static volatile SingletonDataSource instance = null;
private SingletonDataSource() {
}
public static SingletonDataSource getInstance() {
// 判断是否要加锁(还没实例化)
if (instance == null) {
// 进行加锁
synchronized (SingletonDataSource.class) {
// 判断是否要实例化
if (instance == null) {
// 实例化
instance = new SingletonDataSource();
}
}
}
return instance;
}
}
案例二(阻塞队列):
消息队列(本质上是功能更丰富的阻塞队列)
阻塞队列:先进先出
- 线程安全的队列
- 带有“阻塞功能”
优点:
- 解耦合性强
- 缓冲(“削峰填谷”)
// 实现一个阻塞队列
class MyBlockingQueue {
// 队列的初始大小
private int[] items = new int[100];
// 记录队列中有效元素的个数
private int size = 0;
// 记录队首的位置
private int head = 0;
// 记录队尾的位置
private int tail = 0;
// 锁对象
private Object locker = new Object();
// 入队列
public void put(int value) throws InterruptedException {
synchronized (locker) {
if (size == items.length) {
// 队列满了
// return;
// 此处的等待条件是队列满了,当队列不满就唤醒
locker.wait();
}
items[tail] = value;
tail++;
if (tail >= items.length) {
tail = 0;
}
size++;
// 唤醒 take 的阻塞等待
locker.notify();
}
}
// 出队列
public Integer take() throws InterruptedException {
synchronized (locker) {
if (size == 0) {
// return null;
// 队列为空的时候,再次进行取元素,就需要阻塞等待
locker.wait();
}
int ret = items[head];
head++;
if (head >= items.length) {
head = 0;
}
size--;
// 取走元素成功,唤醒依次 put
locker.notify();
return ret;
}
}
}
public class Demo2 {
// 使用这个队列作为交易场所
private static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) throws InterruptedException {
// 实现一个简单的生产者消费者模型
// 两个线程,一个作为生产者,一个作为消费者
Thread producer = new Thread(() -> {
int n = 1;
while (true) {
try {
System.out.println("生产者生产了:" + n);
queue.put(n);
n++;
// 给生产者加个 sleep,让生成的慢,消费的快
// 此时大部分情况下是空的,消费者就在阻塞等待,生成一个立马消费一个
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
Thread customer = new Thread(() -> {
while (true) {
int n = 0;
try {
n = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者消费了:" + n);
// 给消费者加个 sleep,让生产的快,消费的慢
// 此时大部分情况下队列是满的,生产者就很多时间在阻塞等待
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
}
}
案例三(定时器):
库中自带的 Timer 定时器
// 库中的定时器
public class Demo3 {
public static void main(String[] args) {
Timer timer = new Timer();
// schedule , TimerTask 重写 run 方法
timer.schedule(new TimerTask() {
@Override
public void run() {
// 在这里描述具体的任务
System.out.println("hello timer");
}
}, 3000);
System.out.println("main");
}
}
自己实现一个定时器
// 实现一个简单的定时器
class MyTimer {
// 使用一个内部类表示当前的一个任务
static class Task implements Comparable<Task>{
// 要执行的任务是什么
private Runnable runnable;
// 什么时间去执行,此处的 time 使用绝对的时间戳表示
private long time;
public Task(Runnable runnable, long after) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + after;
}
public void run() {
runnable.run();
}
@Override
public int compareTo(Task o) {
// 重写比较方法 this - o 是把时间短的放前面
// o - this 时间大的放前面
return (int)(this.time - o.time);
}
}
// 把若干个 Task 放入堆中
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
// 通过这个方法来往定时器中注册一个任务
// 告知 定时器 after 毫秒之后,执行这个 runnable 里的 run 方法
public void schedule(Runnable runnable, long after) {
Task task = new Task(runnable, after);
tasks.put(task);
}
private Object locker = new Object();
// 创建一个扫描线程,扫描线程不停的取队首元素,判定任务是否可以执行
// MyTimer 实例化的时候,创建线程
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
// 取队首元素,判定时间是不是到了
Task task = tasks.take();
long curTime = System.currentTimeMillis();
if (curTime < task.time) {
// 时间没到,把任务放回去
tasks.put(task);
// 此处的 locker 存在的价值是为了等待
synchronized (locker) {
locker.wait(task.time - curTime);
}
} else {
// 时间到了,就执行这个任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
public class Demo4 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
System.out.println("main");
}
}
案例四(线程池):
内核态 vs 用户态
Java标准库内置的线程池:ThreadPollExecutor
手动实现简化版线程池:
class MyThreadPool {
// 1. 描述任务,直接使用 Runnable
// 2. 组织任务,使用一个阻塞队列来存放若干个任务
private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
// 3. 还需要描述一个工作线程是啥样的
static class Worker extends Thread {
// 当前这里的 worker 线程有好几个,这些线程共享一个任务队列
// 通过这个线程的构造方法,把上面创建好的任务队列给传到线程里面,方便线程去取任务
private BlockingDeque<Runnable> queue = null;
public Worker(BlockingDeque<Runnable> queue) {
this.queue = queue;
}
@Override
public void run() {
// 一个线程要做的工作
// 反复的从队列中读取任务,然后执行任务
while (true) {
Runnable task = null;
try {
// 如果任务队列不为空,此时就能立即取出一个任务并执行
// 如果任务队列为空,就hi产生阻塞,阻塞到有人加入新的任务为止
task = queue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 4. 需要组织若干个工作线程
private List<Worker> workerList = new ArrayList<>();
// 5. 搞一个构造方法,指定一下有多少个线程在线程池中
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Worker worker = new Worker(queue);
// 启动线程,先跑起来,再保存到数组
worker.start();
workerList.add(worker);
}
}
// 6. 实现一个 submit 来注册任务到线程池中
public void submit(Runnable runnable) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Demo5 {
public static void main(String[] args) {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 30; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
}