JUC
volatile
volatile是Java虚拟机提供的轻量级的同步机制。它保证可见性,不保证原子性,禁止指令重排序。
如何保证原子性:用AtomicInteger代替int,不用sync
JMM-java memory model
JMM是一种抽象的概念,并不真实存在。它描述的是一组规范或规则,通过这个规范定义了程序中各个变量的访问方式。
JMM关于同步的规范:
- 线程解锁之前,必须把共享变量的值刷新回主内存。
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但是线程对变量 的操作必须在工作内存中进行,首先将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成再将变量写回主内存。不能直接操控主内存中的变量,各个线程的工作内存保存着主内存的变量副本拷贝,因此不同的线程之间无法直接互相访问,访问都得通过主内存进行。
JMM三大特性:
- 可见性—一个线程修改了变量,其他线程必须马上知道
- 原子性—完整性,要么全部执行,要么全部不执行
- 有序性—只在多线程情况考虑—加内存屏障保证指令有序
哪里使用到了volatile:单例,JUC
DCL单例模式下会产生指令重排问题,所以要加volatile防止指令重排
CAS
compare and set
CAS的底层原理,对unsafe的理解?
CAS是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值。
CAS并发原语体现在JAVA语言中就是sun.misc.unsafe类中的各个方法。调用CAS方法,JVM会帮我们实现出CAS的汇编指令。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);//获得初始值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
compareAndSwapInt是通过Atomic::cmpxchg实现。
CAS缺点:
- 上述代码的getAndAddInt有个do–while循环;如果CAS失败,会一直进行循环尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销【毕竟它没加锁】。
- 只能保证一个共享变量的原子操作。
- 会出现ABA问题。
原子类AtomicInteger的ABA问题,原子更新引用?
CAS–> UnSafe–>CAS底层思想–>ABA–>原子引用更新–>如何规避ABA问题‘
用带时间戳的AtomicStampedReference代替没有时间戳的AtomicReference
集合不安全
并发修改异常问题:ArrayList
Vector jdk1.0 就出来了,而ArrayList jdk1.2才出来。
Vector是用synchronized加锁的,而ArrayList没有加锁-----加锁效率太低
ArrayList线程不安全:
解决方案
* 1.用Vector,内部加了锁
* 2.Collections.synchronizedList(new ArrayList<>());
* 3.new CopyOnWriteArrayList<>();
利用CopyOnWriteArrayList进行写时复制。
往一个容器添加元素的时候,不直接添加进容器,而是先将容器进行拷贝,操作新容器,再将原容器的引用指向新的容器;这样的好处是容器可以并发读,而不用加锁;只需要对写操作加锁。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
各种锁–可重入锁(递归锁)、乐观锁、自旋锁、独占锁/共享锁、读写锁
公平锁—想要获得锁,就要在队列里排队等获取锁。
非公平锁—新来的线程可以直接插队获得锁,获取失败就去排队。
可重入锁指的是同一线程外层函数获得锁之后,内层递归函数仍然额能获取该锁的代码;同一线程在外层获得锁后,进入内层方法会自动获得锁
自旋锁—尝试获取锁的线程不会立即阻塞,而是采用循环的方式去上市获取锁,减少线程的上下文切换的消耗,缺点是循环消耗CPU。
public final int getAndAddInt(Objet var1, long var2, int var4){
int var5;
do{
var5 = this.getIntVolatile(var1,var2);
}while(this.compareAndSwapInt(var1,var2,var5,var5+var4));
return var5;
}
自旋锁实现:
/**
* 自旋锁实现
*/
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();//null
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+" come in...");
while (!atomicReference.compareAndSet(null,thread)){
System.out.println("我他妈就不给你放锁,你能咋地");
}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+" invoke unlock");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLockDemo.myUnlock();
}
},"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnlock();
},"t2").start();
}
}
独占锁:该锁一次只能被一个线程持有;如ReentrantLock和synchronized
共享锁:该锁可以被多个线程持有。如ReentrantLock对读操作是共享锁,写锁是独占锁
读写锁代码
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
public void put(String key,Object value){
rwlock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写入: "+key);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写入完成。。");
}catch (Exception e){
e.printStackTrace();
}finally {
rwlock.writeLock().unlock();
}
}
public void get(String key){
rwlock.readLock().lock();
try {
System.out.printf(Thread.currentThread().getName()+" 正在读取:");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 读取完成。。。。");
} catch (Exception e) {
e.printStackTrace();
} finally {
rwlock.readLock().unlock();
}
}
}
public class ReadWriteLock {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i=0;i<5;i++) {
final int ii = i;
new Thread(()->{
myCache.put(ii+"",ii+"");
},String.valueOf(i)).start();
}
for (int i=0;i<5;i++) {
final int ii = i;
new Thread(()->{
myCache.get(ii+"");
},String.valueOf(i)).start();
}
}
}
阻塞队列
分类:
- ArrayBkockingQueue:数组结构构成的有界阻塞队列
- LinkedBlockingQueue:链表结构组成的有界阻塞队列
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayQueue:优先级队列实现的延迟无界阻塞队列
- SynchronousQueue:不存储元素的阻塞队列,也就是单个元素的队列
- LinkedTransferQueue:链表结构构成的无界阻塞队列
- LinkedBlockigDeque:链表结构组成的双向阻塞队列
ArrayBkockingQueue:
synchronized和lock的区别
-
构成
synchronized是关键字,属于JVM层面
构成:monitorenter(底层是通过monitor对象完成,wait和notify等方法也是依赖于monitor对象)和monitorexit
lock是具体的类 java.util.concurrent.locks.lock 是api层面的锁。 -
使用方法
synchronized不需要用户手动去释放锁,执行完系统会自动让线程释放。
Reentrantlock需要用户手动去释放锁,不释放可能就死锁了。需要lock()和unlock()方法配合使用 -
等待是否中断
synchronized不可中断,除非抛出异常或正常结束
Reentrantlock可中断:1.设置超时方法 trylock();2.lockInterruptibly()放在代码块中可以调用interrupt()方法中断。 -
是否公平
sync非公平;Reentrantlock可选择是否公平 -
锁绑定多个条件
sync没有;lock可以分组唤醒,精确唤醒,而不是像sync一样要么随机唤醒要么全部唤醒
线程池
特点:线程复用,控制最大并发,管理线程
优点:降低资源消耗,挺高响应速度,提高可管理性。
线程池重点:
Executor.newFixedThreadPool(int)----执行长期任务
Executor.newSignleThreadExecutor()—一个任务一个任务执行的场景
Executor.newCachedThreadPool()—执行许多短期异步的小程序或负载轻的服务器
上述线程池都是用的ThreadPoolExecutor实现
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//七大参数
public ThreadPoolExecutor(int corePoolSize,//核心线程数,常驻
int maximumPoolSize,//最大容纳的线程数
long keepAliveTime,//多余的空闲贤臣的存货时间,当前线程数超过核心线程数后,当空闲时间达到keepAliveTime值时,多余线程被销毁直到只剩核心个数个线程。
TimeUnit unit,//keepalive的单位
BlockingQueue<Runnable> workQueue,//被提交但是尚未执行的任务
ThreadFactory threadFactory,//生成线程池中工作线程的线程工厂
RejectedExecutionHandler handler//拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
当前线程满了并且阻塞队列满了并且还没达到最大线程数,那么开新线程并且新任务直接加塞到新开的线程。 -----也即是非公平锁
线程池的底层工作原理
线程池的拒绝策略:
- AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统运行。
- CallerRunsPolicy:“调用者运行”一种调节机制,不抛弃任务也不抛异常,而是将某些任务回退给调用者,从而降低流量。
- DiscardOldestPolicy:抛弃队列里等待最久的任务,然后把当前任务加入队列再次尝试提交。
- DiscardPolicy:直接丢弃任务,无异常不处理。—如果允许任务丢失这个是最好的方法。
线程必须由线程池创建,不能new
不允许用Executors,必须用ThreadPoolExecutor方式。
Executors中的阻塞队列长度为Integer.MAX_VALUE,不符合实际。
如何合理配置线程数:
CPU密集型 CPU*2
IO密集型
C
P
U
1
−
D
C
,
D
C
≈
0.9
\frac{CPU}{1-DC},DC \approx 0.9
1−DCCPU,DC≈0.9,
死锁编码及定位分析
在对应文件夹下用 jps -l 定位进程号,再用jstack 进程号 找到死锁看