1、CAS
compare and swap,比较并交换,实现并发算法时常用到的一种技术,它包含三个操作数--内存位置、预期原值以及更新值。执行CAS操作的时候,将内存位置的值和预期原值比较:如果匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何处理,多个线程同时执行CAS操作只有一个会成功。
CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B,只有V和A的值型同时,才会将V修改成B,否则什么都不做处理或者重来,重来的这种行为是叫做----自旋。
public class CasDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
boolean b = atomicInteger.compareAndSet(5, 2021);//如果期望值达到了,就更新,否则不更新
System.out.println(atomicInteger.get()+"\t 修改成功与否:"+b);
boolean b1 = atomicInteger.compareAndSet(5, 1024);
System.out.println(atomicInteger.get()+"\t 修改成功与否:"+b1);
}
}
CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-----更新的原子性。它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapxxX)底层实现即为CPU指令cmpxchg。
执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。
CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe 类中的各个方法。调用UnSafe 类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
原子引用
其实上面就有这个,只不过是interger类型的,现在换一个
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> userAtomicReference = new AtomicReference<>();
User z3 = new User("z3", 22);
User li4 = new User("li4",24);
userAtomicReference.set(z3);
boolean b = userAtomicReference.compareAndSet(z3, li4);
System.out.println("\t"+userAtomicReference.get().toString()+"修改成功"+b);
boolean b1 = userAtomicReference.compareAndSet(z3, li4);
System.out.println("\t"+userAtomicReference.get().toString()+"修改成功:"+b1);
}
}
#自己加Lombok注解
class User{
String userName;
int age;
}
1.1、自旋锁
CAS 是实现自旋锁的基础,CAS 利用 CPU指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻寒,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in");
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void unlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t unlock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unlock();
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unlock();
}, "t2").start();
}
}
2、原子操作类
2.1、基本类型原子类
代表AtomicInteger、AtomicBoolean、AtomicLong。常用API如下
-
public final int get() 获取当前值
-
public final int getAndSet(int newValue) 获取当前值,并设置新的值
-
public final int getAndIncrement() 获取当前值并且自增
-
public final int getAndDecrement() 获取当前值并且自减
-
public final int getAndAdd(int delta) 获取当前值,并且加上预期值
-
public final boolean compareAndSet(int expect, int update) 如果输入值和预期值相等,以原子的方式将该值替换为输入值
public class Jiben {
public static final int SIZE =50 ;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
number number = new number();
for (int i = 0; i < SIZE; i++) {
new Thread(() -> {
try {
for (int j = 0; j < 1000; j++) {
number.add();
}
} finally {
countDownLatch.countDown();//每个线程执行完之后,countDownLatch减一
}
}, String.valueOf(i)).start();
}
countDownLatch.await();//等待上面的线程全部执行完
System.out.println(Thread.currentThread().getName() + "\t" + number.atomicInteger.get());
}
}
class number{
AtomicInteger atomicInteger = new AtomicInteger();
public void add() {
atomicInteger.getAndIncrement();
}
}
2.2、数组类型原子类
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
public class AtomicIntegerArrayDemo
{
public static void main(String[] args)
{
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
for (int i = 0; i <atomicIntegerArray.length(); i++) {
System.out.println(atomicIntegerArray.get(i));
}
System.out.println();
int tmpInt = 0;
tmpInt = atomicIntegerArray.getAndSet(0,1122);
System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
tmpInt = atomicIntegerArray.getAndIncrement(0);
System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
}
}
2.3、引用类型原子类
AtomicReference、AtomicStampdReference、AtomicMarkableReference
public class AtomicMarkableReferenceDemo
{
static AtomicMarkableReference markableReference = new AtomicMarkableReference(100,false);
public static void main(String[] args)
{
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName()+"\t"+"默认标识:"+marked);
//暂停1秒钟线程,等待后面的T2线程和我拿到一样的模式flag标识,都是false
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
markableReference.compareAndSet(100,1000,marked,!marked);
},"t1").start();
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName()+"\t"+"默认标识:"+marked);
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
System.out.println(Thread.currentThread().getName()+"\t"+"t2线程CASresult: "+b);
System.out.println(Thread.currentThread().getName()+"\t"+markableReference.isMarked());
System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference());
},"t2").start();
}
}
2.4、对象的属性修改原子类
AtomicIntegerFiledUpdater原子更新对象中int类型字段的值,AtomicLongFieldUpdater原子更新对象中Long类型字段的值,AtomicRefenceFieldUpdater原子更新引用类型字段的值,局部麻醉和全麻醉的区别
使用要求:更新的对象属性必须要使用public volatile修饰,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
class BankAccount//资源类
{
String bankName = "CCB";
//更新的对象属性必须使用 public volatile 修饰符。
public volatile int money = 0;//钱数
public void add()
{
money++;
}
//因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
// 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");
//不加synchronized,保证高性能原子性,局部微创小手术
public void transMoney(BankAccount bankAccount)
{
fieldUpdater.getAndIncrement(bankAccount);
}
}
/**
* 以一种线程安全的方式操作非线程安全对象的某些字段。
*
* 需求:
* 10个线程,
* 每个线程转账1000,
* 不使用synchronized,尝试使用AtomicIntegerFieldUpdater来实现。
*/
public class AtomicIntegerFieldUpdaterDemo
{
public static void main(String[] args) throws InterruptedException
{
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <=10; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=1000; j++) {
//bankAccount.add();
bankAccount.transMoney(bankAccount);
}
} finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t"+"result: "+bankAccount.money);
}
}
2.5、原子操作类增强类
DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder
-
public void add(long x) 将当前的value加X
-
public void increment() 将当前的value加1
-
public void decrement() 将当前的value减1
-
public long sum() 返回当前值,高 不时精确值 不高 时精确值
-
public void reset() 将value重置为0 没有并发更新的情况下使用
-
public long sumThenReset() 获取当前的value 将value重置为0
LongAdder只能用来计算加法,且从零开始计算,LongAccumulator提供了自定义的函数操作
class ClickNumber //资源类
{
int number = 0;
public synchronized void clickBySynchronized()
{
number++;
}
AtomicLong atomicLong = new AtomicLong(0);
public void clickByAtomicLong()
{
atomicLong.getAndIncrement();
}
LongAdder longAdder = new LongAdder();
public void clickByLongAdder()
{
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
public void clickByLongAccumulator()
{
longAccumulator.accumulate(1);
}
}
/**
* 需求: 50个线程,每个线程100W次,总点赞数出来
*/
public class AccumulatorCompareDemo
{
public static final int _1W = 10000;
public static final int threadNumber = 50;
public static void main(String[] args) throws InterruptedException
{
ClickNumber clickNumber = new ClickNumber();
long startTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickBySynchronized();
}
} finally {
countDownLatch1.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySynchronized: "+clickNumber.number);
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickByAtomicLong();
}
} finally {
countDownLatch2.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong: "+clickNumber.atomicLong.get());
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickByLongAdder();
}
} finally {
countDownLatch3.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder: "+clickNumber.longAdder.sum());
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickByLongAccumulator();
}
} finally {
countDownLatch4.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator: "+clickNumber.longAccumulator.get());
}
}
3、ThreadLocal
ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或 set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。
-
protected T initialValue() 返回此线程局部变量的当前线程的“初始值”
-
public T get() 返回此线程局部变量的当前线程副本中的值。如果变量没有当前线程的值,则首先将其初始化为调用方法返回 initialValue 的值
-
public void set(T value) 此线程局部变量的当前线程副本设置为指定值。大多数子类不需要重写此方法,只需依靠该 initialValue 方法来设置线程局部变量的值。
-
public void remove() 删除此线程局部变量的当前线程值。
-
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) 创建线程局部变量
class House //资源类
{
int saleCount = 0;
public synchronized void saleHouse()
{
++saleCount;
}
/*ThreadLocal<Integer> saleVolume = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue()
{
return 0;
}
};*/
ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
public void saleVolumeByThreadLocal()
{
saleVolume.set(1+saleVolume.get());
}
}
/**
* @auther zzyy
* @create 2021-12-31 15:46
*
* 需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数。
*
* 需求2: 5个销售卖完随机数房子,各自独立销售额度,自己业绩按提成走,分灶吃饭,各个销售自己动手,丰衣足食
*
*
*/
public class ThreadLocalDemo
{
public static void main(String[] args) throws InterruptedException
{
House house = new House();
for (int i = 1; i <=5; i++) {
new Thread(() -> {
int size = new Random().nextInt(5)+1;
try {
for (int j = 1; j <=size; j++) {
house.saleHouse();
house.saleVolumeByThreadLocal();
}
System.out.println(Thread.currentThread().getName()+"\t"+"号销售卖出:"+house.saleVolume.get());
} finally {
house.saleVolume.remove();
}
},String.valueOf(i)).start();
};
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套: "+house.saleCount);
}
}
这种是new 的方式,最后不删除线程中的数据也可以,但是如果是使用的是线程池的话不移除的话会出现问题---共用数据的问题。
必须回收自定义的ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。
class MyData
{
ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
public void add()
{
threadLocalField.set(1 + threadLocalField.get());
}
}
public class ThreadLocalDemo2
{
public static void main(String[] args) throws InterruptedException
{
MyData myData = new MyData();
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try
{
for (int i = 0; i < 10; i++) {
threadPool.submit(() -> {
try {
Integer beforeInt = myData.threadLocalField.get();
myData.add();
Integer afterInt = myData.threadLocalField.get();
System.out.println(Thread.currentThread().getName()+"\t"+"beforeInt:"+beforeInt+"\t afterInt: "+afterInt);
} finally {
myData.threadLocalField.remove();
}
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
3.1、内存泄露
什么是内存泄漏,不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
3.2、强引用
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。 强引用是我们最常见的普通对象引周,只要还有强引用指向一个对象,就能表明对象还 “活着”,垃圾收集器不会碰这种对象。在Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null, 一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。
3.3、软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。对于只有软引用的对象来说,
-
当系统内存充足时它不会被回收,
-
当系统内存不足时它会被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
3.4、弱引用
弱引用需要使用java.long.ref.WeakReference类来实现,它比软引用的生命周期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存是否够用,都会回收对象占用的内存。
3.5、虚引用
虚引用必须和引用队列 (ReferenceQueue)联合使用
虚引用需要java.lang ref.PhantorReference类来实现,顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列 (ReferenceQueue)联合使用。
PhantomReference的get方法总是返回null
虛引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize以后,做某些事情的通知机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。
处理监控通知使用
换句话说,设置虚引用关联对象的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理,用来实现比finalize机制更灵活的回收操作
3.6、总结
ThreeLocal并不解决线程之间数据共项的数据问题,适用于变量在线程间隔离且在方法之间共享的场景,通过隐式的在不同的线程内创建独立实例副本避免了实例线程安全问题,每一个线程持有一个只属于自己的专属Map并维护ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,所以不存在线程的安全以及锁的问题。
ThreadLocalMap的Entity对ThreadLocal的引用为弱引用,避免啦ThreadLocal对象无法被回收的问题都会通过ExpungeStaleEntity、cleanSomeSlots、replaceStaleEntitry这三个方法回收键为null的Entitry对象的值(即为具体实例)以及Entry对象本身从而防止内存泄漏,属于安全加固的方法。