首页 > 编程 > Java > 正文

Java锁类型

2019-11-06 06:10:05
字体:
来源:转载
供稿:网友

)7、乐观锁 相信,这里是安全的。概念:这里抛开数据库来谈乐观锁和悲观锁,扯上数据库总会觉得和java离得很远.悲观锁:一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放.乐观锁:一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作.从解释上可以看出,悲观锁具有很强的独占性,也是最安全的.而乐观锁很开放,效率高,安全性比悲观锁低,因为在乐观锁检查数据版本一致性时也可能被其他线程修改数据.从下面的例子中可以看出来这里说的安全差别.乐观锁例子:复制代码复制代码package note.com;/*** 乐观锁* * 场景:有一个对象value,需要被两个线程调用,由于是共享数据,存在脏数据的问题* 悲观锁可以利用synchronized实现,这里不提.* 现在用乐观锁来解决这个脏数据问题* * @author lxz**/public class OptimisticLock {public static int value = 0; // 多线程同时调用的操作对象/*** A线程要执行的方法*/public static void invoke(int Avalue, String i)throws InterruptedException {Thread.sleep(1000L);//延长执行时间if (Avalue != value) {//判断value版本System.out.PRintln(Avalue + ":" + value + "A版本不一致,不执行");value--;} else {Avalue++;//对数据操作value = Avalue;;//对数据操作System.out.println(i + ":" + value);}}/*** B线程要执行的方法*/public static void invoke2(int Bvalue, String i)throws InterruptedException {Thread.sleep(1000L);//延长执行时间if (Bvalue != value) {//判断value版本System.out.println(Bvalue + ":" + value + "B版本不一致,不执行");} else {System.out.println("B:利用value运算,value="+Bvalue);}}/*** 测试,期待结果:B线程执行的时候value数据总是当前最新的*/public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {//A线程public void run() {try {for (int i = 0; i < 3; i++) {int Avalue = OptimisticLock.value;//A获取的valueOptimisticLock.invoke(Avalue, "A");}} catch (InterruptedException e) {e.printStackTrace();}}}).start();new Thread(new Runnable() {//B线程public void run() {try {for (int i = 0; i < 3; i++) {int Bvalue = OptimisticLock.value;//B获取的valueOptimisticLock.invoke2(Bvalue, "B");}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}复制代码复制代码测试结果:A:10:1B版本不一致,不执行B:利用value运算,value=1A:2B:利用value运算,value=2A:3从结果中看出,B线程在执行的时候最后发现自己的value和执行前不一致,说明被A修改了,那么放弃了本次执行.多运行几次发现了下面的结果:A:1B:利用value运算,value=0A:21:2B版本不一致,不执行A:3B:利用value运算,value=2从结果看A修改了value值,B却没有检查出来,利用错误的value值进行了操作. 为什么会这样呢?这里就回到前面说的乐观锁是有一定的不安全性的,B在检查版本的时候A还没有修改,在B检查完版本后更新数据前(例子中的输出语句),A更改了value值,这时B执行更新数据(例子中的输出语句)就发生了与现存value不一致的现象.针对这个问题,我觉得乐观锁要解决这个问题还需要在检查版本与更新数据这个操作的时候能够使用悲观锁,比如加上synchronized,让它在最后一步保证数据的一致性.这样既保证多线程都能同时执行,牺牲最后一点的性能去保证数据的一致.补充感谢评论中提出的cas方式解决乐观锁最后的安全问题,以前不知道cas(比较-交换)这个在java中的存在,找了找资料才发现java的concurrent包确实使用的cas实现乐观锁的数据同步问题.下面是我对这两种方式的一点看法:有两种方式来保证乐观锁最后同步数据保证它原子性的方法1,CAS方式:Java非公开API类Unsafe实现的CAS(比较-交换),由C++编写的调用硬件操作内存,保证这个操作的原子性,concurrent包下很多乐观锁实现使用到这个类,但这个类不作为公开API使用,随时可能会被更改.我在本地测试了一下,确实不能够直接调用,源码中Unsafe是私有构造函数,只能通过getUnsafe方法获取单例,首先去掉eclipse的检查(非API的调用限制)限制以后,执行发现报 java.lang.SecurityException异常,源码中getUnsafe方法中执行访问检查,看来java不允许应用程序获取Unsafe类. 值得一提的是反射是可以得到这个类对象的.2,加锁方式:利用Java提供的现有API来实现最后数据同步的原子性(用悲观锁).看似乐观锁最后还是用了悲观锁来保证安全,效率没有提高.实际上针对于大多数只执行不同步数据的情况,效率比悲观加锁整个方法要高.特别注意:针对一个对象的数据同步,悲观锁对这个对象加锁和乐观锁效率差不多,如果是多个需要同步数据的对象,乐观锁就比较方便.8、公平锁 有优先级的锁()

7、乐观锁 相信,这里是安全的。

概念:

这里抛开数据库来谈乐观锁和悲观锁,扯上数据库总会觉得和Java离得很远.

悲观锁:一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放.

乐观锁:一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作.

从解释上可以看出,悲观锁具有很强的独占性,也是最安全的.而乐观锁很开放,效率高,安全性比悲观锁低,因为在乐观锁检查数据版本一致性时也可能被其他线程修改数据.从下面的例子中可以看出来这里说的安全差别.

 

乐观锁例子:

复制代码

复制代码

package note.com;

/**

* 乐观锁

* 场景:有一个对象value,需要被两个线程调用,由于是共享数据,存在脏数据的问题

* 悲观锁可以利用synchronized实现,这里不提.

* 现在用乐观锁来解决这个脏数据问题

* @author lxz

*

*/

public class OptimisticLock {

public static int value = 0; // 多线程同时调用的操作对象

/**

* A线程要执行的方法

*/

public static void invoke(int Avalue, String i)

throws InterruptedException {

Thread.sleep(1000L);//延长执行时间

if (Avalue != value) {//判断value版本

System.out.println(Avalue + ":" + value + "A版本不一致,不执行");

value--;

} else {

Avalue++;//对数据操作

value = Avalue;;//对数据操作

System.out.println(i + ":" + value);

}

}

/**

* B线程要执行的方法

*/

public static void invoke2(int Bvalue, String i)

throws InterruptedException {

Thread.sleep(1000L);//延长执行时间

if (Bvalue != value) {//判断value版本

System.out.println(Bvalue + ":" + value + "B版本不一致,不执行");

} else {

System.out.println("B:利用value运算,value="+Bvalue);

}

}

/**

* 测试,期待结果:B线程执行的时候value数据总是当前最新的

*/

public static void main(String[] args) throws InterruptedException {

new Thread(new Runnable() {//A线程

public void run() {

try {

for (int i = 0; i < 3; i++) {

int Avalue = OptimisticLock.value;//A获取的value

OptimisticLock.invoke(Avalue, "A");

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

new Thread(new Runnable() {//B线程

public void run() {

try {

for (int i = 0; i < 3; i++) {

int Bvalue = OptimisticLock.value;//B获取的value

OptimisticLock.invoke2(Bvalue, "B");

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

}

}

复制代码

复制代码

 

测试结果:

A:1

0:1B版本不一致,不执行

B:利用value运算,value=1

A:2

B:利用value运算,value=2

A:3

从结果中看出,B线程在执行的时候最后发现自己的value和执行前不一致,说明被A修改了,那么放弃了本次执行.

 

多运行几次发现了下面的结果:

A:1

B:利用value运算,value=0

A:2

1:2B版本不一致,不执行

A:3

B:利用value运算,value=2

从结果看A修改了value值,B却没有检查出来,利用错误的value值进行了操作. 为什么会这样呢?

这里就回到前面说的乐观锁是有一定的不安全性的,B在检查版本的时候A还没有修改,在B检查完版本后更新数据前(例子中的输出语句),A更改了value值,这时B执行更新数据(例子中的输出语句)就发生了与现存value不一致的现象.

 

针对这个问题,我觉得乐观锁要解决这个问题还需要在检查版本与更新数据这个操作的时候能够使用悲观锁,比如加上synchronized,让它在最后一步保证数据的一致性.这样既保证多线程都能同时执行,牺牲最后一点的性能去保证数据的一致.

 

补充

感谢评论中提出的cas方式解决乐观锁最后的安全问题,以前不知道cas(比较-交换)这个在java中的存在,找了找资料才发现java的concurrent包确实使用的cas实现乐观锁的数据同步问题.

下面是我对这两种方式的一点看法:

有两种方式来保证乐观锁最后同步数据保证它原子性的方法

1,CAS方式:Java非公开API类Unsafe实现的CAS(比较-交换),由C++编写的调用硬件操作内存,保证这个操作的原子性,concurrent包下很多乐观锁实现使用到这个类,但这个类不作为公开API使用,随时可能会被更改.我在本地测试了一下,确实不能够直接调用,源码中Unsafe是私有构造函数,只能通过getUnsafe方法获取单例,首先去掉eclipse的检查(非API的调用限制)限制以后,执行发现报 java.lang.SecurityException异常,源码中getUnsafe方法中执行访问检查,看来java不允许应用程序获取Unsafe类. 值得一提的是反射是可以得到这个类对象的.

2,加锁方式:利用Java提供的现有API来实现最后数据同步的原子性(用悲观锁).看似乐观锁最后还是用了悲观锁来保证安全,效率没有提高.实际上针对于大多数只执行不同步数据的情况,效率比悲观加锁整个方法要高.特别注意:针对一个对象的数据同步,悲观锁对这个对象加锁和乐观锁效率差不多,如果是多个需要同步数据的对象,乐观锁就比较方便.

8、公平锁 有优先级的锁(转载http://ifeve.com/starvation-and-fairness/#more-4370)

9、非公平锁 无优先级的锁

如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。解决饥饿的方案被称之为“公平性” – 即所有线程均能公平地获得运行机会。

下面是本文讨论的主题:

1. Java中导致饥饿的原因:

高优先级线程吞噬所有的低优先级线程的CPU时间。

线程被永久堵塞在一个等待进入同步块的状态。

线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。

2. 在Java中实现公平性方案,需要:

使用锁,而不是同步块。

公平锁。

注意性能方面。

Java中导致饥饿的原因

在Java中,下面三个常见的原因会导致线程饥饿:

高优先级线程吞噬所有的低优先级线程的CPU时间。

线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。

线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。

高优先级线程吞噬所有的低优先级线程的CPU时间

你能为每个线程设置独自的线程优先级,优先级越高的线程获得的CPU时间越多,线程优先级值设置在1到10之间,而这些优先级值所表示行为的准确解释则依赖于你的应用运行平台。对大多数应用来说,你最好是不要改变其优先级值。

线程被永久堵塞在一个等待进入同步块的状态

Java的同步代码区也是一个导致饥饿的因素。Java的同步代码区对哪个线程允许进入的次序没有任何保障。这就意味着理论上存在一个试图进入该同步区的线程处于被永久堵塞的风险,因为其他线程总是能持续地先于它获得访问,这即是“饥饿”问题,而一个线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。

线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象

如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

在Java中实现公平性

虽Java不可能实现100%的公平性,我们依然可以通过同步结构在线程间实现公平性的提高。

首先来学习一段简单的同步态代码:

1

public class Synchronizer{

2

 

3

public synchronized void doSynchronized(){

4

 

5

//do a lot of work which takes a long time

6

 

7

}

8

}

如果有一个以上的线程调用doSynchronized()方法,在第一个获得访问的线程未完成前,其他线程将一直处于阻塞状态,而且在这种多线程被阻塞的场景下,接下来将是哪个线程获得访问是没有保障的。

使用锁方式替代同步块

为了提高等待线程的公平性,我们使用锁方式来替代同步块。

1

public class Synchronizer{

2

Lock lock = new Lock();

3

public void doSynchronized() throws InterruptedException{

4

this.lock.lock();

5

//critical section, do a lot of work which takes a long time

6

this.lock.unlock();

7

}

8

}

注意到doSynchronized()不再声明为synchronized,而是用lock.lock()和lock.unlock()来替代。

下面是用Lock类做的一个实现:

01

public class Lock{

02

 

03

private boolean isLocked      = false;

04

 

05

private Thread lockingThread = null;

06

 

07

public synchronized void lock() throws InterruptedException{

08

 

09

while(isLocked){

10

 

11

wait();

12

 

13

}

14

 

15

isLocked = true;

16

 

17

lockingThread = Thread.currentThread();

18

 

19

}

20

 

21

public synchronized void unlock(){

22

 

23

if(this.lockingThread != Thread.currentThread()){

24

 

25

throw new IllegalMonitorStateException(

26

 

27

 "Calling thread has not locked this lock");

28

 

29

}

30

 

31

isLocked = false;

32

 

33

lockingThread = null;

34

 

35

notify();

36

 

37

}

38

}

注意到上面对Lock的实现,如果存在多线程并发访问lock(),这些线程将阻塞在对lock()方法的访问上。另外,如果锁已经锁上(校对注:这里指的是isLocked等于true时),这些线程将阻塞在while(isLocked)循环的wait()调用里面。要记住的是,当线程正在等待进入lock() 时,可以调用wait()释放其锁实例对应的同步锁,使得其他多个线程可以进入lock()方法,并调用wait()方法。

这回看下doSynchronized(),你会注意到在lock()和unlock()之间的注释:在这两个调用之间的代码将运行很长一段时间。进一步设想,这段代码将长时间运行,和进入lock()并调用wait()来比较的话。这意味着大部分时间用在等待进入锁和进入临界区的过程是用在wait()的等待中,而不是被阻塞在试图进入lock()方法中。

在早些时候提到过,同步块不会对等待进入的多个线程谁能获得访问做任何保障,同样当调用notify()时,wait()也不会做保障一定能唤醒线程(至于为什么,请看线程通信)。因此这个版本的Lock类和doSynchronized()那个版本就保障公平性而言,没有任何区别。

但我们能改变这种情况。当前的Lock类版本调用自己的wait()方法,如果每个线程在不同的对象上调用wait(),那么只有一个线程会在该对象上调用wait(),Lock类可以决定哪个对象能对其调用notify(),因此能做到有效的选择唤醒哪个线程。

公平锁

下面来讲述将上面Lock类转变为公平锁FairLock。你会注意到新的实现和之前的Lock类中的同步和wait()/notify()稍有不同。

准确地说如何从之前的Lock类做到公平锁的设计是一个渐进设计的过程,每一步都是在解决上一步的问题而前进的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。这些本身的讨论虽已超出本文的范围,但其中每一步的内容都将会专题进行讨论。重要的是,每一个调用lock()的线程都会进入一个队列,当解锁后,只有队列里的第一个线程被允许锁住Farlock实例,所有其它的线程都将处于等待状态,直到他们处于队列头部。

01

public class FairLock {

02

private boolean           isLocked       = false;

03

private Thread            lockingThread  = null;

04

private List<QueueObject> waitingThreads =

05

new ArrayList<QueueObject>();

06

 

07

 public void lock() throws InterruptedException{

08

QueueObject queueObject           = new QueueObject();

09

boolean     isLockedForThisThread = true;

10

synchronized(this){

11

waitingThreads.add(queueObject);

12

}

13

 

14

while(isLockedForThisThread){

15

 synchronized(this){

16

isLockedForThisThread =

17

isLocked || waitingThreads.get(0) != queueObject;

18

if(!isLockedForThisThread){

19

 isLocked = true;

20

  waitingThreads.remove(queueObject);

21

  lockingThread = Thread.currentThread();

22

  return;

23

}

24

 }

25

 try{

26

queueObject.doWait();

27

 }catch(InterruptedException e){

28

synchronized(this) { waitingThreads.remove(queueObject); }

29

throw e;

30

 }

31

}

32

 }

33

 

34

 public synchronized void unlock(){

35

if(this.lockingThread != Thread.currentThread()){

36

 throw new IllegalMonitorStateException(

37

"Calling thread has not locked this lock");

38

}

39

isLocked      = false;

40

lockingThread = null;

41

if(waitingThreads.size() > 0){

42

 waitingThreads.get(0).doNotify();

43

}

44

 }

45

}

 

01

public class QueueObject {

02

 

03

private boolean isNotified = false;

04

 

05

public synchronized void doWait() throws InterruptedException {

06

 

07

while(!isNotified){

08

this.wait();

09

}

10

 

11

this.isNotified = false;

12

 

13

}

14

 

15

public synchronized void doNotify() {

16

this.isNotified = true;

17

this.notify();

18

}

19

 

20

public boolean equals(Object o) {

21

return this == o;

22

}

23

 

24

}

首先注意到lock()方法不在声明为synchronized,取而代之的是对必需同步的代码,在synchronized中进行嵌套。

FairLock新创建了一个QueueObject的实例,并对每个调用lock()的线程进行入队列。调用unlock()的线程将从队列头部获取QueueObject,并对其调用doNotify(),以唤醒在该对象上等待的线程。通过这种方式,在同一时间仅有一个等待线程获得唤醒,而不是所有的等待线程。这也是实现FairLock公平性的核心所在。

请注意,在同一个同步块中,锁状态依然被检查和设置,以避免出现滑漏条件。

还需注意到,QueueObject实际是一个semaphore。doWait()和doNotify()方法在QueueObject中保存着信号。这样做以避免一个线程在调用queueObject.doWait()之前被另一个调用unlock()并随之调用queueObject.doNotify()的线程重入,从而导致信号丢失。queueObject.doWait()调用放置在synchronized(this)块之外,以避免被monitor嵌套锁死,所以另外的线程可以解锁,只要当没有线程在lock方法的synchronized(this)块中执行即可。

最后,注意到queueObject.doWait()在try – catch块中是怎样调用的。在InterruptedException抛出的情况下,线程得以离开lock(),并需让它从队列中移除。

性能考虑

如果比较Lock和FairLock类,你会注意到在FairLock类中lock()和unlock()还有更多需要深入的地方。这些额外的代码会导致FairLock的同步机制实现比Lock要稍微慢些。究竟存在多少影响,还依赖于应用在FairLock临界区执行的时长。执行时长越大,FairLock带来的负担影响就越小,当然这也和代码执行的频繁度相关。 

 

10、偏向锁 无竞争不锁,有竞争挂起,转为轻量锁(转载)

14、轻量级锁 CAS 实现

 

11、对象锁 锁住对象(转载http://ifeve.com/?x=42&y=9&s=%E5%AF%B9%E8%B1%A1%E9%94%81)

类锁和对象锁是否会冲突?对象锁和私有锁是否会冲突?通过实例来进行说明。

一、相关约定

为了明确后文的描述,先对本文涉及到的锁的相关定义作如下约定:

1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段,如下文中的increament();

2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段,如下文中的synOnMethod()和synInMethod();

3.私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock),如下文中的synMethodWithObj()。

二、测试代码

1.编写一个启动类ObjectLock

public class ObjectLock {

public static void main(String[] args) {

System.out.println("start time = " + System.currentTimeMillis()+"ms");

LockTestClass test = new LockTestClass();

for (int i = 0; i < 3; i++) {

Thread thread = new ObjThread(test, i);

thread.start();

}

}

}

2.编写一个线程类ObjThread,用于启动同步方法(注意它的run方法可能会调整以进行不同的测试)

public class ObjThread extends Thread {

LockTestClass lock;

int i = 0;

public ObjThread(LockTestClass lock, int i) {

this.lock = lock;

this.i = i;

}

public void run() {

//无锁方法

//lock.noSynMethod(this.getId(),this);

//对象锁方法1,采用synchronized synInMethod的方式

lock.synInMethod();

//对象锁方法2,采用synchronized(this)的方式

//lock.synOnMethod();

//私有锁方法,采用synchronized(object)的方式

//lock.synMethodWithObj();

//类锁方法,采用static synchronized increment的方式

LockTestClass.increment();

}

}

3.再编写一个锁的测试类LockTestClass,包括各种加锁方法

public class LockTestClass {

//用于类锁计数

private static int i = 0;

//私有锁

private Object object = new Object();

/**

* &lt;p&gt;

* 无锁方法

*

* @param threadID

* @param thread

*/

public void noSynMethod(long threadID, ObjThread thread) {

System.out.println("nosyn: class obj is " + thread + ", threadId is"

+ threadID);

}

/**

* 对象锁方法1

*/

public synchronized void synOnMethod() {

System.out.println("synOnMethod begins" + ", time = "

+ System.currentTimeMillis() + "ms");

try {

Thread.sleep(2000L);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("synOnMethod ends");

}

/**

* 对象锁方法2,采用synchronized (this)来加锁

*/

public void synInMethod() {

synchronized (this) {

System.out.println("synInMethod begins" + ", time = "

+ System.currentTimeMillis() + "ms");

try {

Thread.sleep(2000L);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("synInMethod ends");

}

}

/**

* 对象锁方法3

*/

public void synMethodWithObj() {

synchronized (object) {

System.out.println("synMethodWithObj begins" + ", time = "

+ System.currentTimeMillis() + "ms");

try {

Thread.sleep(2000L);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("synMethodWithObj ends");

}

}

/**

* 类锁

*/

public static synchronized void increament() {

System.out.println("class synchronized. i = " + i + ", time = "

+ System.currentTimeMillis() + "ms");

i++;

try {

Thread.sleep(2000L);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("class synchronized ends.");

}

}

三、测试结果

1.测试类锁和对象锁,ObjectThread的run方法修改如下:

public void run() {

//无锁方法

//lock.noSynMethod(this.getId(),this);

//对象锁方法1,采用synchronized synInMethod的方式

lock.synInMethod();

//对象锁方法2,采用synchronized(this)的方式

//lock.synOnMethod();

//私有锁方法,采用synchronized(object)的方式

//lock.synMethodWithObj();

//类锁方法,采用static synchronized increment的方式

LockTestClass.increament();

}

终端输出:

start time = 1413101360231ms

synInMethod begins, time = 1413101360233ms

synInMethod ends

class synchronized. i = 0, time = 1413101362233ms

synInMethod begins, time = 1413101362233ms

class synchronized ends.

synInMethod ends

class synchronized. i = 1, time = 1413101364233ms

synInMethod begins, time = 1413101364233ms

class synchronized ends.

synInMethod ends

class synchronized. i = 2, time = 1413101366234ms

class synchronized ends.

可以看到对象锁方法(synInMothod)第一次启动时比类锁方法(increament)快2秒,这是因为在synInMehtod执行时sleep了2秒再执行的increament,而这两个方法共用一个线程,所以会慢2秒,如果increament在run中放到synInMethod前面,那么第一次启动时就是increament快2秒。

而当类锁方法启动时,另一个线程时的对象锁方法也几乎同时启动,说明二者使用的并非同一个锁,不会产生竞争。

结论:类锁和对象锁不会产生竞争,二者的加锁方法不会相互影响。

2.私有锁和对象锁,ObjectThread的run方法修改如下:

public void run() {

//无锁方法

//lock.noSynMethod(this.getId(),this);

//对象锁方法1,采用synchronized synInMethod的方式

lock.synInMethod();

//对象锁方法2,采用synchronized(this)的方式

//lock.synOnMethod();

//私有锁方法,采用synchronized(object)的方式

lock.synMethodWithObj();

//类锁方法,采用static synchronized increment的方式

//LockTestClass.increament();

}

终端输出:

start time = 1413121912406ms

synInMethod begins, time = 1413121912407ms.

synInMethod ends.

synMethodWithObj begins, time = 1413121914407ms

synInMethod begins, time = 1413121914407ms.

synInMethod ends.

synMethodWithObj ends

synInMethod begins, time = 1413121916407ms.

synMethodWithObj begins, time = 1413121916407ms

synInMethod ends.

synMethodWithObj ends

synMethodWithObj begins, time = 1413121918407ms

synMethodWithObj ends

和类锁和对象锁非常类似。

结论:私有锁和对象锁也不会产生竞争,二者的加锁方法不会相互影响。

3.synchronized直接加在方法上和synchronized(this),ObjectThread的run方法修改如下:

public void run() {

//无锁方法

//lock.noSynMethod(this.getId(),this);

//对象锁方法1,采用synchronized synInMethod的方式

lock.synInMethod();

//对象锁方法2,采用synchronized(this)的方式

lock.synOnMethod();

//私有锁方法,采用synchronized(object)的方式

//lock.synMethodWithObj();

//类锁方法,采用static synchronized increment的方式

//LockTestClass.increament();

}

终端输出:

start time = 1413102913278ms

synInMethod begins, time = 1413102913279ms

synInMethod ends

synInMethod begins, time = 1413102915279ms

synInMethod ends

synOnMethod begins, time = 1413102917279ms

synOnMethod ends

synInMethod begins, time = 1413102919279ms

synInMethod ends

synOnMethod begins, time = 1413102921279ms

synOnMethod ends

synOnMethod begins, time = 1413102923279ms

synOnMethod ends

  可以看到,二者严格地串行输出(当然再次执行时先运行synInMethod还是先运行synOnMethod并不是确定的,取决于谁获得了锁)。

结论:synchronized直接加在方法上和synchronized(this)都是对当前对象加锁,二者的加锁方法够成了竞争关系,同一时刻只能有一个方法能执行。

12、线程锁

13、锁粗化 多锁变成一个,自己处理

15、锁消除 偏向锁就是锁消除的一种

16、锁膨胀 jvm实现,锁粗化

17、信号量 使用阻塞锁 实现的一种策略


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表