1.10乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁
1)乐观锁:
就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
   
 乐观锁(Optimistic
Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
2)悲观锁:
还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
    悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁。
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
Select * from xxx for update;
3)重入锁:
  也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。
锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利。
synchronized锁示例:

package threadLearning.threadLock;
public class synchronizedTest implements Runnable {
public synchronized void get() {
System.out.println("name:" + Thread.currentThread().getName() + " get();");
set();
} public synchronized void set() {
System.out.println("name:" + Thread.currentThread().getName() + " set();");
} public void run() {
get();
} public static void main(String[] args) {
synchronizedTest ss = new synchronizedTest();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}

运行结果如下:

name:Thread-0 get();
name:Thread-0 set();
name:Thread-3 get();
name:Thread-3 set();
name:Thread-2 get();
name:Thread-2 set();
name:Thread-1 get();
name:Thread-1 set();

Lock锁示例:

package threadLearning.threadLock;

import java.util.concurrent.locks.ReentrantLock;

public class LockTest4 extends Thread {
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
LockTest4 ss = new LockTest4();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}

运行结果如下:

10
12
11

4) 读写锁:

  相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。尽管如此,还是应该了解其实现背后的原理。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReentrantReadWriteLockTest {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock(); // 获取一个key对应的value
public static final Object get(String key) {
r.lock();
try {
System.out.println("正在做读的操作,key:" + key + " 开始");
Thread.sleep(100);
Object object = map.get(key);
System.out.println("正在做读的操作,key:" + key + " 结束");
System.out.println();
return object;
} catch (InterruptedException e) { } finally {
r.unlock();
}
return key;
} // 设置key对应的value,并返回旧有的value
public static final Object put(String key, Object value) {
w.lock();
try {
System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始.");
Thread.sleep(100);
Object object = map.put(key, value);
System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束.");
return object;
} catch (InterruptedException e) { } finally {
w.unlock();
}
return value;
} // 清空所有的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
} public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
ReentrantReadWriteLockTest.put(i + "", i + "");
} }
}).start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
ReentrantReadWriteLockTest.get(i + "");
} }
}).start();
clear();
}
}

运行结果如下:

正在做写的操作,key:0,value:0开始.
正在做写的操作,key:0,value:0结束.
正在做写的操作,key:1,value:1开始.
正在做写的操作,key:1,value:1结束.
正在做写的操作,key:2,value:2开始.
正在做写的操作,key:2,value:2结束.
正在做写的操作,key:3,value:3开始.
正在做写的操作,key:3,value:3结束.
正在做写的操作,key:4,value:4开始.
正在做写的操作,key:4,value:4结束.
正在做写的操作,key:5,value:5开始.
正在做写的操作,key:5,value:5结束.
正在做读的操作,key:0 开始
正在做读的操作,key:0 结束 正在做写的操作,key:6,value:6开始.
正在做写的操作,key:6,value:6结束.
正在做写的操作,key:7,value:7开始.
正在做写的操作,key:7,value:7结束.
正在做写的操作,key:8,value:8开始.
正在做写的操作,key:8,value:8结束.
正在做写的操作,key:9,value:9开始.
正在做写的操作,key:9,value:9结束.
正在做读的操作,key:1 开始
正在做读的操作,key:1 结束 正在做读的操作,key:2 开始
正在做读的操作,key:2 结束 正在做读的操作,key:3 开始
正在做读的操作,key:3 结束 正在做读的操作,key:4 开始
正在做读的操作,key:4 结束 正在做读的操作,key:5 开始
正在做读的操作,key:5 结束 正在做读的操作,key:6 开始
正在做读的操作,key:6 结束 正在做读的操作,key:7 开始
正在做读的操作,key:7 结束 正在做读的操作,key:8 开始
正在做读的操作,key:8 结束 正在做读的操作,key:9 开始
正在做读的操作,key:9 结束

5)CAS无锁机制
  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以后,虚拟机便可以使用这个指令来实现并发操作和并发数据结构,并且,这种操作在虚拟机中可以说是无处不在。
6)自旋锁
  是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。示例如下:

package threadLearning.threadLock;

import java.util.concurrent.atomic.AtomicReference;

/**
*
* @classDesc: 功能描述:
* @author: zjb
* @createTime: 创建时间:2018-7-5 上午10:12:50
* @version: v1.0
* @copyright:
*/ public class SpinLock {
//AtomicReference 可以用原子方式更新的对象引用。
private AtomicReference<Thread> sign =new AtomicReference<Thread>();//使用给定的初始值创建新的 AtomicReference
public void lock() {
Thread current = Thread.currentThread();
//compareAndSet(V expect, V update) 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
while (!sign.compareAndSet(null, current)) {
}
}
public void unlock() {
Thread current = Thread.currentThread();
sign.compareAndSet(current, null);
} } package threadLearning.threadLock;
public class SpinLockTest implements Runnable {
static int sum;
private SpinLock lock; public SpinLockTest(SpinLock lock) {
this.lock = lock;
} /**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
SpinLock lock = new SpinLock();
for (int i = 0; i < 100; i++) {
SpinLockTest test = new SpinLockTest(lock);
Thread t = new Thread(test);
t.start();
} Thread.currentThread().sleep(1000);
System.out.println(sum);
} public void run() {
this.lock.lock();
this.lock.lock();
sum++;
this.lock.unlock();
this.lock.unlock();
} }

运行结果如下:

   当一个线程 调用这个不可重入的自旋锁去加锁的时候没问题,当再次调用lock()的时候,因为自旋锁的持有引用已经不为空了,该线程对象会误认为是别人的线程持有了自旋锁使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。
当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。
7)分布式锁:--示例 会后续补充
如果想在不同的jvm中保证数据同步,使用分布式锁技术。有数据库实现、缓存实现、Zookeeper分布式锁。

最新文章

  1. KVM的前世今生
  2. GDB详解
  3. BZOJ4443:[SCO2015]小凸玩矩阵
  4. java数组排序问题:array.sort()是从小到大排序,那么如何从大到小排序?
  5. Linux下Find命令的使用
  6. 常用mimetype
  7. poj 3753 Training little cats_矩阵快速幂
  8. sqlplus conn远程连接
  9. hibernate的cascade
  10. WordPress独立下载页面与演示插件:xydown
  11. JMeter中Ultimate Thread Group插件使用
  12. HNの野望
  13. 【CF1097E】Egor and an RPG game(动态规划,贪心)
  14. Nodejs this详解
  15. Java中的线程实现
  16. android bundle 对象 序列化
  17. 奖券数目|2015年蓝桥杯B组题解析第一题-fishers
  18. Linux学习笔记(第十二章)
  19. C# Redis Server分布式缓存编程(一)(转)
  20. EL标签

热门文章

  1. 跨域-JSONP
  2. 【硬核教程】只需1秒—你也可以有自己的API文档
  3. Pychram 运行程序在 run 窗口和 python console 窗口之间切换
  4. Python NumPy中数组array.min(0)返回数组
  5. 【代码审计】VAuditDemo 命令注入漏洞
  6. Vue 项目中使用less
  7. for in 与for 与hasOwnProperty
  8. Session服务器之Session复制!
  9. springboot集成过滤器
  10. if 条件的 true / false 及 select 的值