Java多线程四个案例
2026/8/23 17:12:47 网站建设 项目流程

目录

  • 1,单例模式
  • 2,阻塞队列
  • 3,线程池
  • 4,定时器

一,单例模式

单例模式也就是一个进程中只允许有一个实例,是我们平时在开发中比较重要常见的一种模式,我们单例模式一般分为两种,一种是饿汉模式,一种是懒汉模式

饿汉模式:

//饿汉模式 class Singleton{ //static保证了成员或者方法是属于类的,不属于实例,所以你不能通过new实例来调用它,刚刚好保证单例,因为new实例会创建多个对象 private static Singleton instance=new Singleton(); //单例模式最关键的 private Singleton(){ } public static Singleton getInstance(){ return instance; } }

在饿汉模式里面,我们调用的开始就直接创建了唯一实例,所以线程安全,因为没有判断等其他操作,只有读的操作

懒汉模式(原始):

class Singleton3 { private static Singleton3 instance; private Singleton3() { } public static Singleton3 getInstance() { if (instance == null) { instance = new Singleton3(); } return instance; } }

在get方法里面判断了是否创建过实例之后再来操作,但是这样的懒汉模式线程不安全,当我们两个线程同时判定是否为空的时候,可能就会出现创建了两个instance实例,单例模式就失败了,并且new操作会因为指令重排序导致出现问题,指令重排序:编译器的一种底层优化,调整指令执行顺序,使代码效率更高。

(1)就比如new这个操作可以简化理解为三个操作:1:分配内存空间,2:针对空间进行初始化,3:内存空间首地址,赋值到引用变量中。按正常顺序,我们应该是123,但是经过指令重排序会导致变成132,就会传回去一个没有数据的值。

(2)并且外层还有if判断,加上new操作,就会使这个创建操作不是原子的,也会因为线程的随机调度导致线程安全问题,所以我们要在instance变量前面加上volatile修饰,防止指令重排序,并且在if和new的这个操作外层套上一把锁,将两个操作打包为一个整体。

(3)但是我们还需要思考一个问题,当我们第一次创建好实例之后,就不需要每次都使用锁然后判断了,这样会浪费时间和内存空间,所以我们外层需要再使用一层if判断

懒汉模式(改进):

