关于Java中的synchronized关键字
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于Java中的synchronized关键字相关的知识,希望对你有一定的参考价值。
【内容简介】
本文主要介绍Java中如何正确的使用synchronized关键字实现线程的互斥锁。
【能力需求】
至少已经完整的掌握了Java的语法基础,基本的面向对象知识,及创建并启动线程。
【正文】
关于synchronized关键字的使用,很多说法是“锁同一个对象”就可以确保锁是正常的,今天,有人提了一个问题,我觉得非常不错,所以与各位一起分享一下。
在这里,就不提关于线程和synchronized关键字的基本使用了,以非常传统的“银行取钱”的故事为案例,直接上代码:
Ps:以下代码是直接敲的,不是在IDE工具中复制出来的,可能存在敲错的地方,请见谅。
public class ThreadSample { public static Object lock = new Object(); public static int money = 1000; public static void main(String args) { Card card1 = new Card(); Card card2 = new Card(); card1.start(); card2.start(); } } class Card extends Thread { public void run() { synchronize(ThreadSample.lock) { if(ThreadSample.money >= 800) { ThreadSample.money -= 800; System.out.println("取款成功,余额:" + ThreadSample.money); } else { System.out.println("余额不足,取款失败!"); } } } }
以上代码是模拟取钱的,使用static成员money表示帐户,所以保证了多个线程访问到的是同一个值,同时,基于static成员是唯一且具有共享特性的,以上代码中使用static成员lock用于synchronized锁定,这个做法是可行的,所以,在很多人的观点里会认为“锁的是同一个对象就可以实现锁定”。
但是,有人测试出了以下这样的代码并提问(忽略掉相同的代码片段):
class Card extends Thread { public Integer i = 1; public void run() { synchronize(i) { if(ThreadSample.money >= 800) { ThreadSample.money -= 800; System.out.println("取款成功,余额:" + ThreadSample.money); } else { System.out.println("余额不足,取款失败!"); } } } }
写到这里,我不得不发自内心的再说一句,他作为一名第1次学习到线程的人,提出这样的问题,肯定是非常优秀的!
以上代码,在Card中声明了public Integer i = 1;并使用这个Integer对象作为synchronized锁的对象,最后,锁定也是成功的。
如果参考此前所说的“锁的是同一个对象就可以实现锁定”,那么,从代码里可以看到,运行过程中创建了2个Card对象,那么这2个Card对象有各自的Integer i成员,锁的就不是同一个对象,却也成功的锁定了,所以,此前的说法是不贴切的!
也许会有人说Integer或类似的包装类很特殊,也许是对应的int这些基本数据类型在作祟,那么,以下代码仍然是可行的:
class Card extends Thread { public String str = "www.tedu.cn"; public void run() { synchronize(str) { if(ThreadSample.money >= 800) { ThreadSample.money -= 800; System.out.println("取款成功,余额:" + ThreadSample.money); } else { System.out.println("余额不足,取款失败!"); } } } }
好了,用String也是可以锁的,那就跟基本数据类型没有关系了,下一步的解释会是什么呢?先别着急,再来看一段代码:
public class ThreadSample { public static int money = 1000; public static void main(String args) { Integer i1 = 1; Integer i2 = 1; Card card1 = new Card(i1); Card card2 = new Card(i2); card1.start(); card2.start(); } } class Card extends Thread { public Integer i; public Card(Integer i) { this.i = i; } public void run() { synchronize(i) { if(ThreadSample.money >= 800) { ThreadSample.money -= 800; System.out.println("取款成功,余额:" + ThreadSample.money); } else { System.out.println("余额不足,取款失败!"); } } } }
以上代码中,在Card中添加了带参数的构造方法,为成员Integer i赋值,并使用成员Integer i来实现锁定,并且,在运行线程之前,分别声明了Integer i1和Integer i2用于创建Card对象,结果,锁定依然是可行的。
如果有兴趣的话,可以把以上代码中的Integer换成String,可以发现,仍然还是可行的!
在这里,已经很明显了,在运行线程之前,声明了不同的对象用于创建Card对象,不管是Integer类型还是String类型,都是可行的!
那么,应该怎么解释这个问题呢?也许会有人想到equals()和hashCode(),是不是equals()对比出true,并且hashCode()值相同,就可以呢?这个想法是错的,请看以下代码:
public class ThreadSample { public static int money = 1000; public static void main(String args) { long timeMillis = System.currentTimeMillis(); Date d1 = new Date(timeMillis); Date d2 = new Date(timeMillis); Card card1 = new Card(d1); Card card2 = new Card(d2); card1.start(); card2.start(); } }
我们都知道Date类是重写了equals()和hashCode()的,以上代码中的d1和d2表示的时间完全相同,所以,d1和d2的equals()对比结果为true,且hashCode()返回值是相同的,但是,以上代码却不能成功的实现锁定!
看完了以上这么纠结的问题,最后总结一下答案,通俗一点来说:如果使用==判断结果为true,就可以用于synchronize锁定!
比如,在使用String测试的时候,声明以下2个String类型的数据:
String str1 = "www.cnblogs.com"; String str2 = "www.cnblogs.com";
分别使用以上2个对象创建Card,并在Card中声明String成员用于锁定,结果是OK的,但是,以下代码就不行:
String str1 = "www.cnblogs.com"; String str2 = ""; str2 += "www.cnblogs.com";
尽管组成字符是相同的,但是,str1是直接使用常量赋值的,对应的数据保存在字符串常量池中,而str2最后是通过变量运算出来的,是运行期得到的结果,对应的数据将位于堆内存中,所以,在栈内存中保存的str1和str2的引用并不相同,也就是str1 == str2的结果为false,所以,这样来锁是失败的!
上述关于String的第1段代码中,是在编译期使用String常量赋值给str1和str2,由于字符串常量池中的字符串是唯一的,所以str1、str2使用==时的结果将是true,至于Integer这些包装类,内部是通过诸多static成员来实现的,基于static的特性,所以上述代码中的i1、i2使用==判断时的结果也会是true。
所以,关于synchronized锁的对象,学术化的定义可以是“对象在栈内存中的引用相同,就是同一个对象,就可以用于锁”,通俗的表达就是“使用==判断为true就可以用于锁”,因为使用==判断其实就是判断栈内存中存储的数据。
【小结】
用于synchronized锁的对象,如果可以存在2个,并使用==判断的结果为true,就可以用于锁!
如果本文有不足或者错误的地方,欢迎随时指出,如需转载,请注明出处,Thank you !
以上是关于关于Java中的synchronized关键字的主要内容,如果未能解决你的问题,请参考以下文章