并发 : 同时拥有两个或多个线程,若是程序在单核处理器上运行,多个线程交替的换入或者换出内存,这些线程是同时存在的,每一个线程都处于执行过程当中的某个状态,若是运行在多核处理器上,此时,程序中的每一个线程都将分配到一个处理器核上,所以能够同时运行。
为何须要cpu cache: cpu 的频率太快,快到主存跟不上,这样在处理器始终周期内,CPU经常须要等待主存,浪费资源。因此cache得出现,是为了缓解CPU和内存之间速度的不匹配问题。
CPU cache 有什么意义:
1) 时间局部性: 若是某个数据被访问,那么在不久的未来它可能被再次的访问
2) 空间局部性: 若是某个数据被访问是,那么与他相邻的数据块可能会被很快的访问
CPU多级缓存 --缓存一致性 (MESI--缓存协议)
用于保证多个CPU cache 之间缓存共享数据的一致
CPU多级缓存-乱序执行优化
处理器为提升运算速度而作出违背代码原有顺序的优化
java内存模型规范(java Memory Model,JMM)
定义:java内存模型规范规定了一个线程如何以及什么时候能够看到由其余线程修改过的共享变量的值,以及在必须时如何同步的访问共享变量。
java内存中的堆: 能够在运行时动态的分配运行时内存,所以运行效率相对慢一些。
java中的栈: 优点: 存取速度要比java 中的堆的 存取速度要快,栈中的数据是能够共享的,可是栈中数据的生存期与大小是肯定的。栈中主要存放一些基本的数据变量。
一般状况下,当cpu 须要读取主存的时候,会先将主存中的数据读取到缓存中,甚至会将缓存中的某些数据读取到内部寄存器中,
在寄存器中执行某些操做。
java内存模型 - 同步的八种操做
lock(锁定) : 做用于主内存的变量,把一个变量标识为一条线程独占状态
unlock(解锁): 做用于主内存的变量,把一个处于锁定状态的变量释放出来,释放出来以后才能够被其余的线程使用。
read(读取): 做用于主内存的变量,把一个变量从主内存传输到线程的工做内存中,以便随后的load动做使用。
load(载入): 做用于工做内存的变量,他把read操做从主内存中获得的变量值放入到工做内存的变量副本中。
use(使用): 做用于工做内存的变量,把工做内存中的一个变量值传递给执行引擎
assign(赋值): 做用于工做内存的变量, 他把一个从执行引擎接受到的值赋值给工做内存的变量。
store(存储): 做用于工做内存的变量,把工做内存中的一个变量的值传输到主内存中,以便随后的write的操做
write(写入): 做用于主内存的变量,它把store操做从工做内存中一个变量的值传送到主内存的变量中 。
java内存模型--同步规则
若是要把一个变量从主内存中复制到工做内存,就须要按顺序的执行read和load操做,若是把变量从工做内存中同步回以内存中,就要按顺序的执行store 和 write 操做。但java 内存模型只要求上述操做必须按顺序执行,而没有保证必须是连续执行。
不容许read和load、store和write操做之一单独出现
不容许一个线程丢弃它的最近assign的操做,即变量在工做内存中改变了以后必须同步到主内存中。
不容许一个线程无缘由的(没有发生过任何assign操做)把数据从工做内存同步回主内存中
一个新的变量只能在主内存中诞生,不容许在工做内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操做以前,必须先执行过了assign和load操做
一个变量在同一时刻只容许一条线程对其执行lock操做,但lock操做能够被同一线程重复执行屡次,屡次执行lock以后,只有执行相同次数的unlock操做,变量才被解锁。lock和unlock必须是成对出现的。
若是对一个变量执行lock操做,将会清空工做内存中此变量的值,在执行引擎使用这个变量前须要从新执行load和assign操做初始化变量的值。
若是一个变量事先没有被lock操做锁定,则不容许对他执行unlock操做,也不容许unlock一个被其余线程锁定的变量
线程安全性 :
原子性: 提供了互斥访问,同一时刻只能有一个线程来对他进行操做
可见性: 一个线程对主内存的修改能够及时的被其余线程观察到
有序性: 一个线程观察其余线程中的指令执行顺序,因为指令重排序的存在,该观察结果通常杂乱无序
CAS线程安全底层原理: 使用了compareAndSwapInt方法,不断的使用当前的数值与底层主存中的值进行比较,只有当想听那个的时候,才进行返回。
actomicXXX类: 保证线程的安全性。 、
JDK1.8 : LongAdder 该类的做用和atomicLong 类的做用是同样的。
LongAdder类的做用: 主要是对热点数据的一个分离,进行单独处理。
在低并发的时候,性能和atomicLong 的性能是一致的,可是高并发的时候,经过分散提升了性能,可是在统计的时候,若是有并发更新也会致使统计偏差。
对于须要生成准确的数值,具备全局惟一性数值的时候,atomicLong才是惟一的选择 。
atomicReference,AtomicReferenceFieldUpdate:
atomicReference:
AtomicStampReference: CAS的ABA问题 假如 某个数据被修改成A而后修改成B再修改成A,该数据的版本就会发生变化
原子性锁:
synchronized: 依赖JVM (做用对象的做用范围内同一时刻只能有一个对象对其进行操做)
Lock:依赖特殊的CPU指令,代码实现,ReentrantLock
synchronized:
修饰代码块: 大括号括起来的代码,做用于调用的对象
修饰方法: 整个方法,做用于调用的对象
修饰静态方法: 整个静态方法,做用于全部的对象
修饰类: 括号括起来的部分,做用于全部的对象
synchronized: 在方法定义中synchronized并非方法的一部分,因此当synchronized修饰的方法被继承的时候,子类是没有synchronized功能的。
可见性: 致使共享变量在线程间不可见的缘由:
线程交叉执行
重排序结合线程交叉执行
共享变量更新后的值没有在工做内存与主存之间及时的更新
JMM关于synchronized的两条规定 :
线程解锁前,必须把共享变量的最新值刷新到内存中。
线程加锁时,将清空工做内存中共享变量的值,从而使用共享内存时须要从主内存中从新进行读取
volatile: 经过加入内存屏障和禁止重排序优化来实现
对volatile变量写操做时,会在写操做后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存中
对volatile变量读操做时,会在读操做前加入一条load屏障指令,从主内存中读取共享变量
volatile不具有原子性:
适用的场景: 一、 对变量的写错作不依赖于当前的值
二、 该变量没有包含在其余变量不变的实时中
很适合作状态标识变量
java内存模型中,容许编译器和处理器对指令进行重排序,可是重排序的过程不会影响到单线程的执行,却会影响到多线程的并发执行正确性
volatile、synchtroniezed、lock
程序次序规则:一个线程内,按照代码顺序,书写在前面的操做现行发生于书写在后面的操做
锁定规则: 一个unlock操做先行发生于后面对同一个锁的lock操做
volatile变量规则: 对一个变量的写操做先行发生于后面对这个变量的读操做
传递规则: 若是操做A先行发生于操做B,而操做B又先行发生于操做C,则能够得出操做A先行发生于操做C
线程启动规则: Thread对象的start() 方法先行发生于此线程的每个动做
线程中断操做: 对线程interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终止规则: 线程中全部的操做都先发生于线程的终止检测,咱们能够经过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则: 一个对象的初始化的完成会先行发生于他的finalized() 的方法的时开始
对象发布:
发布对象: 使一个对象可以被当前范围以外的代码所使用
对象逸出: 一种错误的发布。当一个对象尚未构造完成时,就使他被其余线程所见。 html
展现对象逸出代码示例:java
package MyStudyTest; /** * 不管是显示的引用仍是隐式的引用都会形成this的逸出,由于构造方法尚未初始化完成,其余的线程就看到了该成员变量的值 * 错误缘由: 在对象尚未完成构造函数初始化以前就对其发布 */ public class Example2 { private int thisCanBeEscape = 0; public Example2(){ new InnerClass(); } private class InnerClass{ public InnerClass(){ System.out.println("{}"+Example2.this.thisCanBeEscape); } }
若是一个对象是一个可变对象,就须要对其进行安全的发布,不然就会形成线程的不安全。数组
安全发布对象的四种方法:缓存
一、在静态初始化函数中初始化一个对象的引用安全
二、将对象的引用保存到volatile类型域或者AtomicReference对象中。多线程
三、将对象的引用保存到某个正确构造对象的final类型域中并发
四、将对象的引用保存到一个有锁保护的域中app
volatile关键字有两个使用场景: 框架
一、 能够作状态标识量 (由于该关键字会将工做内存空间中的数据刷新到主内存中)函数
二、能够进行双重检测(由于该关键字能够限制寄存器发生指令重排--针对单例懒汉模式的双重检测机制)
package MyStudyTest; /** * 懒汉模式: * 单例实例在第一次使用的时候建立 * 线程不安全的,只适用于在单线程的状况下使用 */ public class SingleTenExample { //私有构造函数,只有当构造函数式私有的时候,才不会被其余的引用随便的建立出来 private SingleTenExample(){}; //单例 private static volatile SingleTenExample instance = null; //静态工厂方法 /* public static SingleTenExample getInstance(){ if(instance == null){ return new SingleTenExample(); } }*/ //添加synchronized关键字,保证线程安全性 /* public static Synchronized SingleTenExample getInstance(){ if(instance == null){ instance = new SingleTenExample(); } }*/ //以上方法虽然实现了线程的安全性,可是在性能方面较差 //双重同步锁单例模式 //这种方式依然是线程不安全的 //内存寄存器依次完成的指令: // 一、memory = allocate() 分配对象的内存空间 // 二、ctorInstance() 初始化对象 // 三、instance = memory 设置instance指向刚分配的内存 // JVM和cpu优化,发生了指令重排 // 一、memory = allocate() 分配对象的内存空间 // 三、instance = memory 设置instance指向刚分配的内存 // 二、ctorInstance() 初始化对象 // /** * 双重检测机制不必定是线程安全的,缘由是因为有指令重排发生的可能, * 当有两个线程同时调用getinstance方法的时候,假如A线程恰好执行到instance = new SingleTenExample(),指令2和3发生了顺序交换, * 那么当线程B请求instance ==null的时候,就会默认已经建立了实例,返回instance,而实际上该线程尚未进行初始化,就会致使线程不安全 * * 注: 经过volatile关键字就能限制发生指令重排 */ public static SingleTenExample getInstance() { if (instance == null) { // 双重检测机制 // B synchronized (SingleTenExample.class) { // 同步锁 if (instance == null) { instance = new SingleTenExample(); // A - 3 } } } return instance; } }
注:静态域和静态代码款的执行顺序不同,产生的结果就会不同
使用枚举来实现单例模式,同时也是一种很安全的单例模式
package com.mmall.concurrency.example.singleton; import com.mmall.concurrency.annoations.Recommend; import com.mmall.concurrency.annoations.ThreadSafe; /** * 枚举模式:最安全 */ @ThreadSafe @Recommend public class SingletonExample7 { // 私有构造函数 private SingletonExample7() { } public static SingletonExample7 getInstance() { return Singleton.INSTANCE.getInstance(); } private enum Singleton { INSTANCE; private SingletonExample7 singleton; // JVM保证这个方法绝对只调用一次 Singleton() { singleton = new SingletonExample7(); } public SingletonExample7 getInstance() { return singleton; } } }
对于为何枚举是最安全的详见博客: https://www.cnblogs.com/wcgstudy/p/11408495.html
不可变对象:
不可变对象须要知足的条件:
一、对象建立之后,其状态就不能修改
二、对象全部的域都是final类型的
三、对象时正确建立的(对象建立期间,this引用没有逸出)
final: 能够用来修饰类、对象、方法
修饰的类不能被继承 ,final修饰的类中的全部方法都会被隐式的指定为final方法
修饰方法: 一、锁定方法不被继承类修改 二、效率(在JDK早期版本中将方法做为一个内嵌方法,来提高效率,可是当方法过于庞大时,效率不会被提高)
二、private修饰的方法会被隐式的转换为final修饰的方法
修饰变量: 一、基本数据类型变量 二、引用数据类型变量 (被初始化以后,不能指向另外的一个对象)
当final修饰map的时候,只是引用不容许指向另外的一个对象,可是里面的值是容许进行修改的 。
java中其余不可变对象的申明方法:
一、 Collections.unmodifiableXXX:Collection、list、set、Map
二、 Guava : ImmutableXXX: Collection、list、set、Map
被以上两个方法修饰的容器中的数据是不可变的
代码演示实例:
package com.mmall.concurrency.example.immutable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.mmall.concurrency.annoations.ThreadSafe; @ThreadSafe public class ImmutableExample3 { private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3); private final static ImmutableSet set = ImmutableSet.copyOf(list); private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4); private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder() .put(1, 2).put(3, 4).put(5, 6).build(); public static void main(String[] args) { System.out.println(map2.get(3)); } }
线程封闭: 就是将对象封装到一个线程中,这样即便这个对象是线程不安全的,也不会有线程不安全的问题存在
实现线程封闭的几种方式:
一、AD-HOC线程封闭: 程序控制实现,最糟糕,能够忽略
二、堆栈封闭: 使用局部变量,无并发问题
三、ThreadLocal 线程封闭: 特别好的线程封闭方法,ThreadLocal底层使用了一个Map实现了线程的封闭
线程不安全的类与写法:
代码演示: 关于stringbuilder
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.NotThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @NotThreadSafe public class StringExample1 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static StringBuilder stringBuilder = new StringBuilder(); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("size:{}", stringBuilder.length()); } private static void update() { stringBuilder.append("1"); } }
StringBuilder 和 StringBuffer 的区别:
SringBuffer 在底层实现的时候,使用了synchronized关键字,因此是线程安全的,可是在同一时刻只有一个线程可以调用该对象,就致使他的性能相对较差 。
simpleDataFormat 方法在多线程中的不安全性:
一、当将simpleDateFormat对象定义为全局变量,进行多线程调用的时候,就会出现异常抛出。
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.NotThreadSafe; import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @NotThreadSafe public class DateFormatExample1 { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update() { try { simpleDateFormat.parse("20180208"); } catch (Exception e) { log.error("parse exception", e); } } }
解决方法:对象堆栈封闭,将对象声明为局部变量:
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @ThreadSafe public class DateFormatExample2 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update() { try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20180208"); } catch (Exception e) { log.error("parse exception", e); } } }
在实际项目运行中最推荐使用的日期转换方法: jodaTime
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @ThreadSafe public class DateFormatExample3 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final int count = i; executorService.execute(() -> { try { semaphore.acquire(); update(count); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update(int i) { log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate()); } }
线程安全同步容器 :
ArrayList --》 Vector,stack
HashMap --> hashtable (里面的key和value不能为空 ),currenthashMap
Collections.SynchronizedXXXX (List,Set,Map)
vector不安全的使用方法:
package com.mmall.concurrency.example.syncContainer; import com.mmall.concurrency.annoations.NotThreadSafe; import java.util.Vector; @NotThreadSafe public class VectorExample2 { private static Vector<Integer> vector = new Vector<>(); public static void main(String[] args) { while (true) { for (int i = 0; i < 10; i++) { vector.add(i); } Thread thread1 = new Thread() { public void run() { for (int i = 0; i < vector.size(); i++) { vector.remove(i); } } }; Thread thread2 = new Thread() { public void run() { for (int i = 0; i < vector.size(); i++) { vector.get(i); } } }; thread1.start(); thread2.start(); } } }
使用静态工厂方法同步容器保证线程安全:
package com.mmall.concurrency.example.syncContainer; import com.google.common.collect.Lists; import com.mmall.concurrency.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.Collections; import java.util.List; import java.util.Vector; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @ThreadSafe public class CollectionsExample1 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; private static List<Integer> list = Collections.synchronizedList(Lists.newArrayList()); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final int count = i; executorService.execute(() -> { try { semaphore.acquire(); update(count); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("size:{}", list.size()); } private static void update(int i) { list.add(i); } }
注: 在容器进行遍历的时候不去作删除操做,不然会抛出异常
package com.mmall.concurrency.example.syncContainer; import java.util.Iterator; import java.util.Vector; public class VectorExample3 { // java.util.ConcurrentModificationException private static void test1(Vector<Integer> v1) { // foreach for(Integer i : v1) { if (i.equals(3)) { v1.remove(i); } } } // java.util.ConcurrentModificationException private static void test2(Vector<Integer> v1) { // iterator Iterator<Integer> iterator = v1.iterator(); while (iterator.hasNext()) { Integer i = iterator.next(); if (i.equals(3)) { v1.remove(i); } } } // success private static void test3(Vector<Integer> v1) { // for for (int i = 0; i < v1.size(); i++) { if (v1.get(i).equals(3)) { v1.remove(i); } } } public static void main(String[] args) { Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); vector.add(3); test1(vector); } }
线程安全之并发容器:
ArrayList --> copyonwriteArrayList
copyonWriteArrayList : 当将原来的数组添加到该数组的时候,会先将原来的数组copy一份到如今的数组,等全部的操做作完以后,再将新的数组的索引指向原来的数组,从而保证数据的一致性。
全部的操做都是在锁的保护下进行的。
缺点: 效率低下,耗费内存多,有可能会触发GC
二、不能进行实时性的操做,数据跟新慢
工做特色: 一、读写分离 二、数据的最终一致性 三、读操做不加锁,可是写操做枷锁
HashSet 、treeSet --》 copyonWriteArraySet 、ConcurrentSkipListSet
ConcurrentSkipListSet 在作批量操做的时候,并不能保证线程的安全性,须要手动的加上同步锁
hashMap、treeMap --》 concurrentHashMap 、concurrentskipListMap
currentHashMap 中是不容许出现空值的
AQS被称为JUC的核心,由一个双向链表实现sync queue以及一个不是必须的单向列表condition queue
使用Node实现FIFO队列,能够用于构建锁或者其余同步装置的基础框架
利用了一个int类型表示状态(表示获取锁的线程的数量 )
使用的方法是继承
子类须要经过继承并经过实现它的方法管理其状态(acquire和release)方法的操纵状态
能够同时实现排它锁和共享锁(独占和共享)
AQS同步组件:
CountDownLatch
semaphore
CyclicBarrier
ReenTrantLock
condition
FutrueTask