线程安全性(1)
保证线程安全性的三点:
一、原子性:提供互斥访问,在同一时刻只能有一个线程来对它进行操作。
二、可见性:一个线程对主内存的修改可以让其它线程及时的观察到。
三、有序性:由于cpu和jvm对指令的重排序,会使得其他线程观察到的该线程操作是杂乱无序的。
java中提供了Atomic包的AtomicXXX类、Lock类以及synchronized关键字来实现原子性
1.Atomic类的使用
1 public class AtomicExample1 { 2 public static int clientTotal=5000; //请求总数 3 4 public static int threadTotal=200; //同时请求的线程数 5 6 public static AtomicInteger count=new AtomicInteger(0); //使用AtomicInteger类来进行并发的计数 7 8 public static void main(String[] args)throws Exception { 9 ExecutorService executorService=Executors.newCachedThreadPool(); 10 final Semaphore semaphore=new Semaphore(threadTotal); 11 final CountDownLatch countDownLatch=new CountDownLatch(clientTotal); 12 for(int i=0;i<clientTotal;i++) { 13 executorService.execute(()->{ 14 try { 15 semaphore.acquire(); 16 add(); 17 semaphore.release(); 18 } catch (Exception e) { 19 log.error("Exception", e); 20 } 21 countDownLatch.countDown(); 22 }); 23 } 24 countDownLatch.await(); 25 executorService.shutdown(); 26 log.info("count:{}",count.get()); 27 } 28 29 private static void add() { 30 count.incrementAndGet(); //增加计数值+1的方法 31 } 32 }
结果等于总请求数5000
原理:其方法中使用的CAS操作 (compareAndSwap)
1 public final int getAndAddInt(Object o, long offset, int delta) { 2 int v; 3 do { 4 //*保证缓存一致性同时从给定偏移量处的对象o 5 v = getIntVolatile(o, offset); 6 } while (!compareAndSwapInt(o, offset, v, v + delta)); 7 //*不断地重新获取内存中的值,直至成功CAS 8 return v; 9 } 10 11 public final int getAndAdd(int delta) { 12 return unsafe.getAndAddInt(this, valueOffset, delta); 13 }
CAS操作将操作对象的地址(o)和它的值(offset)进行本地方法的运算,如果当前对象值在判断时依然与之前的到的本地结果(v)一致则说明没有其他线程操作过,对值进行加操作,若不一致则不断进行循环直到符合本地结果。
CAS可以有效避免脏读,但是在竞争激烈的情况可能会出现循环时间过长导致性能下降。
ABA问题:CAS操作时出现的,某一线程将值由A修改为B之后又修改到A值,虽然可以通过CAS操作但是它的值是有过变化的,与我们的设计思想不符合。
解决方法版本号机制:若线程修改过该对象的值,则该对象的版本号进行递增。
Atomic包中的AtomicStampReference类使用了该机制
2.Lock类依赖于特殊的CPU指令,其实现类有ReentrantLock。
3.synchronized关键字依赖于JVM
三者之间的比较
1、synchronized:不可中断的锁,可读性好,适合竞争不激烈的场景
2、Lock类,可中断锁,在竞争激烈的情况下依然保持良好
3、Atomic包,无锁算法,在激烈时性能依然良好,比Lock类更优,只能同步一个值
对java高并发学习的记录=v=~

浙公网安备 33010602011771号