五. 单例模式
5.1 饿汉模式
理解饿汉,因为太饿了,所以一拿到东西就想吃,这里就是类加载的过程就直接创建唯一实例。
好处:没有线程安全问题
坏处:不像懒汉模式调用才创建实例,这个是类加载就创建实例,所以如果一直没调用,也得创建实例浪费资源。
//饿汉模式//单例实体classSingleton { //唯一实例的本体privatestaticSingletoninstance=newSingleton(); //获取到实例的方法publicstaticSingletongetInstance() { returninstance; } //禁止外部new实例,构造器私有化privateSingleton() { } } publicclassDemo8 { publicstaticvoidmain(String[] args) { Singletons1=Singleton.getInstance(); Singletons2=Singleton.getInstance(); } }
5.2 懒汉模式
懒汉就是懒加载,在类加载的时候不会去创建实例,在第一次调用的时候才会创建实例。
非必要不创建
//懒汉模式实现单例classSingletonLazy { //先置为空privatestaticSingletonLazyinstance=null; publicstaticSingletonLazygetInstance() { //只有调用getInstance方法时,才会去newif (instance==null) { instance=newSingletonLazy(); } returninstance; } privateSingletonLazy() { } } publicclassDemo9 { publicstaticvoidmain(String[] args) { SingletonLazys1=SingletonLazy.getInstance(); SingletonLazys2=SingletonLazy.getInstance(); //s1 和 s2 指向的是同一个实例System.out.println(s1==s2); } }
5.3 饿汉模式和懒汉模式的线程安全问题
显而易见,对于饿汉模式,在多线程中时线程安全的,因为并没有发生修改的操作,而懒汉模式在多线程中就存在线程安全的问题,通过代码可以看见,我们需要判断instance是否为空,然后执行new对象的操作,因为线程的执行是一个抢占式的过程,所以在这里我们需要对懒汉模式进行加锁:
//懒汉模式实现单例classSingletonLazy { //先置为空volatileprivatestaticSingletonLazyinstance=null; publicstaticSingletonLazygetInstance() { //只有调用getInstance方法时,才会去newif (instance==null) { //加锁,保证线程安全问题synchronized (SingletonLazy.class) { if (instance==null) { instance=newSingletonLazy(); } } } returninstance; } privateSingletonLazy() { } } publicclassDemo9 { publicstaticvoidmain(String[] args) { SingletonLazys1=SingletonLazy.getInstance(); SingletonLazys2=SingletonLazy.getInstance(); //s1 和 s2 指向的是同一个实例System.out.println(s1==s2); } }
注:
- 对于 instance 用 volatile 修饰的原因是禁止指令重排序问题;
- 在getInstance方法中,我们为什么不直接对这个方法进行加锁,而是采用一个先判断的形式,再决定要不要对其加锁,这样做的好处是提高程序执行的效率,
六. 阻塞队列
6.1 概念
阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型.
6.2 生产者消费者模型
概念:假设有两个进程(或线程)A、B和一个固定大小的缓冲区,A进程生产数据放入缓冲区,B进程从缓冲区中取出数据进行计算,这就是一个简单的生产者-消费者模型。这里的A进程相当于生产者,B进程相当于消费者。
为什么要用生产者消费者模型?
在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完数据才能够继续生产数据,因为生产过多的数据可能会导致存储不足;同理如果消费者的速度大于生产者那么消费者就会经常处理等待状态,所以为了达到生产者和消费者生产数据和消费数据之间的平衡,那么就需要一个缓冲区用来存储生产者生产的数据,所以就引入了生产者-消费者模式
简单来说,这里缓冲区的作用就是为了平衡生产者和消费者的数据处理能力,一方面起到缓存作用,另一方面达到解耦合作用。
生产者消费者模型代码:
//生产这消费者模型publicclassDemo10 { publicstaticvoidmain(String[] args) { //阻塞队列BlockingQueue<Integer>blockingQueue=newLinkedBlockingQueue<>(); //消费者线程Threadt1=newThread(()->{ while (true){ try { intvalue=blockingQueue.take(); System.out.println("消费元素:"+value); } catch (InterruptedExceptione) { e.printStackTrace(); } } }); t1.start(); //生产者线程Threadt2=newThread(()->{ intvalue=0; while (true){ try { System.out.println("生产元素:"+value); blockingQueue.put(value); value++; Thread.sleep(1000); } catch (InterruptedExceptione) { e.printStackTrace(); } } }); t2.start(); } }
6.3 模拟实现一个阻塞队列
我们可以分为三步来实现:
- 先实现一个普通队列
- 加上线程安全
- 加上阻塞功能
//模拟实现一个阻塞队列classMyBlockingQueue { privateint[] array=newint[10000]; //[head,tail)之间为有效元素volatileprivateinthead=0; volatileprivateinttail=0; volatileprivateintsize=0; synchronizedpublicvoidput(intelem) throwsInterruptedException { //判断队列是否满了if (size==array.length) { this.wait(); } array[tail] =elem; tail++; //判断tail是否达到末尾,如果达到了,把tail置为0,从头开始if (tail==array.length) { tail=0; } size++; this.notify(); } synchronizedpublicIntegertake() throwsInterruptedException { //判断队列是否为空if (size==0) { this.wait(); } intvalue=array[head]; head++; if (head==array.length) { head=0; } size--; this.notify(); returnvalue; } }
七. 定时器
7.1 定时器概念
定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码.
- 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
- schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)
//实现一个定时器,先执行任务2,等待2s后再执行任务1publicclassDemo12 { publicstaticvoidmain(String[] args) { Timertimer=newTimer(); timer.schedule(newTimerTask() { publicvoidrun() { System.out.println("任务1"); } },2000);//等待2s后再执行任务1System.out.println("任务2"); } }
7.2 模拟实现定时器
//模拟实现一个定时器//表示一个任务classMytaskimplementsComparable<Mytask> { publicRunnablerunnable; publiclongtime; publicMytask(Runnablerunnable, longdelay) { this.runnable=runnable; //当前时刻的时间戳+定时器参数列表的时间this.time=System.currentTimeMillis() +delay; } publicintcompareTo(Mytasko) { //确保每次取出的是时间最小的元素return (int) (this.time-o.time); } } classMyTimer { privatePriorityBlockingQueue<Mytask>queue=newPriorityBlockingQueue<>(); publicvoidschedule(Runnablerunnable, longdelay) { //根据参数,构造Mytask,插入到队列Mytaskmytask=newMytask(runnable, delay); queue.put(mytask); synchronized (locker) { locker.notify(); } } //创建锁对象privateObjectlocker=newObject(); //创建线程,执行任务publicMyTimer() { Threadt=newThread(() -> { while (true) { try { synchronized (locker) { Mytaskmytask=queue.take(); //获取当前时间longcurTime=System.currentTimeMillis(); //判断时间是否到了if (mytask.time<=curTime) { mytask.runnable.run(); } //时间没到else { //取出当前执行的任务,重新塞回阻塞队列中queue.put(mytask); locker.wait(mytask.time-curTime); } } } catch (InterruptedExceptione) { e.printStackTrace(); } } }); t.start(); } } publicclassDemo13 { publicstaticvoidmain(String[] args) { MyTimermyTimer=newMyTimer(); myTimer.schedule(newRunnable() { publicvoidrun() { System.out.println("任务1"); } }, 1000); myTimer.schedule(newRunnable() { publicvoidrun() { System.out.println("任务2"); } }, 1000); myTimer.schedule(newRunnable() { publicvoidrun() { System.out.println("任务3"); } }, 1000); System.out.println("最开始的任务"); } }
八. 线程池
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。
8.1 构造方法参数解析
jdk1.8官方文档关于ThreadPoolExecutor的构造方法的参数解析:
corePoolSize:核心线程数(相当于一个公司的正式员工);
maximumPoolSize:最大线程数(相当于一个公司的正式员工+实习生);
如果当前任务比较多的时候,线程池会多创建一些 “临时线程” 去帮助解决任务;
如果任务比较少,线程池会多出来的 “临时线程” 给销毁掉,但是核心线程数不会销毁;
long keepAliveTime:描述了 “临时线程” 最大存活时间,超出这个时间,就会被销毁;
TimeUnit unit:是数值的单位;
BlockingQueue<Runnable>:阻塞队列;
ThreadFactroy threadFactory:线程工厂,用来创建线程;
RejectedExecutionHandler handler:线程池的拒绝策略;如果线程池满了,继续往里面添加任务,就是触发拒绝策略
8.2 模拟实现线程池
classMyThreadPool { privateBlockingQueue<Runnable>queue=newLinkedBlockingQueue(); publicvoidsubmit(Runnablerunnable) throwsInterruptedException { queue.put(runnable); } publicMyThreadPool(intn) { for (inti=0; i<n; i++) { Threadt=newThread(() -> { try { while (true) { Runnablerunnable=queue.take(); runnable.run(); } } catch (InterruptedExceptione) { e.printStackTrace(); } }); t.start(); } } } publicclassDemo15 { publicstaticvoidmain(String[] args) throwsInterruptedException { MyThreadPoolmyThreadPool=newMyThreadPool(10); for (inti=0; i<1000; i++) { intret=i; myThreadPool.submit(newRunnable() { publicvoidrun() { System.out.println("任务 "+ret); } }); } } }
/