一、阻塞队列
ArrayBlockingQueue类:底层是数组,有界,没有无参构造方法
LinkedBlockingQueue类:底层是链表,无界但最多能存放int的最大值,无参构造方法默认容量就是最大值
常用方法:
put(Object o):将参数放入队列,如果放不进去会阻塞
take():取出第一个数据,取不到会阻塞
使用阻塞队列实现生产者消费者模式:
public class Test { public static void main(String[] args) throws InterruptedException { ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1); Foodie foodie = new Foodie(arrayBlockingQueue); Cooker cooker = new Cooker(arrayBlockingQueue); foodie.start(); cooker.start(); } } class Foodie extends Thread{ private ArrayBlockingQueue<String> arrayBlockingQueue; public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue){ this.arrayBlockingQueue = arrayBlockingQueue; } @Override public void run() { while (true) { try { String take = arrayBlockingQueue.take(); System.out.println("生产者消费了一个"+take);} catch (InterruptedException e) { e.printStackTrace(); } } } } class Cooker extends Thread{ private ArrayBlockingQueue<String> arrayBlockingQueue; public Cooker(ArrayBlockingQueue<String> arrayBlockingQueue){ this.arrayBlockingQueue = arrayBlockingQueue; } @Override public void run() { while (true) { try { arrayBlockingQueue.put("汉堡包"); System.out.println("生产者放了一个汉堡包");} catch (InterruptedException e) { e.printStackTrace(); } } } }
二、线程池
每一个线程的启动和结束都是比较消耗时间和资源的,如果在系统中用到很多线程,大量的线程启动和结束操作会导致性能变卡,响应变慢,为了解决这个问题,引入了线程池的设计思想,线程池就是一种生产者消费者模式
主要思想是创建若干个线程放入池子,有任务需要处理时将任务提交到线程池中的任务队列,任务处理完后线程池并不会销毁,而是继续在线程池中等待下一个任务
静态方法创建线程池:
1、使用Executors类中的静态方法static ExecutorService newCachedThreadPool()创建线程池,默认线程池是空的,根据需要创建线程,超过60秒未被使用的线程则销毁,最多创建int最大值个线程
2、使用Executors类中的静态方法static ExecutorService newFixedThreadPool(int nThreads)创建线程池,默认线程池是空的,根据需要创建线程,参数表示线程池最多能够创建的线程,创建的线程将一直存到直到显式调用shutdown()方法
3、这两个方法返回值类型是ExecutorService接口,这个接口里边定义了操作线程的方法,常用的两个方法是:
submit(task):task是需要执行的任务,可以是实现Runnable接口或Callable接口的类对象,也可以是Lambda表达式
shutdown():用于任务执行后关闭线程池
使用ThreadPoolexecutor类创建线程池:
1、上述使用静态方法创建的线程池实际上是使用类该类来创建并返回线程池
2、常用构造方法:
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler ){}
corePoolSize :核心线程数量
maximumPoolSize :最大线程数量
keepAliveTime :空闲线程存活时间的值
unit :存活时间的单位
workQueue :任务队列
threadFactory :线程工厂,指定创建线程的方式
handler :任务拒绝策略,当任务队列已满,新任务不能提交到线程池时触发对新任务
的处理策略
3、任务拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectExecutionException异
常,默认的任务拒绝策略
ThreadPoolExecutor.DiscardPolicy:丢弃任务但不抛出异常,不推荐使用
ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务然后将当
前任务加入任务队列
ThreadPoolExecutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行
三、原子性
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败。即使是多个线程一起执行,一个操作一旦开始,就不会被其他线程所干扰。
volatile不能保证原子性,synchronized可以保证原子性。
原子性操作类,既能保证原子性又比synchronized高效,如Atomiclnteger类
1、构造方法
public AtomicInteger():创建初始值为0的对象
public AtomicInteger(int value):创建指定值的对对象
2、常用方法
方法名 | 说明 |
int get() | 获取值 |
int getAndIncrement() | 以原子方式将当前值加1,返回加1前的旧值 |
int incrementAndGet() | 以原子方式将当前值加1,返回加1后的新值 |
int addAndGet(int value) | 以原子方式将当前值与参数相加,并返回结果 |
int getAndSet(int value) | 以原子方式将当前值设置为参数的值,返回旧值 |
3、原理(底层使用自旋+CAS算法)
自旋:就是重新获取共享变量的操作
CAS算法:
● 线程在修改共享数据时查看共享数据的值与变量副本的值是否相同
● 如果相同说明共享变量的值没有被其他线程修改,可以直接将新值赋给共享数据
● 如果不相同,说明在对变量副本进行操作时有其他线程修改了共享数据,此时不能
修改共享数据,而是重新获取共享数据的值
4、sychronized与CAS的区别
相同点:在多线程的情况下,都可以保证共享数据的安全性
不同的:
1、sychronized总是从最坏的角度出发,认为每次获取数据时,别的线程都有可能修改,所以每次操作共享数据前,都会上锁(悲观锁)
2、CAS是从乐观的角度出发,假设每次获取数据时别的线程都不会修改,所以不上锁,只是在修改共享数据时再查看其他线程有没有修改共享数据,如果有就重新获取新的共享数据,如果没有就直接修改共享数据(乐观锁)
四、并发工具类
HashTable类
● HashMap是线程不安全的,为了保证数据安全性可以使用线程安全的HashTable代替
● HashTable效率比较低下
● HashTable采用sychronized悲观锁,当有线程访问时会将整个集合加锁
ConcurrentHashMap类
● ConcurrentHashMap是线程安全的,效率较高
● 在JDK7和JDK8中实现的原理有区别
● JDK7原理
■ 使用无参构造创建对象时,创建一个默认长度16,加载因子为0.75的数组,数组名为
segment,并且这个数组无法扩容
■ 再创建长度为2的小数组,将该小数组地址存入segment数组的0索引,segment其他索引均为null,这个小数组作为模板数组
■ 在添加元素时会根据元素的哈希值计算出在segment的应存入位置的索引。如果为null
则按照模板数组创建小数组,创建完毕后会进行二次哈希,计算出在小数组中应存入位置的索引,然后直接存入;如果不是null则直接找到小数组进行二次哈希,计算出在小数组中应存入位置的索引,如果小数组需要扩容则扩容到两倍,然后存入,如果小数组不需要扩容就查看该位置有无元素,如果没有元素直接存,如果有元素就调用equals()方法比较,相同的话不存,不相同就形成哈希桶结构
■ 根据添加的原理,该集合实际上是创建了16个哈希表结构
■ 保证线程安全的方式是,当线程对segment某个索引处的哈希表进行操作时对该索引处加锁,而其他索引则不加锁
● JDK8原理
■ 使用无参构造创建对象时并不会创建底层数组,而是在第一次添加数据时初始化长度为16,加载因子为0.75的数组
■ 添加元素时计算应存入的索引,如果索引为null,则利用CAS算法将元素添加到此处;如果不为null,则利用volatile关键字获取当前位置最新的节点地址,将当前元素挂在它下面变成链表,当链表长度大于8时转为红黑树
■ 保证数据安全的方式是对链表或者红黑树头节点加锁,配合悲观锁保证多线程操作集合时的安全性