锁的类型
可重入锁和不可重入锁
可重入锁:一个线程可以多次抢占同一个锁,可重复可递归调用。
用ReentrantLock进行锁的可重入测试,在同一个线程中定义get()和set()两个方法,在这两个方法中都使用lock.lock();方法获得锁,代码可以运行。
不可重入锁:不可递归调用,递归调用就发生死锁。自旋锁一般情况下是不可重入的,但可在自旋前加入判断改为可重入的。
如果是不可重入锁,调用下列程序输出set方法后发生死锁,线程阻塞。
public class ReentrantLockTest1 implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
set();
}
private void set() {
try {
lock.lock();
System.out.println("set 方法");
//在同一个线程中先在set方法中获得锁 , 调用get方法 又在get方法中获得同一个锁
get();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
private void get() {
try {
lock.lock();
System.out.println("get 方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockTest1 lockTest1 = new ReentrantLockTest1();
new Thread(lockTest1).start();
}
}
公平锁和非公平锁
公平锁:先到先得,获得锁的顺序遵循先进先出FIFO原则,先对锁进行获取的请求,一定先满足 。
非公平锁:不按顺序执行,原因:在线程切换A线程到B线程的时候要进行上下文切换需要耗费一段时间,在这个时间间隔上锁是空闲的如果此时有另一个线程C在cpu的另一个核心上执行,就不需要进行上下文切换,这时C就可以获得锁,这样利用了锁的空档期,提高了cpu利用效率,但是可能会导致“线程饥饿 ”即某个线程一直没有获得到锁。
synchronized是非公平锁,原因是synchronized是重量级锁, 它的线程间的调度和状态变更由操作系统负责,而非公平锁的吞吐量大于公平锁,是主流操作系统线程调度的基本选择。
自旋锁
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
悲观锁和乐观锁
悲观锁:悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观地认为,不加锁的并发操作一定会出问题。(synchronized对代码段加锁)
乐观锁:读数据时,不担心数据被修改,每次读数据的时候不会加锁,只是在修改数据时,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作,如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新。(CAS)
共享锁和独占锁
共享锁:指可以同时被多个线程读取,但只能被一个线程修改。比如 Java 中的 ReentrantReadWriteLock(读写锁)就是共享锁的实现方式,它允许一个线程进行写操作,允许多个线程读操作。
独占锁:指任何时候都只有一个线程能执行资源操作
可中断锁和不可中断锁
中断锁指的是锁在执行时可被中断,也就是在执行时可以接收 interrupt 的通知,从而中断锁执行。
Lock 显示锁是可中断锁,而 synchronized 则为不可中断锁。
中断锁的核心实现代码是 lock.lockInterruptibly() 方法,它和 lock.lock() 方法作用类似,只不过使用 lockInterruptibly 方法可以优先接收中断的请求。
CAS
原理:
(比较再交换)它是一种有名的无锁算法。CAS算法是乐观锁的一种实现。CAS有3个操作数,内存所存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B并返回true,否则返回false。如果预期值A和内存值V不相同时进入死循环(自旋)不断进行CAS操作。
存在的问题:
ABA问题:
问题:读取变量值为 A; 其它线程将变量值更改为 B; 其它线程又将变量值更改为 A; 这时去更新,发现此时变量值与我之前读到的值是相同的,就以为没有被其它线程更新过,案例参考文章Unsafe类中CAS相关的代码案例。
解决:加入一个时间戳或者递增变量等实现的版本号,来标识这个变量是否之前被更新过;
CPU开销大:
问题:CAS多与自旋结合。如果自旋CAS长时间不成功,就会占用大量的CPU资源。
解决:破坏掉for死循环,当超过一定时间或者一定次数时,return退出。JDK8新增的LongAddr,和ConcurrentHashMap类似的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来。
如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用:第一,它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;第二,它可以避免在循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空,从而提高CPU的实行效率。
只能保证一个共享变量的原子操作:
把多个共享变量合并成一个共享变量来操作。
封装成对象。注:从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之前的原子性,可以把多个变量放在一个对象里来进行CAS操作。
UNSAFE类:
作用:
Java中的Unsafe类为我们提供了类似C++手动管理内存的能力,它可以直接分配内存,内存复制,copy,提供cpu级别的CAS乐观锁等操作。
方法说明参考
方法解析参考
Unsafe对象的获取:
public class GetUnsafe {
/* public static Unsafe get() {
*//* 报错Exception in thread "main" java.lang.SecurityException:Unsafe
我们写的程序调用的时 Laucher入口类 AppClassLoader加载器
Unsafe类在rt.jar 下 由Bootstarp类价值器加载
Unsafe.getUnsafe();
方法中存在下列判断
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");*//*
Unsafe unsafe = Unsafe.getUnsafe();
return unsafe;
}*/
public static Unsafe get() {
try {
/**
* 通过反射获取到Unsafe类中的 private static final Unsafe theUnsafe;这个属性
* usafe在静态代码块中已经 theUnsafe = new Unsafe(); 创建了这个属性
*/
Field field = Unsafe.class.getDeclaredField("theUnsafe");
//将属性设置为可取
field.setAccessible(true);
//获得属性值
return (Unsafe) field.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
偏移量:
//staticFieldOffset方法用于获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量。
public native long staticFieldOffset(Field var1);
//objectFieldOffset方法用于获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量。
public native long objectFieldOffset(Field var1);
//staticFieldBase方法用于返回Field所在的对象。
public native Object staticFieldBase(Field var1);
//arrayBaseOffset方法用于返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量。
public native int arrayBaseOffset(Class<?> var1);
//arrayIndexScale方法用于计算数组中第一个元素所占用的内存空间。
public native int arrayIndexScale(Class<?> var1);
CAS相关:
//参数说明: 要操作的对象 要操作属性的偏移量 预期值 要更新的值
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
代码案例:
public class MyUnsafe {
//要设置的值
public volatile int state = 0;
public static void main(String[] args) throws NoSuchFieldException {
MyUnsafe myUnsafe = new MyUnsafe();
//获得Unsafe实例
Unsafe unsafe = GetUnsafe.get();
//获得要修改属性的偏移量
long stateOffset = unsafe.objectFieldOffset(MyUnsafe.class.getDeclaredField("state"));
//通过偏移量来修改state的值 要操作的对象 要操作属性的偏移量 预期值 要更新的值
boolean b = unsafe.compareAndSwapInt(myUnsafe, stateOffset, 0, 5);
System.out.println("是否修改成功" + b);
System.out.println("更新后的值" + myUnsafe.state);
//ABA问题示例
boolean v = unsafe.compareAndSwapInt(myUnsafe, stateOffset, 5, 3);
System.out.println("是否修改成功" + v);
System.out.println("再次更新后的值" + myUnsafe.state);
}
}
原子操作类
各种原子操作类内部通过Unsafe使用CAS保证操作的原子性,用volatile保证可见性和有序性(禁止指令重排),从而保证多线程变量的安全性。
原子类种类
部分原子类的简单使用
Atomiclnteger 基本数据类型的原子类
public class Atomiclnteger_Test {
public static void main(String[] args) {
Atomiclnteger_Test t = new Atomiclnteger_Test();
t.test();
}
public void test() {
AtomicInteger integer = new AtomicInteger(10);
System.out.println(integer.get()); //读值
integer.incrementAndGet();//安全自增
System.out.println(integer.get());
integer.getAndIncrement();//安全自增
System.out.println(integer.get());
integer.addAndGet(5); //确认加值
System.out.println(integer.get());
//设置给定值并返回旧值。
System.out.println(integer.getAndSet(2));
System.out.println(integer.get());
}
}
AtomicReference 引用类型原子类
public class AtomicReference_Test {
//原子引用类
static AtomicReference<User> userRef = new AtomicReference();
public static void main(String[] args) {
User xrl = new User("xrl", 18);
//设置原子引用类的值
userRef.set(xrl);
User ll = new User("ll", 5);
//修改原子引用类的值 , xrl对象本身不变
//参数: 预期对象 修改对象
userRef.compareAndSet(xrl, ll);
//内存值改变
System.out.println(userRef.get().getName());
}
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
}
}
AtomicArray 数组类型原子类
public class AtomicArray_Test {
static int[] value = new int[]{1, 2};
static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(value);
public static void main(String[] args) {
//将数组0位置上的值修改为3,原数组不变
atomicIntegerArray.set(0,3);
System.out.println(atomicIntegerArray.get(0));
System.out.println(value[0]);
}
}
AtomicStampedReference 带时间戳的原子类
public class AtomicStampedReference_Test {
//参数:原子操作的对象 版本号
static AtomicStampedReference atr = new AtomicStampedReference("xrl", 0);
public static void main(String[] args) throws InterruptedException {
int stamp = atr.getStamp();
String reference = (String) atr.getReference();
System.out.println(reference + "========" + stamp);
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 的当前变量为:" + atr.getReference()
//原信息 修改的信息 原版本号 修改后的版本号
+ " 时间戳为:" + atr.getStamp() + " ---CAS交换的结果 " + atr.compareAndSet(reference, reference + "java", stamp, stamp + 1));
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//数据更新不能成功,线程休眠了一下, atr.compareAndSet会比上面那个线程后运行
//上面那个线程运行完后 , reference信息和stamp版本号(时间戳)已经改变
System.out.println(Thread.currentThread().getName() + " 的当前变量为:" + atr.getReference()
//原信息 修改的信息 原版本号 修改后的版本号
+ " 时间戳为:" + atr.getStamp() + " ---CAS交换的结果 " + atr.compareAndSet(reference, reference + "c", stamp, stamp + 1));
//ABA
System.out.println("ABA问题:" + "当前变量为:" + atr.getReference()
//原信息 修改的信息 原版本号 修改后的版本号
+ "时间戳为:" + atr.getStamp() + " ---CAS交换的结果 " + atr.compareAndSet("xrljava", reference + "c", 1, stamp + 1));
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果:" + atr.getStamp() + "========" + atr.getReference());
}
}
自定义原子类
public class MyAtomicLong {
private static Unsafe unsafe;
private static long valueOffset;
private volatile long value;
static {
unsafe = GetUnsafe.get();
try {
valueOffset = unsafe.objectFieldOffset(MyAtomicLong.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public MyAtomicLong(long value) {
this.value = value;
}
public final long incrementAndGet() {
//自旋
long v;
do {
//unsafe.getAndAddLong(this,valueOffset,1L);
v = unsafe.getLongVolatile(this, valueOffset);
System.out.println(Thread.currentThread().getName() + "值为" + v);
} while (!unsafe.compareAndSwapLong(this, valueOffset, v, v + 1L));
return v;
}
public long get() {
return unsafe.getLong(this, valueOffset);
}
public static void main(String[] args) {
MyAtomicLong aLong = new MyAtomicLong(5);
System.out.println(aLong.incrementAndGet());
System.out.println(aLong.get());
}
}