JUC
synchronize
底层原理
在执行同步代码块之前之后都有一个monitor字
样,其中前面的是monitorenter,后面的是离开
monitorexit,不难想象一个线程也执行同步代码
块,首先要获取锁,而获取锁的过程就是
monitorenter ,在执行完代码块之后,要释放
锁,释放锁就是执行monitorexit指令。
为什么会有两个monitorexit呢?
这个主要是防止在同步代码块中线程因异常退
出,而锁没有得到释放,这必然会造成死锁(等
待的线程永远获取不到锁)。因此最后一个
monitorexit是保证在异常情况下,锁也可以得到
释放,避免出现死锁
线程的状态
线程调用sleep 线程进入TimeWaiting,不会释放锁
线程调用wait 线程进入wait状态,会释放锁
当一个线程进入,另一个线程已经拿到锁(
synchronize)
另一个线程进入堵塞blocked状态
当一个线程进入,另一个线程已经拿到锁(lock) 另一个线程进入waiting状态
线程不安全
谈谈你对集合的理解
说一下你常见的异常有那些
ArithmeticException 进行算数计算时出现的异常
IOException 进行IO流传输的时候容易遇到的异常
ArrayIndexOutOfBoundException 向数组中存取元素时容易发生的索引越界异常
NullPointerException 访问空对象时发生的空指针异常
NumberFromatException
数字转化格式异常,当将字符串转换成数字,数字转
换无效.
java.util.ConcurrentModificationException
并发修改异常:多线程对Vector、ArrayList在迭
代的时候如果同时对其进行修改就会抛出java.
util.ConcurrentModificationException异常
说一下List集合
list集合底层为数组+链表的格式,1.8之后引入了红
黑树结构,他可以通过扩容实现动态增长,,容量不
够时,扩容之原来的1.5倍,通过右移一位实现的.(有
序,可重复)
ArrayList
底层数据结构是数组。线程不安全
LinkedList
底层数据结构是链表。线程不安全
Vector
底层数据结构是数组。线程安全,采用的是悲观锁
机制,效率较低
copyonwriteArraylist,底层就是ArrayList,采用
lock锁,线程安全,效率较高
说一下set集合
set集合底层为HashMap,用HashMap的key去保
存Set中的值,而HashMap中的V是一个Object
对象常量
HashSet,无序,去重
TreeSet,有序,去重,底层红黑树,这个有序就需要
有一个compare比较器的过程,因此会需要参数
实现Comparable接口。
CopyOnWriteArraySet:线程安全
说一下HashMap集合底层原理
jdk1.7中,是由数组+链表组成,这种结构有一种链
表的循环问题,当我们调用get方法时,出现死循环,
1.8之后是数组+链表+红黑树组成的,这种可以解
决死循环的问题,当然在高并发的情况下还会有死
锁问题(当链表超过8,且数组长度超过64才会转成
红黑树,),
高并发情况下我们还是用ConcurrentHashMap
采用的是细粒度锁 所以效率高
加载因子为0.75,底层数组的初始长度为16,初始临
界值为12,当超过这个阈值时,容量扩容为当前的2
倍
加载因子越大,填满的元素越多,空间利用率越
高,但发生冲突的机会变大了;
加载因子越小,填满的元素越少,冲突发生的机
会减小,但空间浪费了更多了,而且还会提高扩
容rehash操作的次数。
特点:无序,键值唯一,值可重复,
单例模式
懒汉式
饿汉式
双重检查(DCL 双端检索机制)
1
Lock锁
ReentrantLock 重入锁 面向对象,多路通知
1
ReentrantReadWriteLock 读写锁 读读共享,读写互斥,写写互斥
1
volatile
Volatile(关键字)是java虚拟机提供的一种轻量级
的同步机制
可见性
java内存模型:每个线程都有属于自己工作空间,并
且是相互隔离的,共享数据储存在主内存中
可见性:两个线程同时操作主内存中的数据,首先会
把主内存的数据copy到各自的工作内存中,然后
进行修改,修改完成后再重新写入主内存中,同时通
知其他线程主内存中数据已经发生改变,其他线程
及时更新各自工作空间的值,即为可见性
不保证原子性 解决方案:Atomic包下的所有类 底层原理:CAS(比较并交换)
禁止JVM指令重排
多线程环境中,由于线程交替执行,由于编译器优化
重排的存在,两个线程中使用的变量能否保证一致
性是无法预测的,,所以为保证唯一程序按照开发者
想要的结果运行,可以禁止指令进行优化重排
在volatile修饰的变量两边设置内存屏障,来保证
不会发生指令重排
CAS
底层原理
unsafe
unsafe是CAS的核心类,他的native修饰的本地方
法可以直接操作指定的内存数据,例如unsafe的
getAndAddInt(this,valueoffset,1),
自旋
通过工作内存的值和主内存的值进行比较,通过
do.while循环进行判断形成自旋,直至两者值一样,
才能跳出自旋,去完成指令.
缺点
高并发情况下,自选时间较长,浪费cpu资源,开销较
大
只能保证一个共享变量的原子性,不适用于代码块
容易引起ABA问题
什么是ABA问题
就是两个线程共同对共享主内存数据进行操作,一
个线程完成了主内存的数据由A-B-A的转变过程,
另一个线程由于执行慢,认为主线程数据没有发生
改变,然后操作完成,所形成的数据不安全问题
怎么解决ABA问题 利用版本号的方式来保证数据安全,解决ABA问题 通过AtomicStampedReference类
1
原子引用更新 AtomicReference类
阻塞队列
概念
队列为空,取数据操作就会阻塞
队列为满,放数据操作就会阻塞
常见的堵塞队列
ArrayBlockingQueue 数组实现的有界阻塞,指定数组的长度
LinkedBlockingQueue
一个由链表结构组成的有界阻塞队列,Integer.
MAX_VALUE
SynchronousQueue
一个不存储元素的阻塞队列
只是起到一个传递数据的作用 必须取和存数据的线程
同时存在 才能往里面放数据
常见方法
add/remove
如果队列满了 会抛出异常
如果队列空了 会抛出异常
offer/poll
如果队列满了 返回false
如果队列空了 返回一个null
put/take
如果队列满了 阻塞 阻塞队列
如果队列空了 阻塞 阻塞队列
塞队列的原理
借助重入锁(乐观锁)+线程通信
ArrayBlockingQueue的put和take使用的是同一把锁 put和take不能同时操作
LinkedBlockingQueue的put和take使用的是不同的锁 put和take能同时操作
线程池
概念
先准备好一些线程, 有任务来了之后直接上手 做
完任务之后不释放资源 线程可以复用.
原理
a.创建线程池之后 线程池里面的线程有零个
b.当有新的任务来了之后
提交的任务数<核心线程数 马上去创建对应个核心线程
提交的任务数>核心线程数 把没有被执行的任务放到阻塞队列当中去进行等待
如果队列满了 当提交的任务<max线程数 创建非核心线程去处理任务
如果队列满了 当提交的任务>max线程数 采用拒绝策略处理任务
创建线程池
a.创建一个固定数量的线程池 适用于执行长期任务
ExecutorService threadPool1 = Executors.newFixedThreadPool(3);
创建一个单线程的线程池 任何时候都只有一个线程在运行 就保证了执行线程的顺序性
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
创建一个可扩展的线程池 适用于短时异步任务
ExecutorService threadPool = Executors.newCachedThreadPool()
允许的创建线程数量为 Integer.MAX_VALUE,
可能会创建大量的线程,从而导致 OOM
自定义线程池
ThreadPoolExecutor ThreadPoolExecutor =new ThreadPoolExecutor()
七大参数
核心线程数:corePoolSize
最大线程数:maximumPoolSize
超时等待时间:keepAliveTime
等待时间单位:TimeUnit.SECONDS
堵塞队列:new ArrayBlockingQueue<>(3),
LinkedBlockingQueue
ArrayBlockingQueue
线程工厂:
privilegedThreadFactory
Executors.defaultThreadFactory()
拒绝策略:new ThreadPoolExecutor.AbortPolicy()
a.AbortPolicy 默认拒绝策略:线程超过线程池最大线程数报错
b.DiscardPolicy 丢弃新任务
c.DiscardOldestPolicy 丢弃老任务 ,用于时效性比较高的需求
d.CallerRunsPolicy
任务来自于哪里 就交给哪里 比较有用的策略 能
够最大限度处理我们的业务
创建线程的方法
直接new
任务类继承Thread 由于java是单继承多实现的特点,这个基本不用
任务类实现Runable
任务类实现Callable
区别
1.是否有返回值
2.是否抛异常
3.方法一个是call run
4.Callable特点:FutureTask可以往里面放Callable和Runnable,get是一个阻塞方法 一旦程序执行完 才能复用
结果
JUC的工具类
CyclicBarrier
所有资源到位之后一起去做某件事情,资源之
间要相互等待
1
CountDownLatch
一个程序需要等待程序做完某些事情之后才能
做某件事情
1
.Semaphore
资源有限 大家要抢夺资源 只用一会儿
1
fork/join框架:把大任务拆分为小任务
1
允许的请求队列长度为 Integer.MAX_VALUE,可能
会堆积大量的请求,从而导致 OOM
评论0
最新资源