1.单例(Singleton)模型
单例模式:保证类中有且只有一个对象。
解决问题:一个全局使用的类频繁地创建与销毁。
主要代码:构造方法私有化(private)。
单例模式的几种实现方式:
1.1懒汉式
/**
* 懒汉式
*/
private static Singleton01 instance;//唯一对象
private Singleton01() {// 构造方法私有化
}
public static Singleton01 getInstance() {// 用来创建(获取)唯一对象的方法
if (instance == null) {
instance = new Singleton01();
}
return instance;
}
//---------------------------------->
public static void main(String[] args) {
Singleton01 s1 = Singleton01.getInstance();
Singleton01 s2 = Singleton01.getInstance();
System.out.println(s1==s2);//结果为true。说明s1与s2指向同一对象
}
多线程情况下:
public class TestSingleton implements Runnable {
public static void main(String[] args) {
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Singleton01 s = Singleton01.getInstance();
System.out.println(s.hashCode());
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
由此可见,懒汉式模型非线程安全。
解决方法:在getInstance()方法上加锁。
public synchronized static Singleton01 getInstance() {// 用来创建(获取)唯一对象的方法
if (instance == null) {
instance = new Singleton01();
}
return instance;
}
1.2饿汉式
public class Singleton02 {
private static Singleton02 instance = new Singleton02();//创建唯一对象
private Singleton02() {//构造方法私有化
}
public static Singleton02 getInstance() {//获取唯一对象的方法
return instance;
}
}
多线程情况下:
public class TestSingleton implements Runnable {
public static void main(String[] args) {
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Singleton02 s = Singleton02.getInstance();
System.out.println(s.hashCode());
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
由此可见,饿汉式模型线程安全。
1.3枚举类
public enum Singleton03 {
INSTANCE;//创建唯一对象
}
多线程情况下:
public class TestSingleton implements Runnable {
public static void main(String[] args) {
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
new Thread(new TestSingleton()).start();
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Singleton03 s = Singleton03.INSTANCE;
System.out.println(s.hashCode());
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
由此可见,枚举式模型线程安全
2.生产者消费者模型
所谓的生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架满的时候,消费者可以从货架上拿走商品,生产者此时等待货架的空位,这样不断的循环。那么在这个过程中,生产者和消费者是不直接接触的,所谓的‘货架’其实就是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是仍给阻塞队列,这个阻塞队列就是来解决生产者消费者的强耦合的。
为什么要使用生产者和消费者模式?
在线程开发中,生产者就是生产数据的线程,消费者就是消费数据的线程。
在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。
如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。
为了解决这个问题于是引入了生产者和消费者模式。
例如:KFC问题。
生产者不断的制造汉堡,消费者不断的消费汉堡,而货架为其两者之间的中间容器。
汉堡类:
public class Hambager {
private static volatile int id;// 每个汉堡唯一id
private String name;// 汉堡名称
private static AtomicInteger ato = new AtomicInteger(0);
public Hambager(String name) {
id = ato.incrementAndGet();
this.name = name;
}
public static int getId() {
return id;
}
public static void setId(int id) {
Hambager.id = id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Hambager [name=" + name + "]";
}
}
货柜类:
public class KFC {
private Deque<Hambager> pool = new LinkedBlockingDeque<>();// 存放汉堡的货柜。相当于中间容器。
private final int MAX_VALUE = 10;// 货柜的最大容量
public synchronized void consume() {// 消费者消费汉堡
while (pool.isEmpty()) {
System.out.println("没有汉堡了,消费者休息等待生产者生产汉堡");
try {
wait();// 当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Hambager h = pool.poll();
System.out.println("消费一个" + h.getName() + ",还剩汉堡数:" + pool.size());
System.out.println("唤醒生产者生产汉堡");
notifyAll();// 唤醒所有等待中的线程
}
public synchronized void produce() {// 生产者生产汉堡
while (pool.size() >= MAX_VALUE) {
System.out.println("汉堡满了,生产者等待消费者消费汉堡");
try {
wait();// 当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Hambager h = new Hambager("鸡肉汉堡");
pool.offer(h);
System.out.println("生产一个" + h.getName() + ",还剩汉堡数:" + pool.size());
System.out.println("唤醒消费者消费汉堡");
notifyAll();// 唤醒所有等待中的线程
}
}
消费者线程:
public class ConsumerThread extends Thread{
private KFC kfc;
public ConsumerThread(KFC kfc) {
this.kfc = kfc;
}
@Override
public void run() {
while(true) {
kfc.consume();//不停消费汉堡
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者线程:
public class ProducerThread extends Thread{
private KFC kfc;
public ProducerThread(KFC kfc) {
this.kfc = kfc;
}
@Override
public void run() {
while(true) {
kfc.produce();//不停生产汉堡
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
模型检测:
public static void main(String[] args) {
KFC kfc = new KFC();
//消费者线程
new ConsumerThread(kfc).start();
new ConsumerThread(kfc).start();
new ConsumerThread(kfc).start();
new ConsumerThread(kfc).start();
new ConsumerThread(kfc).start();
new ConsumerThread(kfc).start();
//生产者线程
new ProducerThread(kfc).start();
new ProducerThread(kfc).start();
new ProducerThread(kfc).start();
new ProducerThread(kfc).start();
}
且为无限运行,不会停止。
3线程池
1.线程池的优势
- 降低资源销毁:通过重复利用已经创建的线程,降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 防止服务器过载:形成内存溢出,或者CPU耗尽。.
- 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
2.线程池的主要参数
- corePoolSiz:线程池中的核心线程数量,在没有用的时候,也不会被回收。
- maximumPool:就是线程池中可以容纳的最大线程的数量
- keepAliveTi:线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间
- util:计算时间的一个单位
- workQueue:就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)
- handler:拒绝策略
3.线程池流程
4.拒绝策略
当任务队列满了之后,如果还有任务提交过来,会触发拒绝策略
1.AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认该方式。
2.CallerRunsPolicy:直接调用execute来执行当前任务。
3.DiscardPolicy:丢弃任务,但是不抛出异常。
4.DiscardOldestPolicy:抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。先从任务队列中弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。
5.提交方式
3.1创建线程池
常见的几种线程池类型:
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newCachedThreadPool();//创建线程池
for(int i =0;i<10;i++) {
Thread.sleep(200);
pool.execute(()->{//线程池创建线程
for(int s =0;s<10;s++) {
System.out.println(Thread.currentThread().getName()+":"+s);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
结果:
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2);//创建定长为2线程池
for(int i =0;i<10;i++) {
Thread.sleep(200);
pool.execute(()->{//线程池创建线程
for(int s =0;s<10;s++) {
System.out.println(Thread.currentThread().getName()+":"+s);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
结果:
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
public static void main(String[] args) throws Exception {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);//创建定长为5线程池
int t =0;
pool.scheduleAtFixedRate(()->//定时创建线程
System.out.println(Thread.currentThread().getName()+":"+t)
, 0, 2, TimeUnit.SECONDS);
}
结果:
- newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newSingleThreadExecutor();//创建线程池
for(int i =0;i<10;i++) {
Thread.sleep(200);
pool.execute(()->{//线程池创建线程
for(int s =0;s<10;s++) {
System.out.println(Thread.currentThread().getName()+":"+s);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
结果:
3.2线程池相关问题
1.线程池核心线程数的设置?
(1)CPU的线程数概念仅仅只针对Intel的CPU才有用,因为它是通过Intel超线程技术来实现的。
(2)如果没有超线程技术,一个CPU核心对应一个线程。 所以,对于AMD的CPU来说,只有核心数的概念,没有线程数的概念。
2.高并发、任务执行时间短的业务怎样使用线程池?
高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
3.并发不高、任务执行时间长的业务怎样使用线程池?
假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。
假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换