[toc]
java 内存模型 Java memory model jMM
java 内存八种操作
lock >read > load > use> assing > store >write >unlock
线程安全
定义
当多个线程访问某一个类时,不管运行时环境何种调度方式或者这些进程如何交替执行,并且在主调用代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个就程这个类是线程安全的-
原子性
- AtomicXXX CAS 实现类 Unsafe.compareAndSwapInt
- AtomicAdder 对AtomicLong 的优化 64 需要拆成两个32 位处理
- AtomicRerference 实现了compareAndSet方法
- AtomicIntegerFiledUpdater 要更新的字段必须要volatile标记,非static
- AtomicStampReference CAS的ABA问题 ,增加了一个数据版本号
- 锁
- synchronized: 依赖JVM
- 修饰代码块 作用于调用的对象
- 修饰方法 作用于调用的对象
- 修饰静态方法 作用于所有对象
- 修饰类 作用于所有对象
- Lock:依赖特殊的CPU指令,代码实现,ReentrantLock
- synchronized: 依赖JVM
- 原子性对比
- synchronized : 不可中断,适合竞争不激烈,可读性好
- Lock 可中断锁,多样化同步,竞争激烈时能维持常态
- Atomic:竞争激烈时能维持常态,比lock性能好,只能同步一个值
-
可见性
- 导致共享变量在线程 间不可见的原因
- 线程交叉执行
- 重排序结合线程交叉执行
- 共享变量更新后的值没有在工作内存与主内存间及时更新
- JMM(Java内存模型) 关于synchronized的两条规定
- 线程解锁前,必须把共享变量的最新值刷新到主内存
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁是同一把锁)
- volatile 通过屏障和禁止重排序优化来实现, 并不是原子操作,不能实现线程安全
- 对volatile变量写操作时,会在写操作后面加入一条store屏障质量,将本地内存中的共享变量值刷新到主内存
- 对volatile变量读操作时, 毁在在读操作前加入一条load屏障指令,从主内存中读取共享变量
- 导致共享变量在线程 间不可见的原因
-
有序性
- Java 内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响大单线程程序的执行,却会影响到多线程并发执行的正确性
- volatile ,synchronize,lock
- happens-before 原则,如果两操作次序不能从happens-before推导出来就不能保证他们有序性,cpu可以对他们任意排序
- 程序次序规则: 一个线程内,按照代码顺序,书写在前面的操作先行发生于与书写在后面的操作
- 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock 操作
- volatile变量规则:对一个变量的写操作 先行发生于后面对这个变量的操作
- 传递规则: 如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于 操作C
- 线程启动原则: Thread对象的start()方法先行发生于此线程的每一个动作
- 线程中断原则:对线程interrupt()方法的调用先行发生于比中断线程检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先发生于线程的终止检查,我们可以通过Thread.join()方法结束.Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
安全发布对象
- 发布对象:使一个对象能够被当前范围之外的代码所使用
- 对象逸出:一种错错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见
- 安全发布对象
-
在静态初始化函数中初始化一个对象引用
- 饿汉模式在类加载的时候初始化缺点
- 私有构造函数逻辑过多会影响加载速度
- 如果实例化的类没有被使用会浪费
- 懒汉模式, sync也,双重检测会被cpu指令重排也是不安全的,需要使用volatile+双重检测
- 饿汉模式在类加载的时候初始化缺点
净对象的引用保存到volatile类型域或者AtomicReference对象中
将对象的引用保存到某个正确构造对象的final类型 域中
净对象的引用保存到一个由锁保护的域中
-
单例方法
- 饿汉模式静态
- 懒汉 volatile +代码同步+双重检测
- 枚举
- final不可变
-
- 不可变对象
- final
- 引用不可变 Collections.unmodifiableMap()
- 线程封闭
- 什么是线程封闭?就是把对象封装到一个线程里,只有这个线程能够访问
- ad-hoc 线封闭: 程序控制实现,最糟糕回来
- 堆栈封闭: 局部变量,无并发问题
- ThreadLocal 线程封闭,特别好的封闭方法, 要是记得释放资源,否则会内存泄露
- 内部是一个map
- key是当前线程名称,value就是要封闭的对象
- 常见线程不安全
- stringBuilder 不安全 stringbuffer 安全
- SimpleDateFormat 不安全 DateTimeFormatter 线程安全,第三方joda-time
- ArrayList HashSet HashMap 等Collects
- 先检查后执行: if(condition(a)){ handle(a)}
- 同步容器
- ArraList -> Vector,Stack
- HashMap -> HashTable (key,value都不能为null)
- Collections.synchronizedXX(List,Set,Map)
J.U.C ,java util concurrent
-
并发容器
- ArrayList -> CopyOnWriteArrayList
- HashSet, TreeSet -> CopyOnWriteArraySet ConcurrentSkipList支持自然排序
- HasMap,TreeMap -> ConcurrentHashMap ,ConcurrentSkipListMap
-
安全共享策略
- 线程限制:一个被线程限制的对象,有线程独占,并且只能被占有他的线程修改
- 共享只读:一个贡献只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何一个线程都不能修改它
- 线程安对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问
- 被守护对象:被守护对象只能通过获得特定的锁来访问
JUC AQS
- AQS AbstractQueuedSynchronizer
- 使用node实现了FIFO队列
- 利用一个int类型表示状态
- 使用方法是集成
- 子类通过继承并通过他的方法管理状态 (acquier 和relese) 方法操纵状态
- 可以同时实现排它锁和共享锁模式(独占,共享)
- AQS 同步组件
-
CountDownLatch 线程计数
Semaphore 信号量 可以控制并发数量
``` final Semaphore semaphore = new Semaphore(20); ///线程 semaphore.acquire(); // 获取一个许可 semaphore.acquire(n); // 获取多个许可 //操作 semaphore.release(); // 释放一个许可 semaphore.release(3); // 释放多个许可 semaphore.tryAcquire() //尝试获取一个许可,如果获取不到就丢弃 semaphore.tryAcquire(1,TimeUnit.SECONDS) //尝试获取一个许可,等1s,如果获取不到就丢弃 ```
- CyclicBarrier 多个线程计数
- 可以重置计数器
- 达到条件后多个线程可以同步执行
- ReentrantLock(可重入锁)与synchronize区别
- 可重入性 ,基于计数器实现
- r JDK实现,s JVM实现
- jdk优化之前 r好,优化之后差不多基于CAS实现
- 功能区别 r需要手动开关,jvm 自动
- ReentrantLock 独有的功能
- 可以指定公平锁,默认非公平锁
- 提供了Condition ,可以分组唤醒
- 提供能改改中断等待做的机制,locklocInterruptibly();
- ReentrantReadWriteLock
- StampedLock
- Condition
- FutureTask
- callable 与runnable
- callable 可以有返回,使用Future接收, futer.get();
- Future 接口
- FutureTask
- callable 与runnable
- Fork/Join 窃取算法 ,双端队列
- BlockingQueue
- 常见实现
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- PriorityBlockingQueue 必须是compar接口
- SynchronousQueue 只能存放一个
- 生产消费场景
-
四套方法
- 常见实现
-
线程池
-
new Thread() 弊端
- 每次new Thrand 性能差,
- 缺乏统一管理,可以无限制新建线程,占用过度系统资源
- 确少更多功能,例如多执行,定期执行,线程中断
-
线程池好处
- 重用存在的线程,减少对象的创建消亡的开销
- 可以有效控制最大并发数,提高系统资源利用率,可以避免过多资源竞争,避免阻塞
- 提供定时,定期,单线程,并发数控制
-
ThreadPoolExecutor
- corePoolSize 核心线程数量
- maximuumPoolSize 线程最大线程数
- workQueue 阻塞队列
- keepAliveTime
- unit 时间单位
- threadFactory 线程工厂
- rejectHandler 拒绝 策略
- 丢弃
- 使用当前调用者线程执行
- 丢弃old
- 丢弃当前
-
线程池状态
-
基础方法
- execute() : 提交任务,交给线程池执行
- submi(): 提交任务,能够返回执行结果 execute() +Future
- shutdown(): 关闭线程池,等待任务执行完
- shutdownNow(): 关闭线程池不等待任务执行完成
-
监控方法
- getTaskCount();
- getCompletedTaskCount();
- getPoolSize()
- getActiveCount();
- Executor 框架接口
- Executors.newCachedThreadPool();
- Executors.newFixedThreadPool();
- Executors.newScheduledThreadPool();
- Executors.newSingleThradExecutor();
- 线程池配置
- cpu密集型任务,就需要进来压榨CPU,参考值可以设置为NCPU+1
- IO密集型任务,参考值可以设置为2*NCPU
J.U.C 学习总结