尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java多线程四个案例

Java多线程四个案例 目录1单例模式2阻塞队列3线程池4定时器一单例模式单例模式也就是一个进程中只允许有一个实例是我们平时在开发中比较重要常见的一种模式我们单例模式一般分为两种一种是饿汉模式一种是懒汉模式饿汉模式//饿汉模式 class Singleton{ //static保证了成员或者方法是属于类的不属于实例所以你不能通过new实例来调用它刚刚好保证单例因为new实例会创建多个对象 private static Singleton instancenew 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判断3volatiledataSource实现单例//dataSource实现单例模式 class DataSource { private static volatile DataSource dataSource; private DataSource() { } public static DataSource getInstance() { if (dataSource null) { synchronized (DataSource.class) { if (dataSource null) { DataSource dsnew DataSource(); dataSource.setUrl(jdbc:mysql://127.0.0.1:3306/java117?characterEncodingutf8useSSLfalseserverTimezoneUTC); dataSource.setUser(root); // 你的mysql账号 dataSource.setPassword(你的密码); // 你的mysql密码 dataSourceds; } } } 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) { BlockingQueueInteger blockingQueue new ArrayBlockingQueue(2); //生产者 Thread t1new Thread(()-{ for (int i 0; i 500; i) { try { blockingQueue.put(i); System.out.println(生产了i); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); //消费者 Thread t2new Thread(()-{ for (int i 0; i 500; i) { try { Thread.sleep(500); int takeblockingQueue.take(); System.out.println(消费了take); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } //消费快生成慢 public static void main(String[] args) { BlockingQueueInteger blockingQueue new ArrayBlockingQueue(2); //生产者 Thread t1new 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 t2new Thread(()-{ for (int i 0; i 500; i) { try { int takeblockingQueue.take(); System.out.println(消费了take); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } }对于代码的解释如果我们不使用阻塞队列进行阻塞的话会出现1瞬时流量洪峰生产者疯狂生成任务消费处理不过来 →队列满生产者 put 阻塞防止 OOM。什么是OOMJVM 堆内存已经全部用光还想继续创建新对象JVM 直接抛OutOfMemoryError异常程序直接崩溃。2流量低谷没有新任务产生队列全部消费完 →队列空消费者 take 阻塞CPU 不轮询空转什么是空转线程一直在 CPU 循环做无效判断啥业务活不干但是一直占用 CPU白白消耗 CPU 资源。阻塞队列就很好地解决了以上的问题自己实现简单的阻塞队列//自己创建的阻塞队列 class MyBlockingQueue{ Object lockernew Object(); private String[] array new String[1000]; private int head 0; private int tail 0; private int size 0; public MyBlockingQueue(int capacity){ arraynew 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 (size0){ 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 myBlockingQueuenew MyBlockingQueue(2); //生产者 Thread t1new 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 t2new Thread(()-{ for (int i 0; i 500; i) { try { String takemyBlockingQueue.take(); System.out.println(消费了take); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } }阻塞我们用到的是wait和notify的组合因为我们需要达成某种条件才会阻塞并且达到某种条件才会唤醒不能单纯的sleep以及join并且我们这里用到的循环是while因为while可以有再次判断的机制三线程池当我们刚引入线程的时候我们会发现线程比进程好太多了但是接下来我们还是会发现线程的使用开销也大了一些所以我们再引入了线程池也就是把线程提前创建好放在里面这样就比操作系统创建的效率来的更高这是一台电脑的基本组成当我们调用start来创建线程的时候我们会有从用户态到内核态再到用户态的切换这样会消耗一些时间和内存但是线程池是纯用户态的操作会更加节省开销线程池的构造我们来介绍一下这里的参数1corePoolSize(核心线程数)maximumPoolSize最大线程数这里涉及到两类线程1核心线程在创建线程池当初就已经有了2非核心线程因为任务太多额外创建的两者相加就是最大线程数2keepAliveTime 数值unit单位这里描述了非核心现场释放的时机3workQueue任务队列这里放要执行的任务并且可以保证线程安全可以让调用者手动指定队列4threadFactory线程工厂工厂设计模式有一个工厂类可以不依赖构造方法可以自己提供工厂类当然也可以用系统提供的工厂类里面有两种5handler拒绝策略任务队列到达上限就会触发拒绝策略主要有四种1抛出异常2调用submit这个线程自己负责来执行3把任务队列中最老的任务丢弃把空的留给新的4把任务队列中最近添加的任务丢弃把空的留给更加新的线程池的创建MyThreadFactory myThreadFactorynew MyThreadFactory(消息业务,false); ThreadPoolExecutor threadPoolExecutornew 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 BlockingQueueRunnable blockingQueuenew ArrayBlockingQueue(10); public void submit(Runnable runnable) throws InterruptedException { blockingQueue.put(runnable); } //添加构造方法 public MyThreadPool(int size){ for (int i 0; i size; i) { Thread tnew 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 timernew Timer(); timer.schedule(new TimerTask() { Override public void run() { System.out.println(定时任务执行了); } },1000); Thread.sleep(2000); timer.cancel(); }自己实现简易定时器自己实现定时器的主要要点1因为编译器的定时器需要我们传入任务然后时间所以我们也需要创建一个任务类里面定义了runnable以及时间有时间间隔delay也有任务时间time2我们用到优先级队列来存储任务并且在任务类里面添加比较方法让优先级队列知道是通过时间来进行排序的3通过锁来使操作原子化并且锁的位置也很重要4通过wait/notify来进行阻塞等待以及唤醒第一个wait是等待队列不为空第二个等待是为了等待到达任务的执行时间两个等待同时因为schedule方法里面的notify来唤醒因为第一个等待本身就是等待不为空schedule方法里面添加了方法所以就唤醒第二个是因为可能新添加的任务时间更早可能需要重新排序但是在我们add方法执行后的瞬间就会重新进行排序了所以同时在这里唤醒就是合理的import java.util.PriorityQueue; //自己实现定时器 //先得有一个任务类定时器中的任务在这里编写以及初始化 class TimerTask implements ComparableTimerTask{ 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 PriorityQueueTimerTask 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); } }
返回列表