线程安全性:
当多个线程访问一个类的时候,这个类始终表示出正确的行为,那么这个类是线程安全的。
无状态的对象一定是线程安全的,例如大部分service、dao、Servlet都是无状态的。
线程安全体现:
1、原子性:
互斥访问,同一个时刻只有一个线程进行操作
2、可见性:
一个线程对变量的修改可以及时被其他线程看到
3、有序性:
一个线程观察其它线程中的指令执行顺序,由于指令重排序的操作该观察结果一般是无序的
原子性
1、CAS:
就是指compareAndSwap/compareAndSet,可以从随便一个Atomic类中看到CAS的实现,例如AtomicInteger的getAndIncrement()
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; }
var1:调用当前方法的对象
var5:通过native方法getIntVolatile得到底层的值,然后在while通过compareAndSwapInt判断var2和var5是否相等,因为var5可能被其它线程
所修改,只有相等的时候,才会进行叠加,这样就可以保证原子性,但还是存在ABA的问题。
CAS缺点:
并发很高的情况下,实现+1的while循环效率很低,所以出现了LongAdder,LongAdder底层实现和CAS不同,更适合高并发使用,可能会出现
计数有误差的情况,低并发和Atomic效率差不多
2、ABA问题:
当前变量i=3,线程A执行getAndIncrement(),如果主存的值应该是3,但是被线程B修改为4,然后修改为3,这时候可以执行+1操作的,
只不过变量被修改过
解决ABA问题:
可以通过AtomicStampReference去解决,只要变量修改过,变量的版本号加一
3、Atomic包:实现互斥
Atomic使用类:
AtomicInteger、AtomicLong、LongAdder(jdk1.8)、AtomicReference、AtomicBoolean、AtomicLongArray(对于数组的原子操作)等。
Atomic核心就是CAS
3.1).AtomicInteger
通过Semaphore(信号量)和CountDownLatch(闭锁),模拟5000个请求,每次并发处理200个
// 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; // public static AtomicInteger count = new AtomicInteger(0); public static int count = 0; 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(); add(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); } private static void add() { // count.incrementAndGet(); // count.getAndIncrement(); count++; }
com.it.TestUnit - count:4809
结果很明显,存在并发问题,如果把count换成AtomicInteger类型
com.it.TestUnit - count:5000
3.2).LongAddr:
public class LongAdder extends Striped64 implements Serializable public void add(long x) { Cell[] as; long b, v; int m; Cell a; if ((as = cells) != null || !casBase(b = base, b + x)) { boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x))) longAccumulate(x, null, uncontended); } } final boolean casBase(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, BASE, cmp, val); } @sun.misc.Contended static final class Cell { volatile long value; Cell(long x) { value = x; } final boolean cas(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long valueOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> ak = Cell.class; valueOffset = UNSAFE.objectFieldOffset (ak.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } } }
extends了Striped64,Striped64也是jdk1.8出现的,为了原子类服务,有个cell数组,Cell内部有一个非常重要的value变量,并且提供了一个
CAS更新其值的方法。
当竞争不激烈的时候,直接通过CAS更新值成功直接返回,否则就会把atomicLong中对一个value的更新,hash到对应的数组cell节点,然后
把value进行汇总,就能提高效率
当计数的时候,将base和各个cell元素里面的值进行叠加,从而得到计算总数的目的。在计数的同时如果修改cell元素,有可能导致计数的结
果不准确。
3.3).AtomicBoolean:使用场景就是让某一段代码只是执行一次
private static AtomicBoolean isHappened = new AtomicBoolean(false); if (isHappened.compareAndSet(false, true)) { log.info("execute"); }
synchronized:每个java对象都可以作为锁
synchronized关键字是不能继承的,父类是synchronized方法,子类必须显示写出来,否则不是同步方法
1、作用范围:
1).修饰非静态方法:synchronized作用于单个对象的,不同的对象之间是不影响的,应该是不同对象交叉执行的
2).修饰修饰静态方法:作用的所有的对象,同一时间只有一个对象获得对象监视器,不同对象调用,应该是等一个对象执行完成,另一个对
象才会执行方法
3).修饰同步方法块:锁是Synchonized括号里配置的对象。
2、实现原理:基于Monitor实现同步的
synchronized获取对象锁保证在执行共享数据的线程是互斥的,可以使用wait、notify、notifyAll进行线程协同工作、Class和Object都关联了
一个Monitor。
3、具体实现:
1、提供了两个高级的字节码指令monitorenter和monitorexit。这两个字节码实现了同步代码块
2、提供了ACC_SYNCHRONIZED标记符隐式实现了同步方法
public class TestUnit{ public synchronized void test1() { System.out.println("this is test1 method"); } public void test2() { synchronized (this) { System.out.println("this is test2 method"); } } }
通过javac编译出class文件,后javap反汇编出字节码
4、锁存放的位置:
锁标记存放在java对象头的Mark Word中
Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
2)synchronized在发生异常时,会自动释放线程占有的锁(独占锁、也是一种悲观锁),因此不会导致死锁现象发生
而Lock在发生异常时,如果没有主动通过unLock()去释放锁(乐观锁),则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
Atomic:竞争激烈也能保持常态,比Lock性能好,但是每次只能同步一个值
可见性
1、导致共享变量在线程间不可见的原因:
1、线程交叉执行
2、重排序结合线程交叉执行
3、共享变量的值没有在工作内存和主存之间及时更新
2、JMM对于synchronized规定:
线程解锁前,必须把共享变量的最新值刷新到主存
线程加锁时,要把工作内存中的共享变量值清空,从而使用时从主存中读取最新变量的值
PS:加锁和解锁,是同一把锁
3、volatile如何实现可见性:
不具有原子性,无法保证线程安全,只能修饰变量,多线程下不会发生阻塞
1、通过加入内存屏障和禁止重排序优化(实现有序性)来实现
2、对volatile变量进行读写操作,都会通过store、load来强制从主存中读取最新的值,或将数据强制刷新到主存中。
4、使用场景:
1、不适合计数场景
2、适合作为状态标示量,多线程下作为flag
3、doubleCheck
4、Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。
5、除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同。
有序性
volatile synchronized lock都能保证有序性
1、只要满足happens-before原则,就能保证先天的有序性,否则就可能发生指令重排序
2、synchronized和volatile保证有序性实现方式的区别:
1).volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。
2).synchronized好像是万能的,三个特性都可以保证,但是比较影响性能,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。