懒汉模式的单例模式 (在调用方法的时候才会创建实例对象),线程安全版的懒汉模式,使用双重 if 提高效率,使用 sychronized 加锁线程安全,volatile 保证内存可见性,外层的 if 读取到的是最新值。
public class ThreadDemo10 {
// 懒汉模式 (在调用方法的时候才会创建实例对象)
static class Singleton {
volatile private Singleton instance = null;
// 构造方式私有,防止其他代码再创建实例
private Singleton () {}
// 双重 if 加 synchronized锁 既保证了线程安全,又保证了效率
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public static void main(String[] args) {
Singleton s = new Singleton();
s.getInstance();
}
}
饿汉模式的单例模式,实例的创建出现在 “类加载” 阶段,第一次使用到这个类的时候,就会把这个类的 .class 加载到内存里。
public class ThreadDemo9 {
// 饿汉模式 (实例的创建出现在类加载的时候)
static class Singleton {
// 先创建一个成员, 保存唯一的一个 Singleton 实例
private static Singleton instance = new Singleton();
// 然后把类的构造方法设为 private, 防止其他代码再创建实例
private Singleton() {}
// 再提供一个方法, 来获取到这个实例
public static Singleton getInstance () {
return instance;
}
}
public static void main(String[] args) {
// 只能通过 getInstance 的方式获取到该实例.
// 而无法通过 new 的方式创建新的 Singleton 实例了.
Singleton s = Singleton.getInstance();
}
}
队列我们知道是是一种数据结构,一边添加数据,另一边删除数据。阻塞队列就是在队列的基础上实现了类似于 “生产者”,“消费者” 模型,当队列中没有数据的时候,使用 take() 方法取数据就会阻塞(线程等待,不是报错),知道有数据写入的时候才会取到值。同理,当队列满的时候,我们使用 put() 方法添加数据的时候,也会阻塞,直到阻塞队列中有空间了才会添加成功。Java 中提供了现成接口
BlockingQueue
。我们通过实现多个线程来观察效果。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
// 阻塞队列
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// 创建生产者模型
Thread producer = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
queue.put(i);
System.out.println("producer 生产数字"+i);
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
producer.start();
// 创建消费者模型
Thread customer = new Thread() {
@Override
public void run() {
while (true){
try {
int elem = queue.take();
System.out.println("customer 消费数字"+elem);
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
customer.start();
producer.join();
customer.join();
}
}
结果如下图:当设置生产速度和消费速度相同时,那么在队列中就会产生这样的一个效果。
// 基于数组实现一个普通的队列
// 改进成阻塞队列
public class ThreadDemo12 {
static class myBlockingQueue {
private int[] elem = new int[100];
// 从头部取元素
private int head = 0;
// 从尾部存元素
private int tail = 0;
// 表示当前队列元素个数
private int size = 0;
// 创建一个锁对象
private Object locker = new Object();
// 入队列
public void put(int value) throws InterruptedException {
synchronized(locker) {
// 队列已经满了,阻塞等待,等待队列不为空的时候,再插入
while (size == elem.length) {
locker.wait();
}
elem[tail] = value;
tail++;
// 处理 tail 超出边界
if (tail >= elem.length) {
tail = 0;
}
size++;
locker.notifyAll();
}
}
// 出队列
public Integer tack() throws InterruptedException {
int value = 0;
// 队列为空的时候, 也要阻塞等待
synchronized (locker) {
if (size == 0) {
locker.wait();
}
value = elem[head];
head++;
if (head >= elem.length) {
head = 0;
}
size--;
locker.notifyAll();
}
return value;
}
}
public static void main(String[] args) throws InterruptedException {
myBlockingQueue queue = new myBlockingQueue();
// 存元素
Thread producer = new Thread() {
@Override
public void run() {
for (int i = 0; i <1000; i++) {
try {
System.out.println("存储了元素: "+i);
queue.put(i);
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
producer.start();
// 取元素
Thread customer = new Thread() {
@Override
public void run() {
while (true) {
try {
int ret = queue.tack();
System.out.println("取出元素: "+ret);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
customer.start();
producer.join();
customer.join();
}
}
定时器(Timer)就相当于一个闹钟,给定时器设定一个任务,约定这个任务在XXX时间之后执行。Timer 提供一个核心接口 schedule ,指定一个任务交给定时器,在一定的时间后来执行这个任务。
1. Timer 中要包含一个 Task 类,每一个 Task 就表示一个具体的任务实例。Task 里面包含一个时间戳(表示什么时候去执行这个任务),还包含一个 Runnable 实例(用来表示任务具体执行的是啥)
2. Timer 里面通过一个带优先级的的阻塞队列,来组织若干个 task。
3. Timer 中还需要一个专门的线程,让这个线程不停的扫描队首元素,看看队首元素是不是可以执行了,如果可以执行,就执行,如果不能执行,就在队列中继续等待
// 定时器
public class ThreadDemo14 {
// 每个 Task 实例包含一个要执行的任务
// Task 要放到优先队列中,但是优先队列里面要进行 ”优先级比较“
static class Task implements Comparable<Task> {
// 啥时候执行
private long time;
// 执行什么
private Runnable command;
// 一般设置定时器的时候,传入的时间,都是一个时间间隔
// 例如写 1000, 意思就是 1000ms 之后再执行
public Task(Runnable command,long time) {
this.command = command;
// 为了后面方便判断,在这里记录一下绝对时间
this.time = System.currentTimeMillis() + time;
}
public void run () {
command.run();
}
@Override
public int compareTo(Task o) {
// 时间小的排到前面
return (int)(this.time - o.time);
}
}
static class Timer {
// 创建一个带优先级的阻塞队列
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
// 使用这个对象来完成线程之间的协调任务
private Object mailBox = new Object();
// schedule 方法的功能就是把一个 Task 放到 Timer中
public void schedule(Runnable command,long after) {
Task task = new Task(command,after);
queue.put(task);
// 当 worker 线程中包含 wait 机制的时候,再安排任务的时候就需要显示的唤醒一下了
// 此处是为了处理, 插入的新任务比当前队首的任务还要靠前的情况
synchronized (mailBox) {
mailBox.notify();
}
}
public Timer() {
// 创建一个线程,让这个线程去扫描队列的队首元素,看看能不嫩执行
Thread worker = new Thread() {
@Override
public void run() {
while (true) {
// 取出队首元素,判断一下这个元素能不执行
try {
Task task = queue.take();
long currentTime = System.currentTimeMillis();
if (currentTime >= task.time) {
// 时间到了,执行任务
task.run();
} else {
// 时间没到,继续等待
queue.put(task);
synchronized (mailBox) {
mailBox.wait(task.time - currentTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.start();
}
}
public static void main(String[] args) {
Timer timer = new Timer();
Runnable command = new Runnable() {
@Override
public void run() {
System.out.println("时间到了");
timer.schedule(this,3000);
}
};
System.out.println("安排任务");
timer.schedule(command,3000);
}
}
总结:定时器
目的:让某个任务在某个时间点再执行,不是立刻执行
接口:schedule,把一个任务+时间加入到定时器中
结构:a) Task类,来描述一个任务。b) 带优先级的阻塞队列。c) 线程扫描队首元素。d) mailBox 方止扫描线程忙等
这个是线程池的一个比较简单的用法。实际上线程池还有更复杂的用法,
标准库里 ThreadPool 类,这个类构造方法中提供了很多的参数选项,来控制线程池具体的行为。
Executors 相当于是对 ThreadPool 进行了简单的封装(把很多参数都给定了默认选项)
工作中更常用的是 ThreadPool 这个复杂版本,就可以更精细的来控制线程池的行为~~
public static void main(String[] args) {
// 创建方式有很多种.
// 比如可以通过 Exectors 的静态方法来负责创建不同类型的线程池.
// 创建一个包含 10 个线程的线程池.
ExecutorService pool = Executors.newFixedThreadPool(10);
// 创建一个线程数量动态变化的线程池
// ExecutorService pool2 = Executors.newCachedThreadPool();
// 创建好了, 就需要往里面放任务.
for (int i = 0; i < 100; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
线程池内部要有哪些结构:
1. 描述一个任务,就使用Runnable即可,只需要知道任务做啥,不需要知道任务啥时候执行
2. 组织很多任务,使用阻塞队列来保存当前 的所有任务
3. 有一些线程,来负责执行阻塞队列中的任务,让这些线程从阻塞队列中取任务并执行,如果阻塞队列为空,就等待
4. 还需要有一个List把当前线程都保存起来,方便管理
public class ThreadDemo16 {
static class ThreadPool {
// 1. 使用 Runnable 来描述这个任务,不需要额外的类
// 2. 组织若干个任务,使用阻塞队列来组织
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// 3. 描述一个线程,用来进行工作
static class Worker extends Thread {
private BlockingQueue<Runnable> queue = null;
public Worker(BlockingQueue<Runnable> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Runnable runnable = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 4. 把线程组织起来
private List<Worker> workers = new ArrayList<>();
// 设置一个线程最大值
private static final int MAX_WORKERS_COUNT = 10;
// 核心接口
public void execute(Runnable command) throws InterruptedException {
if (workers.size() < MAX_WORKERS_COUNT) {
// 当前池子里没有足够的线程, 就创建个新的线程
Worker worker = new Worker(queue);
worker.start();
workers.add(worker);
}
queue.put(command);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadPool pool = new ThreadPool();
for (int i = 0; i < 100; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
}