文章目录
单例模式
设计模式部分后续也会出,但是可能会比较晚,先鸽一下嘿嘿嘿,
首先来加深理解一下加锁的作用
单例模式 作用:
能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。( 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() { | |
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(); | |
} | |
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 { | |
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() { | |
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(); | |
} | |
} | |
} | |
} | |
} |
到目前,基本概念和其的一些简单用法都已实现,后续将详细介绍各种锁、和多线程的运用敬请期待 。