class Singleton2 { private static volatile Singleton2 instance; private Singleton2() { } public static Singleton2 getInstance() {//双重检查 if (instance == null) { synchronized (Singleton2.class) { if (instance == null) { instance = new Singleton2(); } } } return instance; } }

总结一下单例模式注意线程安全需要注意的点1:正确的加锁,2:双重if判断,3:volatile

dataSource实现单例:

//dataSource实现单例模式 class DataSource { private static volatile DataSource dataSource; private DataSource() { } public static DataSource getInstance() { if (dataSource == null) { synchronized (DataSource.class) { if (dataSource == null) { DataSource ds=new DataSource(); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/java117?characterEncoding=utf8&useSSL=false&serverTimezone=UTC"); dataSource.setUser("root"); // 你的mysql账号 dataSource.setPassword("你的密码"); // 你的mysql密码 dataSource=ds; } } } return dataSource; } }

这里有个注意点,就是我们用来一个ds这样的零时变量,最后再赋值回去,这样的作用是什么:因为有指令重排序这样的问题,所以如果没有这个零时变量,其他线程可能最坏情况下拿到还没初始化,并且还没有set的database对象,因为即使有锁,外线程依旧可以拿到里面的对象,因为DataSource是static修饰的,所有线程是共用的这一个

二,阻塞队列

队列特点:先进先出

阻塞队列的特点:1:线程安全,2:阻塞功能(阻塞队列为空的时候尝试出的时候,队列满尝试入的情况)

阻塞功能可以帮助实现生产者-消费者模型:1:降低资源的竞争,2:解决耦合(耦合就是代码功能的关联性,我们追求低耦合,关联性越低越好),3:削峰填谷(可以使请求高峰以及请求低谷的时候,服务器依旧可以按照自己的节奏来执行)

生产者-消费者的不足:1:单个请求的响应时间会受影响,2:使服务器结构更加复杂,3:这个模型更适合异步

阻塞队列的相关方法展示:

方便分为峰值和谷值来展示

import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; //阻塞队列 public class demo8 { //生产快,但是消费慢,因为消费要有sleep,所以队列就产生了阻塞,每次保证队列只有两个元素 //如果我不设置队列大小,不用阻塞队列,生产者就会疯狂的生成,然后占满内存,抛出异常 public static void main1(String[] args) { BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2); //生产者 Thread t1=new Thread(()->{ for (int i = 0; i < 500; i++) { try { blockingQueue.put(i); System.out.println("生产了"+i); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); //消费者 Thread t2=new Thread(()->{ for (int i = 0; i < 500; i++) { try { Thread.sleep(500); int take=blockingQueue.take(); System.out.println("消费了"+take); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } //消费快,生成慢 public static void main(String[] args) { BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2); //生产者 Thread t1=new Thread(()->{ for (int i = 0; i < 500; i++) { try { Thread.sleep(500); blockingQueue.put(i); System.out.println("生产了"+i); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); //消费者 Thread t2=new Thread(()->{ for (int i = 0; i < 500; i++) { try { int take=blockingQueue.take(); System.out.println("消费了"+take); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } }

对于代码的解释:如果我们不使用阻塞队列进行阻塞的话,会出现

1:瞬时流量洪峰:生产者疯狂生成任务,消费处理不过来 →队列满,生产者 put 阻塞(防止 OOM)。

什么是OOM:JVM 堆内存已经全部用光,还想继续创建新对象,JVM 直接抛OutOfMemoryError异常,程序直接崩溃。

2:流量低谷:没有新任务产生,队列全部消费完 →队列空,消费者 take 阻塞,CPU 不轮询空转

什么是空转:线程一直在 CPU 循环做无效判断,啥业务活不干,但是一直占用 CPU,白白消耗 CPU 资源。

阻塞队列就很好地解决了以上的问题

自己实现简单的阻塞队列:

//自己创建的阻塞队列 class MyBlockingQueue{ Object locker=new Object(); private String[] array = new String[1000]; private int head = 0; private int tail = 0; private int size = 0; public MyBlockingQueue(int capacity){ array=new String[capacity]; } public void put(String elm) throws InterruptedException { synchronized (locker) { //队列是满的 while (size == array.length) { locker.wait(); } //队列不满开始放东西 array[tail] = elm; tail++; if (tail >= array.length) { tail = 0; } size++; locker.notify(); } } public String take() throws InterruptedException { synchronized (locker) { //当队列为空的时候 while (size==0){ locker.wait(); } //当队列不为空的时候 String elm = array[head]; head++; if (head >= array.length) { head = 0; } size--; locker.notify(); return elm; } } } public class demo3 { public static void main(String[] args) { MyBlockingQueue myBlockingQueue=new MyBlockingQueue(2); //生产者 Thread t1=new Thread(()->{ for (int i = 0; i < 500; i++) { try { Thread.sleep(500); myBlockingQueue.put(i+""); System.out.println("生产了"+i); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); //消费者 Thread t2=new Thread(()->{ for (int i = 0; i < 500; i++) { try { String take=myBlockingQueue.take(); System.out.println("消费了"+take); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } }

阻塞我们用到的是wait和notify的组合,因为我们需要达成某种条件才会阻塞,并且达到某种条件才会唤醒,不能单纯的sleep以及join,并且我们这里用到的循环是while,因为while可以有再次判断的机制

三,线程池

当我们刚引入线程的时候,我们会发现线程比进程好太多了,但是接下来我们还是会发现线程的使用开销也大了一些,所以我们再引入了线程池,也就是把线程提前创建好放在里面,这样就比操作系统创建的效率来的更高

这是一台电脑的基本组成,当我们调用start来创建线程的时候,我们会有从用户态到内核态再到用户态的切换,这样会消耗一些时间和内存,但是线程池是纯用户态的操作,会更加节省开销

线程池的构造

我们来介绍一下这里的参数

(1)corePoolSize(核心线程数),maximumPoolSize(最大线程数)

这里涉及到两类线程1:核心线程(在创建线程池当初就已经有了)2:非核心线程(因为任务太多额外创建的),两者相加就是最大线程数

(2)keepAliveTime (数值),unit(单位)

这里描述了非核心现场释放的时机

(3)workQueue(任务队列)

这里放要执行的任务,并且可以保证线程安全,可以让调用者手动指定队列

(4)threadFactory(线程工厂)

工厂设计模式:有一个工厂类,可以不依赖构造方法

可以自己提供工厂类,当然也可以用系统提供的工厂类(里面有两种)

(5)handler(拒绝策略)

任务队列到达上限,就会触发拒绝策略,主要有四种1:抛出异常,

2:调用submit这个线程自己负责来执行,

3:把任务队列中,最老的任务丢弃,把空的留给新的,

4:把任务队列中,最近添加的任务丢弃,把空的留给更加新的

线程池的创建

MyThreadFactory myThreadFactory=new MyThreadFactory("消息业务",false); ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor( 2, 4, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), myThreadFactory, new ThreadPoolExecutor.AbortPolicy() );
package thread; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; public class MyThreadFactory implements ThreadFactory { //获取旧的编号,并且进行自增 private final AtomicInteger threadIndex = new AtomicInteger(1); private final String threadName; private final boolean isDaemon; public MyThreadFactory(String threadName, boolean isDaemon) { this.threadName = threadName; this.isDaemon = isDaemon; } @Override public Thread newThread(Runnable r) { //先自定义名字 String threadname= this.threadName + threadIndex.getAndIncrement(); //创建线程 Thread thread = new Thread(r,threadname); //设置守护线程 thread.setDaemon(isDaemon); //设置未捕获异常处理器:线程运行抛出异常会进入这里 thread.setUncaughtExceptionHandler((t, e) -> { System.err.println("【线程异常】线程名:" + t.getName() + ",异常信息:" + e.getMessage()); e.printStackTrace(); }); return thread; // 返回给线程池使用 } }

这是我们自己创建了一个线程工厂然后传入进去,当然我们也可以用操作系统自带的

不过操作系统还提供了一个更加简单的构造方法,使用Excutor这个工厂类来创建线程池

前面两个是普通线程池,可以自动扩容,三四是指定了线程池的大小,六七十控制在未来的某个时间点执行,其他的可以自行去了解一下

并且我们往线程池中添加任务是使用submit关键字

自己实现固定数量的线程池

//自己创建线程池 class MyThreadPool{ private BlockingQueue<Runnable> blockingQueue=new ArrayBlockingQueue<>(10); public void submit(Runnable runnable) throws InterruptedException { blockingQueue.put(runnable); } //添加构造方法 public MyThreadPool(int size){ for (int i = 0; i < size; i++) { Thread t=new Thread(()->{ try { while (true) { Runnable runnable = blockingQueue.take(); runnable.run();} } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); t.setDaemon(true); } } }

四,定时器

定时器就和闹钟一样,在规定时间后进行执行

定时器的使用

public static void main(String[] args) throws InterruptedException { Timer timer=new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("定时任务执行了"); } },1000); Thread.sleep(2000); timer.cancel(); }

