重点:单例模式,阻塞队列,定时器,线程池
多线程案例
1.单例模式
1.1定义
- 是一种设计模式,在代码中有些对象应该只有一个实例,就称为“单例”
- eg:数据库的DataSource应该就是一个单例
- 有些负责加载数据到内存的类也应该是单例
- 强制性某个类只有一个实例
1.2实现
- 主要依托于static关键字,静态成员
1.2.1饿汉模式
/**
* User:yang
*/
public class ThreadDemo16 {
//饿汉模式
//实例创建出现在类加载阶段
static class Singleton {
//希望是单例类,只有一个实例
//县创建一个成员,保存唯一的实例
private static Singleton instanse = new Singleton();
//在提供方法获取实例
public static Singleton getInstance() {
return instanse;
}
//把构造方法私有,防止其他代码创建实例
private Singleton() {
}
}
public static void main(String[] args) {
//如何获取这个实力
Singleton singleton=Singleton.getInstance();
}
}
1.2.2懒汉模式
/**
* User:yang
*/
public class ThreadDemo17 {
//懒汉模式
//创建实例是在第一次使用getinstance的时候,比饿汉模式迟
static class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton() {
}
}
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
}
}
1.3如何保证懒汉模式线程安全
- 加锁
- 以效率为代价保证安全
/**
* User:yang
*/
public class ThreadDemo18 {
//线程安全版本懒汉模式
//创建实例是在第一次使用getinstance的时候,比饿汉模式迟
static class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
private Singleton() {
}
}
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
}
}
- 优化,当已经创建实例后,不进入加锁,并且volatile防止寄存器优化
/**
* User:yang
*/
public class ThreadDemo18 {
//线程安全版本懒汉模式
//创建实例是在第一次使用getinstance的时候,比饿汉模式迟
static class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() {
}
}
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
}
}
2.阻塞队列
2.1定义
- 1.这个队列是线程安全的(内部进行了加锁控制)
- 2.当队列满的时候,往里插入元素,此时就会阻塞,一直阻塞到队列不满的时候插入
- 当队列为空的时候,从队列取元素也会阻塞,一直阻塞到队列不为空的时候才完成取元素
- 使用场景:生产着消费者模型
2.1.1生产着消费者模型
- 实例:
- 包饺子
- 擀饺子皮(生产者)
- 包(消费者)
- 在服务器开发中很有用,一个服务器,同一时刻可能受到很多请求,服务器能力有限,可能会挂(削弱峰值)
- 实际上我们会在请求的时候使用阻塞队列,应用程序按照固定节奏从阻塞队列里面获取数据,请求在阻塞队列缓存着,不会消耗太多CPU资源
- 消息队列:也是阻塞队列,但是功能更强大
- 1.里面的数据带有类型topic,按照topic进行分类,把相同的topic的数据放到不同的队伍当中,分别进行排队,
- 一个消息队列,可以同时支撑多个业务的多组数据
- 2.往往是单独的服务器/服务器集群,通过网络通信的方式,进行“生产/消费”
- 3.还支持持久化存储(数据存在磁盘上)
- 4.消费的时候支持多种消费模式
- (1.指定位置消费,(不一定是取出队首元素)
- (2.镜像模式消费(一个数据可以被取多次,不是取一次就直接删除)
3.Java提供的BlockingQueue
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* User:yang
*/
public class ThreadDemo20 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
//创建生产者线程
Thread producer = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
System.out.println("producer生产 str" + i);
queue.put("str" + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
producer.start();
//消费者线程
Thread customer = new Thread() {
@Override
public void run() {
while (true) {
try {
String elem = queue.take();
Thread.sleep(1000);
System.out.println("customer获取到" + elem);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
customer.start();
try {
producer.join();
customer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.自己实现阻塞队列
4.1基于数组(环形队列)
/**
* User:yang
*/
public class ThreadDemo21 {
//先基于数组的方式实现一个普通队列
//再改进程阻塞队列
static class BlockingQueue {
private int[] items = new int[1000];
//约定[head,tail)是一个前闭后开区间
//初始状态head,tail为0,表示区间为空
//从head取元素
private int head = 0;
//往tail添加元素
private int tail = 0;
//表示元素个数
public int size = 0;
//引入一个“锁对象”
private Object locker = new Object();
//入队列
public void put(int value) throws InterruptedException {
synchronized (locker) {
while (size == items.length) {//wait结束之后,不一定队列不是满的
//队列已经满了,阻塞等待
locker.wait();
}
//队列没有满,插入到tail位置
items[tail] = value;
tail++;
//处理tail超出边界
if (tail >= items.length) {
tail = 0;
}
size++;
locker.notifyAll();
}
}
public Integer take() throws InterruptedException {
int ret = 0;
synchronized (locker) {
//队列为空,阻塞等待
while (size == 0) {
locker.wait();
}
ret = items[head];
head++;
if (head >= items.length) {
head = 0;
}
size--;
locker.notifyAll();
}
return ret;
}
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue queue = new BlockingQueue();
Thread producer = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
System.out.println("产生了元素" + i);
queue.put(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
producer.start();
Thread customer = new Thread() {
@Override
public void run() {
while (true){
int ret= 0;
try {
ret = queue.take();
System.out.println("消费了元素"+ret);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
customer.start();
producer.join();
customer.join();
}
public static void main1(String[] args) throws InterruptedException {
BlockingQueue queue = new BlockingQueue();
queue.put(1);
queue.put(2);
queue.put(3);
queue.put(4);
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
}
}
4.2基于链表
5.定时器
- 设置一个任务,约定这个任务在指定时间之后执行
- Timer提供核心及入口,schedule(安排)指定一个任务交给定时器,在一定的时间之后来执行这个任务。
- 1.Timer中要包含一个Task类,每个Task就表示一个具体的任务实例
- Task里面包含一个时间戳(啥时候执行任务),还包含一个Runnable实例(用来表示任务具体是啥)
- 2.Timer里面通过一个带优先级的阻塞队列(时间到了就先执行,快要到时间的任务优先级更高),来组织若干个task
- 3.Timer中还需要一个专门的线程,让这个线程不停的扫描队首元素,看看队首元素是不是可以执行了,如果可以执行了,就执行这个任务,如果不能执行,就继续在队列中等待。
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;
/**
* User:yang
* 一个简单定时器
*/
public class ThreadDemo22 {
//每个Task就表示一个具体的任务实例
//task放入优先队列的时候要比较优先级
static class Task implements Comparable<Task> {
//啥时候执行
private long time;
//执行啥
private Runnable command;
//
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);
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 comand=new Runnable() {
@Override
public void run() {
System.out.println("时间到了");
timer.schedule(this,3000);
}
};
System.out.println("安排任务");
timer.schedule(comand,3000);
}
}
5.5 定时器总结
- 目的:让某个任务在某个时间点再执行,不是立即执行
- 接口:schedule把一个任务和时间加入到定时器中
- 结构:
- (1)Task类,来描述一个任务
- (2)带优先级的阻塞队列
- (3)线程,扫描队首元素
- (4)mailBox防止扫描线程忙等
6.协程
- 比线程更升级
7.线程池(java提供的)
- 把一些线程创建好,用的时候从池子取一个线程,用完了不是销毁,而是放回池子里。
- java提供的
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* User:yang
*/
public class ThreadDemo23 {
public static void main(String[] args) {
//创建包含10个线程的线程池
ExecutorService pool= Executors.newFixedThreadPool(10);
// //创建线程数量动态变化的的线程池
// ExecutorService pool2=Executors.newCachedThreadPool();
int i = 0;
for (; i <100 ; i++) {
int finalI = i;
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("hello"+ finalI);
}
});
}
}
}
8.线程池(自己实现)
- 线程池内部结构
- 1.描述一个任务(runnable),只需要知道任务做啥,不需要知道任务啥时候执行
- 2.组织很多任务,使用阻塞队列来保存当前的所有任务
- 3.有一些线程,来负责执行阻塞队列中的任务,阻塞队列为空则等待
- 4.还需要list把当前线程保存起来,方便管理
import javafx.concurrent.Worker;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* User:yang
*/
public class ThreadDemo24 {
static class ThreadPool {
//描述一个任务(runnable)
//组织很多任务,使用阻塞队列来保存当前的所有任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
//描述一个线程,用来进行工作
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();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//八线程组织起来
private List<worker> workers=new ArrayList<>();
private static final int maxcount=10;
//核心接口execute
public void execute(Runnable command) throws InterruptedException {
if (workers.size()<maxcount){
//当前池子没有足够的线程,创建新的线程
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 < 10; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
}