文章目录
单例模式
设计模式部分后续也会出,但是可能会比较晚,先鸽一下嘿嘿嘿,
首先来加深理解一下加锁的作用
单例模式 作用:
能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。( JDBC 中的 DataSource 实例就只需要一个)
单例模式实现方法
饿汉模式
类加载的同时, 创建实例
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance () {
return instance;
}
}
懒汉模式
类加载的时候不创建实例. 第一次使用的时候才创建实例
懒汉模式单线程
public class 懒汉单线程 {
private static 懒汉单线程 instance = null;
private 懒汉单线程() {
}
public synchronized static 懒汉单线程 getInstance() {
if (instance == null) {
instance = new 懒汉单线程();
}
return instance;
}
}
仔细观察这段代码,好像没什么问题,但是如果在 多线程 的话,它其实 线程 不安全,那我们怎么修改呢?看下面:
懒汉模式多线程
public class 懒汉多线程 {
private static 懒汉多线程 instance = null;
private 懒汉多线程() {
}
public synchronized static 懒汉多线程 getInstance() {
if (instance == null) {
instance = new 懒汉多线程();
}
return instance;
}
}
没错就是你看到那样我们只需要添加synchronized关键字。
但是,众所周知,加锁 、 解锁是一件开销比较高的事情.,否则 StringBuffer 。。。而懒汉模式的线程不安全只是发生在首次创建实例的时候.,后续的使用,其实就 不必再进行加锁了,所以我们可以做如下修改:
public class 懒汉模式多线程改进 {
private static volatile 懒汉模式多线程改进 instance = null;
private 懒汉模式多线程改进() {
}
public static 懒汉模式多线程改进 getInstance() {
if (instance == null) {
synchronized (懒汉模式多线程改进.class) {
if (instance == null) {
instance = new 懒汉模式多线程改进();
}
}
}
return instance;
}
}
提问:为什么要这么设计?
解:首先我们要知道不安全的可能是因为多线程调用时,可能会有多个实例来getInstance(),那么我们就给他加锁,然后解决性能问题:外层判断使创建的实例只能有一个因为给他上锁了,但是内存可见性怎么保证呢,万一读缓存呢?所以我们给他加 volatile ,诺~,完美解决!!!
想必现在我们对单例模式的 线程安全 处理已经有了一定理解那么接下来:
阻塞式队列
首先顾名思义啦,队列队列了啦,当然遵循队列的先进先出的规则啦。
当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型的开发模型:生产消费者模型,开发模型这个东西就也先鸽一下吧
当然俺会丢下队友吗?显然不会,所以这里还是介绍一下生产消费者模型吧:
既然是阻塞队列的一种应用,他当然遵循阻塞队列的一些特性比如上面那些。
那么接下来我们看一段生产消费者模型相关代码
public class 生产消费者模型 {
public static void main(String[] args) throws Interrupted Exception {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println("消费元素: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
int num = random.nextInt(1000);
System.out.println("生产元素: " + num);
blockingQueue.put(num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
producer.start();
customer.join();
producer.join();
}
}
这个代码能看懂吧,应该吧,注意阻塞队列的加入,当然,如果看不懂的话,我们接下来实现一下阻塞队列:
public class BlockingQueue {
private int[] items = new int[1000];
private volatile int size = 0;
private int head = 0;
private int tail = 0;
public void put(int value) throws InterruptedException {
synchronized (this) {
while (size == items.length) {
wait();
}
items[tail] = value;
tail = (tail + 1) % items.length;
size++;
notifyAll();
}
}
public int take() throws InterruptedException {
int ret = 0;
synchronized (this) {
while (size == 0) {
wait();
}
ret = items[head];
head = (head + 1) % items.length;
size--;
notifyAll();
}
return ret;
}
public synchronized int size() {
return size;
}
public static void main(String[] args) throws InterruptedException {
//既然是psvm啦当然是测试一下用的了啦
BlockingQueue blockingQueue = new BlockingQueue();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
blockingQueue.put(random.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
producer.start();
customer.join();
producer.join();
}
}
字面意思就可以理解的大概了吧,程序在定时多久后执行
先来看一下官方的东西:
public class 定时器官方 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 5000);
}
}
使用:
- 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule
- schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒).
实现定时器
定时器的构成:
- 一个带优先级的阻塞队列
- 队列中的每个元素是一个 Task 对象.
- Task 中带有一个时间属性, 队首元素就是即将
- 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
- 提问:为啥要带优先级呢?
- 解:因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效地把这个 delay 最小的任务找出来
下面我们来看一下实现过程:
public class Timer {
static class Task implements Comparable<Task> {
private Runnable command;
private long time;
public Task(Runnable command, long time) {
this.command = command;
// time 中存的是绝对时间, 超过这个时间的任务就应该被执行
this.time = System.currentTimeMillis() + time;
}
public void run() {
command.run();
}
@Override
public int compareTo(Task o) {
// 谁的时间小谁排前面
return (int) (time - o.time);
}
}】
private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
private Object mailBox = new Object();
class Worker extends Thread {
@Override
public void run() {
while (true) {
try {
Task task = queue.take();
long curTime = System.currentTimeMillis();
if (task.time > curTime) {
// 时间还没到, 就把任务再塞回去
queue.put(task);
synchronized (mailBox) {
// 指定等待时间wait
mailBox.wait(task.time - curTime);
}
} else {
// 时间到了, 可以执行任务
task.run();
}
} catch (
InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public Timer() {
// 启动 worker 线程
Worker worker = new Worker();
worker.start();
}
public void schedule(Runnable command, long after) {
Task task = new Task(command, after);
queue.offer(task);
synchronized (mailBox) {
mailBox.notify();
}
}
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);
}
};
timer.schedule(command, 3000);
}
}
众所周知线程的启动销毁是很费资源的,所以有什么好的办法吗?
线程池 工作原理
Executors 创建线程池的几种方式:
- newFixedThreadPool: 创建固定线程数的线程池
- newCachedThreadPool: 创建线程数目动态增长的线程池.
- newSingleThreadExecutor: 创建只包含单个线程的线程池.
- newScheduledThreadPool: 设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer.
实现线程池
- 核心操作为 submit, 将任务加入线程池中
- 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
- 使用一个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
- 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了
下面我们来看代码:
public class MyThreadPool{
/**存放线程的集合*/ private ArrayList<MyThead> threads;
/**任务队列*/ private ArrayBlockingQueue<Runnable> taskQueue;
/**线程池初始限定大小*/ private int threadNum;
/**已经工作的线程数目*/ private int workThreadNum;
private final ReentrantLock main lock = new ReentrantLock();
public MyThreadPool(int initPoolNum) {
threadNum = initPoolNum;
threads = new ArrayList<>(initPoolNum);
//任务队列初始化为线程池线程数的四倍
taskQueue = new ArrayBlockingQueue<>(initPoolNum*4);
workThreadNum = 0;
}
public void execute(Runnable runnable) {
try {
mainLock.lock();
//线程池未满,每加入一个任务则开启一个线程
if(workThreadNum < threadNum) {
MyThead myThead = new MyThead(runnable);
myThead.start();
threads.add(myThead);
workThreadNum++;
}
//线程池已满,放入任务队列,等待有空闲线程时执行
else {
//队列已满,无法添加时,拒绝任务
if (!taskQueue.offer(runnable)) {
rejectTask();
}
}
} finally {
mainLock.unlock();
}
}
private void rejectTask() {
System.out.println("任务队列已满,无法继续添加,请扩大您的初始化线程池!");
}
public static void main(String[] args) {
MyThreadPool myThreadPool = new MyThreadPool(5);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行中");
}
};
for (int i = 0; i < 20; i++) {
myThreadPool.execute(task);
}
}
class MyThead extends Thread{
private Runnable task;
public MyThead(Runnable runnable) {
this.task = runnable;
}
@Override
public void run() {
//该线程一直启动着,不断从任务队列取出任务执行
while (true) {
//如果初始化任务不为空,则执行初始化任务
if(task != null) {
task.run();
task = null;
}
//否则去任务队列取任务并执行
else {
Runnable queueTask = taskQueue.poll();
if(queueTask != null)
queueTask.run();
}
}
}
}
}
到目前,基本概念和其的一些简单用法都已实现,后续将详细介绍各种锁、和多线程的运用敬请期待 。