自己实现简易定时器

自己实现定时器的主要要点:(1)因为编译器的定时器需要我们传入任务然后时间,所以我们也需要创建一个任务类,里面定义了runnable以及时间(有时间间隔delay,也有任务时间time)

(2)我们用到优先级队列来存储任务,并且在任务类里面添加比较方法,让优先级队列知道是通过时间来进行排序的

(3)通过锁来使操作原子化,并且锁的位置也很重要

(4)通过wait/notify来进行阻塞等待以及唤醒,第一个wait是等待队列不为空,第二个等待是为了等待到达任务的执行时间,两个等待同时因为schedule方法里面的notify来唤醒,因为第一个等待本身就是等待不为空,schedule方法里面添加了方法所以就唤醒,第二个是因为可能新添加的任务时间更早,可能需要重新排序,但是在我们add方法执行后的瞬间就会重新进行排序了,所以同时在这里唤醒就是合理的

import java.util.PriorityQueue; //自己实现定时器 //先得有一个任务类,定时器中的任务在这里编写以及初始化 class TimerTask implements Comparable<TimerTask>{ private Runnable runnable; //目的是判定是否到达执行时间 private long time; //delay是时间间隔 public TimerTask(Runnable runnable, long delay) { this.runnable = runnable; this.time = System.currentTimeMillis() + delay; } //在MyTimer里面通过run方法进行执行 public void run(){ runnable.run(); } public long getTime() { return time; } @Override public int compareTo(TimerTask o) { return (int)(this.time - o.time); } } class MyTimer { Object locker =new Object(); private PriorityQueue<TimerTask> queue = new PriorityQueue<>(); public void schedule(Runnable runnable, long delay) { synchronized (locker){ TimerTask timerTask = new TimerTask(runnable, delay); queue.add(timerTask); locker.notify(); } } //去执行任务 public MyTimer() { Thread thread = new Thread(() -> { while (true) { synchronized (locker) { TimerTask timerTask = queue.peek(); try { while (timerTask == null) {//队列为空 locker.wait(); timerTask = queue.peek(); } long currentTime = System.currentTimeMillis(); if (currentTime >= timerTask.getTime()) {//到达执行时间 queue.poll().run(); } else { locker.wait(timerTask.getTime() - currentTime); } }catch (InterruptedException e){ throw new RuntimeException("wait被终止"); } } } }); thread.setDaemon(true); thread.start(); } } public class demo12 { public static void main(String[] args) throws InterruptedException { MyTimer myTimer = new MyTimer(); myTimer.schedule(() -> System.out.println("定时任务执行了1000"), 1000); myTimer.schedule(() -> System.out.println("定时任务执行了2000"), 2000); myTimer.schedule(() -> System.out.println("定时任务执行了3000"), 3000); Thread.sleep(5000); } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询