public class SynchronizedDemo implements Runnable {
@Override
public void run() {
set();
}
public synchronized void get() {
System.out.println("get方法");
}
public synchronized void set() {
System.out.println("set方法");
get();
}
public static void main(String[] args) {
new Thread(new SynchronizedDemo()).start();
}
}
public class ReentrantLockDemo implements Runnable {
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
set();
}
public void get() {
try {
lock.lock();
System.out.println("get方法");
} catch (Exception ex) {
} finally {
lock.unlock();
}
}
public void set() {
try {
lock.lock();
System.out.println("set方法");
} catch (Exception ex) {
} finally {
lock.unlock();
}
get();
}
public static void main(String[] args) {
new Thread(new ReentrantLockDemo()).start();
}
}
由上面的synchronized和ReentrantLock 锁的demo中,get和set方法都加锁了,执行set方法的时候,当执行到get方法时get方法也加了锁,get与set都属用的同一把锁,执行get方法时,set方法并没有释放锁,但是get方法并没有去重新获取锁资源,由于可以看出set方法将锁资源传递给了get方法,才可以正常执行。如果synchronized和ReentrantLock锁不具备重入性的话,则执行get方法时会重新获取锁资源,但是set方法并没有释放锁资源,则get与set方法会产生死锁情况,代码不能够正常执行。ReadWriteLock同Lock一样也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个是只读的锁,一个是写锁
public class ReadWriteLockDemo {
private volatile Map<String, String> cache = new HashMap<>();
public void put(String key, String value) {
System.out.println("put开始,key=" + key);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
cache.put(key, value);
System.out.println("put结束,key=" + key);
}
public String get(String key) {
System.out.println("get开始,key=" + key);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("get结束,key=" + key);
return cache.get(key);
}
public static void main(String[] args) {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
//写线程
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.put(i + "", i + "");
}
}).start();
//读线程
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.get(i+"");
}
}).start();
}
}
put开始,key=0
get开始,key=0
get结束,key=0
put结束,key=0
get开始,key=1
put开始,key=1
get结束,key=1
put结束,key=1
执行结果可以看出,当正在在写入数据的时候,同时也发生了读操作,这样读操作的时候很容易读取到脏数据public class ReadWriteLockDemo {
private volatile Map<String, String> cache = new HashMap<>();
//读写锁
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = rwl.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();
public void put(String key, String value) {
try {
writeLock.lock();
System.out.println("put开始,key=" + key);
Thread.sleep(1000);
cache.put(key, value);
System.out.println("put结束,key=" + key);
} catch (Exception e) {
} finally {
writeLock.unlock();
}
}
public String get(String key) {
try {
readLock.lock();
System.out.println("get开始,key=" + key);
Thread.sleep(1000);
System.out.println("get结束,key=" + key);
return cache.get(key);
} catch (Exception e) {
return null;
} finally {
readLock.unlock();
}
}
public static void main(String[] args) {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
//写线程
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.put(i + "", i + "");
}
}).start();
//读线程
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.get(i + "");
}
}).start();
}
}
put开始,key=0
put结束,key=0
get开始,key=0
get结束,key=0
put开始,key=1
put结束,key=1
put开始,key=2
put结束,key=2
执行结果看出,读写操作的开始和结束都是成对存在,由此可见:当写的时候,读操作并没有发生,读的时候,写操作也并没有发生,保证了读写操作时,线程安全问题。update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
(1)与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。
(2)无锁的好处:
(3)CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量(主内存),E表示预期值(本地内存),N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值
(4)CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理
(5)简单地说,CAS需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,那说明它已经被别人修改过了。你就重新读取,再次尝试修改就好了
(6)在硬件层面,大部分的现代处理器都已经支持原子化的CAS指令。在JDK 5.0以后,虚拟机便可以使用这个指令来实现并发操作和并发数据结构,并且,这种操作在虚拟机中可以说是无处不在
CAS存在一个很明显的问题,即ABA问题。
问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?
如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//获取当前值
int current = get();
//设置期望值
int next = current + 1;
//调用Native方法compareAndSet,执行CAS操作
if (compareAndSet(current, next))
//成功后才会返回期望值,否则无线循环
return next;
}
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 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;
}
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
#hasQueuedPredecessors的实现
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
public class FairLock implements Runnable {
/**
* true 表示 ReentrantLock 的公平锁
*/
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
try {
lock.lock();
Thread.sleep(50);
System.out.println(Thread.currentThread().getName() + "获得锁");
} catch (Exception ex) {
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
FairLock fairLock = new FairLock();
for (int i = 0; i < 5; i++) {
new Thread(fairLock).start();
}
}
}
Thread-0获得锁
Thread-1获得锁
Thread-2获得锁
Thread-3获得锁
Thread-4获得锁
可以看到,获取锁的线程顺序正是线程启动的顺序final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
public class NotFairLock implements Runnable {
/**
* false 表示 ReentrantLock 的非公平锁
*/
private ReentrantLock lock = new ReentrantLock(false);
@Override
public void run() {
try {
lock.lock();
Thread.sleep(50);
System.out.println(Thread.currentThread().getName() + "获得锁");
} catch (Exception ex) {
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
NotFairLock fairLock = new NotFairLock();
for (int i = 0; i < 5; i++) {
new Thread(fairLock).start();
}
}
}
Thread-0获得锁
Thread-2获得锁
Thread-1获得锁
Thread-3获得锁
Thread-4获得锁
可以看出非公平锁对锁的获取是乱序的,即有一个抢占锁的过程如果在不同的jvm中保证数据同步,需要使用分布式锁技术
实现方式:有数据库实现、缓存实现、Zookeeper分布式锁