countdownlatch 在一定条件下阻塞线程,条件结束线程继续执行,
semaphore 信号量,阻塞线程,可以控制同一时间执行线程的数量,
原子类能做到线程安全的原因,incrementAndGet,
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));//将当前工作线程传入的值与主内存的值比较,如果相同 就返回var5与var4之和 return var5; }
AtomicLong与LongAdder的比较
从上面的AtomicInt的实现可以看的出来,Atomic是通过死循环轮训内做修改尝试,当竞争不激烈的时候,修改成功的概率比较高,当竞争激烈的时候 修改失败的概率很高,修改失败后又继续进行修改尝试,因此高并发条件下 性能会受到一定的影响。
LongAdder比Atomic效率高的原因:https://blog.csdn.net/little_newBee/article/details/80352445
其缺点是当有并发更新的时候。其统计结果会有误差,因此高并发计算的需求下使用LongAdder,低并发下使用AtomicLong,当需要准确的数据的时候,AtomicLong才是应该的选择。
AtomicReference
//如果是expect 那么更新为 update public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
使用AtomicIntegerFieldUpdater来 原子性的修改类的某个字段
public class ATomICExample1 { //指定为ATomICExample1的count字段 private static AtomicIntegerFieldUpdater<ATomICExample1> updater = AtomicIntegerFieldUpdater.newUpdater(ATomICExample1.class, "count"); public volatile int count = 100;//这里必须使用volatile 来修饰 public static void main(String[] args) { ATomICExample1 example5 = new ATomICExample1(); if (updater.compareAndSet(example5, 100, 120)) { System.out.println(example5.getCount());//输出120 } if (updater.compareAndSet(example5, 100, 120)) { System.out.println(example5.getCount()); } else { System.out.println(example5.getCount());//输出120 } } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
使用AtomicBoolean ,在N个线程内 某段代码只执行一次
public class ATomICExample1 { private static AtomicBoolean isHappened = new AtomicBoolean(false); // 请求总数 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(); test(); semaphore.release(); } catch (Exception e) { System.out.println(e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("isHappened:"+ isHappened.get()); } private static void test() { if (isHappened.compareAndSet(false, true)) { System.out.println("execute"); } } }
synchronized 锁
public class ATomICExample1 { // 修饰一个代码块 作用的对象是this public void test1(int j) { synchronized (this) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+""+ i); } } } // 修饰一个方法 作用的对象是调用方法的对象 public synchronized void test2(int j) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+""+ i); } } public static void main(String[] args) { ATomICExample1 example1 = new ATomICExample1(); ATomICExample1 example2 = new ATomICExample1(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { example1.test2(1); }); executorService.execute(() -> { example2.test2(2); }); } }
public class ATomICExample1 { // 修饰一个类 public static void test1(int j) { synchronized (ATomICExample1.class) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+"--"+ i); } } } // 修饰一个静态方法 public static synchronized void test2(int j) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+"--"+ i); } } public static void main(String[] args) { ATomICExample1 example1 = new ATomICExample1(); ATomICExample1 example2 = new ATomICExample1(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { example1.test1(1); }); executorService.execute(() -> { example2.test1(2); }); } }
可见性 :一个线程对主内存的修改可用被其他线程观察到,
synchronized 实现可见性, JMM对其有以下两条规定,
1 线程解锁前 必须把共享变量刷新到主内存中,
2 线程加锁前 将清空本地共享变量的值,重新去主内存中获取共享变量的值
volatile 实现可见性,
1 对 volatile 写操作后,会在写操作后加入store屏障,将本地线程的共享变量的值刷新到主内存中
2 对 volatile 写操作前, 会在读操作前加入load屏障,从主内存中加载读取共享变量的值。
因此,任何时候,线程获取到 volatile 修饰的变量都是最新的值, 但是volatile修饰的共享变量 不是线程安全的,因为count++,是分三步的,1 获取变量,2 递增 3刷新数据到主内存。volatile 适合做B线程等待A线程完成某件事的场景,或者双重检测
对象的安全发布
public class UnsafePublish { private String[] states = {"a", "b", "c"}; public String[] getStates() { return states; } } public class Test { public static void main(String[] args) { UnsafePublish unsafePublish = new UnsafePublish(); System.out.println(Arrays.toString(unsafePublish.getStates())); unsafePublish.getStates()[0] = "d"; System.out.println(Arrays.toString(unsafePublish.getStates())); } }
以上是非安全的发布对象,可以看出来,对于一个类的私有属性,这种方式是可以修改private域的,假设一个线程修改了这个值,那么其他线程拿到的值就有可能是不正确的值。
1 饿汉单例模式或者线程安全的懒汉模式(双锁检验)
public class SingletonExample2 { // 私有构造函数 private SingletonExample2() { } // 单例对象 private static SingletonExample2 instance = new SingletonExample2(); // 静态的工厂方法 public static SingletonExample2 getInstance() { return instance; } }
public class SingletonExample4 { // 私有构造函数 private SingletonExample4() { } // 单例对象 private static SingletonExample4 instance = null; // 静态的工厂方法 public static SingletonExample4 getInstance() { if (instance == null) { // 双重检测机制 // B synchronized (SingletonExample4.class) { // 同步锁 if (instance == null) { //这里的新建对象分三步 ,1 分配对象的内存空间,2 初始化对象 ,3设置instance指向刚分配的内存 //第二步和第三步符合cpu指令重排的条件,所以有可能真正的执行步骤是 1->3->2,这样当其他线程来执行if (instance == null)时得到的结果将是false,存在线程安全的问题 instance = new SingletonExample4(); } } } return instance; } }
public class SingletonExample4 { // 私有构造函数 private SingletonExample4() { } // 单例对象 加上volatile+双锁检验 限制指令重排可以达到线程安全 private static volatile SingletonExample4 instance = null; // 静态的工厂方法 public static SingletonExample4 getInstance() { if (instance == null) { // 双重检测机制 // B synchronized (SingletonExample4.class) { // 同步锁 if (instance == null) { instance = new SingletonExample4(); } } } return instance; } }
恶汉静态域和静态块实现
public class SingletonExample6 { // 私有构造函数 private SingletonExample6() { } // 单例对象 这里的静态域如果和下面的静态块代码顺序调换将会导致实例化失败 空指针异常,静态代码块和静态域是按照声明顺序执行的,这与普通方法是不一样的 private static SingletonExample6 instance = null; static { instance = new SingletonExample6(); } // 静态的工厂方法 public static SingletonExample6 getInstance() { return instance; } public static void main(String[] args) { System.out.println(getInstance().hashCode()); System.out.println(getInstance().hashCode()); } }
最安全的单例模式--枚举类型
不可变对象,使用final修饰变量,当变量为基本类型时,变量的值不可变,当为对象类型时 变量指向的引用不可变,但是变量的内容是可以变化的,比如
public class ImmutableExample1 { private final static Integer a = 1; private final static String b = "2"; private final static Map<Integer, Integer> map = Maps.newHashMap(); static { map.put(1, 2); map.put(3, 4); map.put(5, 6); } public static void main(String[] args) { // a = 2; // b = "3"; // map = Maps.newHashMap(); map.put(1, 3); } private void test(final Map<Integer, Integer> map) { // map = Maps.newHashMap(); } }
如果需要将其设置为内容也不可变,可以使用Collections.unmodifiable方法,如以下操作将会报错,从而保证了对象的不可变
public class ImmutableExample2 { private static Map<Integer, Integer> map = Maps.newHashMap(); static { map.put(1, 2); map.put(3, 4); map.put(5, 6); map = Collections.unmodifiableMap(map); } public static void main(String[] args) { map.put(1, 3); } }
StringBuild 是线程不安全的,但是效率高,但是使用其处理局部变量是没有线程安全的问题的,因为局部是堆栈封闭的,不存在线程安全问题,因此可以发现对于一些线程不安全的方法 如simpleDateFormat.parse方法等,要使其线程安全,可以使用局部变量来做。
StringBuffer 是线程安全的,线程安全的原因是其涉及的方法均使用synchronized同步, 因此也带来了性能上的问题
容器
ArrayList Vector(使用synchronized保证安全) Stack(继承于Vector,也是使用synchronized保证安全),
HashMap HashTable(也是使用synchronized保证安全)
使用Collections.synchronizedXXX(HashMap,ArrayList,HashSet)生成同步容器类。
CopyOnWriteArrayList:CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁
Synchronized 与Reentranlock比较
Synchronized 在被优化前效率很低,但是优化后效率也还可以,
Synchronized 语法简单,并且自动加锁与自动解锁,很便利。
Reentranlock 独有的功能
Reentranlock 可以指定公平锁和非公平锁
提供了Condition,可以分组的唤醒需要唤醒的线程
可以中断等待锁的的线程机制,lock.lockInterruptibly();
Synchronized 能做的事Reentranlock基本都能做到,但是前者语法简单 使用容易
volatile 的定义及原理
volatile可以被理解为轻量级的synchronized,它可以保证当A线程修改一个共享变量时,B线程能读到这个修改值。
每个处理器通过嗅探总线上传播来的数据来检查自己缓存的值是不是过期,如果过期了,那么就会将缓存的数据设置为无效状态,并且刷新缓存值。
synchronized 的原理与应用
synchronized 被称为重量级锁,但是java6之后进行各种优化之后,有时候它也不是很重,
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。锁的三种形式
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是synchronized括号里配置的对象
锁的级别
由低到高一次是 无锁<偏向锁<轻量锁<重量锁
偏向锁:大多数的时候,锁不存在多线程的竞争,而是总是由一个线程获取到,偏向锁就是为了让线程获取锁的代价更低而引入的,当一个线程访问同步块并获取锁的时候,会在对象头和栈帧中记录锁的偏向线程id,以后该线程在进入或者退出同步块是不需要CAS操作来加锁和解锁,仅仅是测试一下对象头是否存储偏向线程id,如果测试成功,那么即代表已获得锁。只有竞争出现的时候才释放偏向锁(其他线程访问同步块时)。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false。
java内存模型(JMM)
线程stack主要存放一下基本类型的变量,比如int ,float,double和对象句柄,这里的Object1 包含的方法中的局部变量,也是存储在栈上的,
静态成员变量会跟随对象存储在堆上,当线程访问对象方法时包含了成员变量,这些线程都拥有了这个对象的成员变量的私有拷贝。
线程读取状态与法则
lock(锁定) :作用于主内存变量,把变量标志为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态变量解放出来,这样才能被其他线程锁定。
read(读取): 作用于主内存变量,把变量值从主内存传输到工作内存中,以便后面的load操作。
load(载入):作用于线程的工作内存,它把read操作从主内存读取的变量值放入到工作内存的变量副本中。
use(使用):作用线程的工作内存,把工作内存的变量值赋值给执行引擎。
assign(赋值):作用于线程的工作内存,把执行引擎收到的值赋值工作内存的变量。
store(存储):作用于线程的工作内存,把工作内存的变量值传递到主内存中,以便后续的write操作。
write(写入):作用于主内存变量,它把store操作中的线程工作内存的变量值传递到主内存中。
1 如果要把变量从主内存复制到工作内存中,就需要按顺序的执行read和load操作,如果把变量从工作内存同步到主内存中,就需要按顺序的执行store和write操作,但是java内存模型要求上述操作的顺序执行,而没保证必须是连续执行。
2 不允许read|laod , store|write操作之一单独出现。
3 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须同步到主内存中,
4 不允许一个线程无原因的(未发生assign操作)就把数据从工作内存同步到主内存中
5 一个新的变量只能在主内存中诞生。不允许在工作内存中直接使用一个未被初始化(load和assign)的变量。即对一个变量实施use和store操作之前,必须先执行assign和load操作。
6 一个变量在同一个时刻只允许一条线程对其进行lock操作,但是lock操作可以被同一个线程执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁,lock和unlock必须成对出现.
7 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load和assign操作初始化变量的值。
8 如果一个变量没有事先被lock锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
9 对一个变量执行unlock操作之前,必须先把这个变量同步到主内存中(执行store和write操作)