1、为什么要用锁?
锁-是为了解决并发操作引起的脏读、数据不一致的问题。
2、锁实现的基本原理
2.1、volatile
Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。
volatile在多处理器开发中保证了共享变量的“ 可见性”.可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。
2.2、synchronized
synchronized通过锁机制实现同步。
先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
2.2.1 synchronized实现原理
synchronized是基于Monitor来实现同步的。
Monitor从两个方面来支持线程之间的同步:
互斥执行
协作
1、Java 使用对象锁 ( 使用 synchronized 获得对象锁 ) 保证工作在共享的数据集上的线程互斥执行。
2、使用 notify/notifyAll/wait 方法来协同不同线程之间的工作。
3、Class和Object都关联了一个Monitor.
Monitor 的工作机理
线程进入同步方法中。
为了继续执行临界区代码,线程必须获取 Monitor 锁。如果获取锁成功,将成为该监视者对象的拥有者。任一时刻内,监视者对象只属于一个活动线程(The Owner)
拥有监视者对象的线程可以调用 wait() 进入等待集合(Wait Set),同时释放监视锁,进入等待状态。
其他线程调用 notify() / notifyAll() 接口唤醒等待集合中的线程,这些等待的线程需要重新获取监视锁后才能执行 wait() 之后的代码。
同步方法执行完毕了,线程退出临界区,并释放监视锁。
参考文档:#/developerworks/cn/java/j-lo-synchronized
2.2.2 synchronized具体实现
1、同步代码块采用monitorenter、monitorexit指令显式的实现。
2、同步方法则使用ACC_SYNCHRONIZED标记符隐式的实现。
通过实例来看看具体实现:
public class SynchronizedTest { public synchronized void method1(){ System.out.println(“Hello World!”); } public void method2(){ synchronized (this){ System.out.println(“Hello World!”); } }}
monitorenter
每一个对象都有一个monitor,一个monitor只能被一个线程拥有。当一个线程执行到monitorenter指令时会尝试获取相应对象的monitor,获取规则如下:
如果monitor的进入数为0,则该线程可以进入monitor,并将monitor进入数设置为1,该线程即为monitor的拥有者。
如果当前线程已经拥有该monitor,只是重新进入,则进入monitor的进入数加1,所以synchronized关键字实现的锁是可重入的锁。
如果monitor已被其他线程拥有,则当前线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor.
monitorexit
只有拥有相应对象的monitor的线程才能执行monitorexit指令。每执行一次该指令monitor进入数减1,当进入数为0时当前线程释放monitor,此时其他阻塞的线程将可以尝试获取该monitor.
2.2.3 锁存放的位置
锁标记存放在Java对象头的Mark Word中。
更多烟台培训相关资讯,请扫描下方二维码