标签:exception throw dem out alt 最新 嗅探 add 比较
锁的作用是什么?就是使多线程同步执行,避免异步出现的脏读错误。从这个角度出发我们可以很自然的象到该如何设计一个锁尼?
我们可以这样设计,当有两个线程要访问待同步的代码块时,我们将该同步代码块的使用权交给第一个访问的线程,使其能够顺利运行,而在第一个线程没有结束使用的之前,其他线程如果也要访问该同步代码块,则会循环等待,知道第一个线程结束了同步代码块的使用。基于这个想法我们实现下面代码。
package reentrantlockTest; /** * @author :dazhu * @date :Created in 2020/4/12 8:43 * @description:朱超成实现的第一把锁 * @modified By: * @version: v1$ */ public class ZccLock { //锁的初始状态为0 private int status = 0; public void lock() { //如果锁状态不为0,则一直循环 //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待 while (this.status != 0) { } //如果状态为0,则置1。使其他线程进入后,一直等待。 this.status = 1; } public void unlock(){ //锁状态置0 this.status = 0; } }
这是最基本的实现方式我们做个测试demo看一下;
package reentrantlockTest; import java.util.concurrent.locks.ReentrantLock; /** * @author :dazhu * @date :Created in 2020/4/12 8:07 * @description: * @modified By: * @version: $ */ public class Main { //可重入锁 public static ReentrantLock reentrantLock = new ReentrantLock(); //自建锁 public static ZccLock zccLock = new ZccLock(); public static void main(String[] args) throws InterruptedException { for(int i=0;i<10;i++) { Thread t1 = new Thread(Integer.toString(i)) { @Override public void run() { testSync(); } }; t1.start(); } } public static void testSync(){ //reentrantLock.lock(); //使用自建锁 zccLock.lock(); System.out.println(Thread.currentThread().getName()); try{ Thread.sleep(100); } catch (InterruptedException ie){ ie.printStackTrace(); } System.out.println(Thread.currentThread().getName()); //reentrantLock.unlock(); //使用自建锁 zccLock.unlock(); } }
经过测试发现;只能循行第一个线程,其他线程似乎并没有从循环等待中退出,也就是status的改变没有被其他线程获得!为什么会这样呢?因为虽然第一个线程在unlock中修改了status为0,但是其他线程的内部缓存中并没有还想其改变为0了,依然里面是旧值,所以依然陷入了循环。那么怎么解决尼?我们可以使用volatile关键字来创建status!这样就可以在第一个线程unlock的时候,其他线程也发现了该值被改变了,从而通过总线嗅探技术来从主存中重新获取新值。经过修改发现这两个线程了同步运行了。
当我们增加线程数量时,发现众多线程之间依然不会同步执行这是为什么尼?可能原因会是什么尼?根据这个现象,显然线程之间异步执行了该代码块。这是怎么发生的尼?
这是由于我们循环比较和置1的这两行代码不是原子操作!怎么理解尼?有其他线程进入lock中的循环比较和置数之间,导致多线程其实自己status依然是0,这样就会多线程就异步执行了!如何解决尼?我们使用同步关键字。。。。。这不是套娃嘛?我们的目的就是创建自己的锁,怎么还是用java的锁关键字了。不过我们可以暂时使用synchronized来作证是不是我们分钟的原因。
package reentrantlockTest; /** * @author :dazhu * @date :Created in 2020/4/12 8:43 * @description:朱超成实现的第一把锁 * @modified By: * @version: v1$ */ public class ZccLock { //锁的初始状态为0 //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程 //发现到,status变为0了,以适他们退出while,得以运行同步块代码。 //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1 private volatile int status = 0; public void lock() { //如果锁状态不为0,则一直循环 //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待 synchronized (this) { while (this.status != 0) { } /** * 依然有问题,如果当t1线程执行到这里后, * 发生了切换,进入t2线程,那么t2线程因为t1线程没有完成修改了status=1 * 所以t2依然可以执行同步的代码段。 * t1和t2可以异步的执行同步的代码段。 * 只有将前面的比较和下面的置1做成同步块,才能避免错误。 * 这不是套娃吗? */ //如果状态为0,则置1。使其他线程进入后,一直等待。 this.status = 1; } } public void unlock(){ //锁状态置0 this.status = 0; } }
结果如下:
果然,线程之间没有异步执行!那如何解决这个问题尼?只要我们可以将循环比较和辅助做成原子操作就可以啦!有专门的汇编指令保证CAS算法的原子性啦
很显然,这种死循环的设计方法,当其他线程进入死循环后,十分浪费cpu的资源,如何改进尼?当进入循环后,不要一直循环,而是主动释放cpu资源阻塞该线程,在合适的时候再取唤醒主动释放cpu资源的这些阻塞线程。主要有以下方法!!!我们依依先进行讲解。
package reentrantlockTest; /** * @author :dazhu * @date :Created in 2020/4/12 8:43 * @description:朱超成实现的第一把锁 * @modified By: * @version: v1$ */ public class ZccLock { //锁的初始状态为0 //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程 //发现到,status变为0了,以适他们退出while,得以运行同步块代码。 //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1 private volatile int status = 0; public void lock() { //如果锁状态不为0,则一直循环 //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待 //死循环的cpu代价很大,所以使用费自旋+yield() while (this.status != 0) { Thread.currentThread().yield(); } this.status = 1; } public void unlock(){ //锁状态置0 this.status = 0; } }
在进入while循环的线程里,使用yiedl进行停止该线程的执行,但是本线程主动释放了该cpu资源后,cpu再次进行线程调度,这个调度的顺序是不受我们控制的,很有可能依然会调度该该主动释放cpu资源的线程。所以这个方法不合适。那么自旋+sleep()方法尼?
package reentrantlockTest; /** * @author :dazhu * @date :Created in 2020/4/12 8:43 * @description:朱超成实现的第一把锁 * @modified By: * @version: v1$ */ public class ZccLock { //锁的初始状态为0 //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程 //发现到,status变为0了,以适他们退出while,得以运行同步块代码。 //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1 private volatile int status = 0; public void lock() { //如果锁状态不为0,则一直循环 //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待 //死循环的cpu代价很大,所以使用自旋+sleep() while (this.status != 0) { Thread.currentThread().sleep(1000); } this.status = 1; } public void unlock(){ //锁状态置0 this.status = 0; } }
这种方法的能不能解决问题尼?似乎也不是最合适的解决问题的方法。这是因为一个线程等待时间多长合适尼?等待太长容易错过已经释放地同步资源,等待太短依然会造成cpu地资源的浪费。
package reentrantlockTest; /** * @author :dazhu * @date :Created in 2020/4/12 8:43 * @description:朱超成实现的第一把锁 * @modified By: * @version: v1$ */ public class ZccLock { //锁的初始状态为0 //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程 //发现到,status变为0了,以适他们退出while,得以运行同步块代码。 //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1 private volatile int status = 0; private Queue queue = new LinkedList(); public void lock() { //如果锁状态不为0,则一直循环 //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待 //死循环的cpu代价很大,所以使用费自旋+yield() while (this.status != 0) { Thread.currentThread().park(); } this.status = 1; } public void unlock(){ //锁状态置0 this.status = 0; lockNotify() } public void park(){ //将当前线程加入队列 queue.add(Thread.currentThread); releaseCPU(); } public void lockNotify(){ //取出队列头部地线程 Thread t = queue.getHead(); //唤醒该等待线程 unpark(t); } }
这样就可以实现避免进入死循环了,当进while地线程会被阻塞并进入等待队列,然后当占有同步资源地线程unlock后,会使用lockNotify将该阻塞线程从queue中取出唤醒,避免一直进入死循环浪费cpu资源。ReentrantLock就是这样的一种实现方法,下一阶我们分析ReentrantLock地源码哈
标签:exception throw dem out alt 最新 嗅探 add 比较
原文地址:https://www.cnblogs.com/dazhu123/p/12683799.html