先看一个简单示例,下面这段代码能够正常通过编译。
public class SyncTest
{
public SyncTest syncVar;
public static SyncTest syncStaticVar;
public static synchronized void testStaticSync()
{
}
public synchronized void testNonStaticSync()
{
}
public void testSyncThis()
{
synchronized (this )
{
try {
System. out.println("test
sync this start" );
Thread. sleep(5000);
System. out.println("test
sync this end" );
} catch (InterruptedException
e) {
e.printStackTrace();
}
}
}
public void testSyncVar()
{
synchronized (syncVar )
{
try {
System. out.println("test
sync var start" );
Thread. sleep(3000);
System. out.println("test
sync var end" );
} catch (InterruptedException
e) {
}
}
}
public void testStaticSyncVar()
{
synchronized (syncStaticVar )
{
}
}
public static void main(String[]
args) {
final SyncTest
testSync = new SyncTest();
testSync. syncVar = new SyncTest();
testSync. syncVar =
testSync;
Thread threadOne = new Thread(new Runnable()
{
@Override
public void run()
{
testSync.testSyncThis();
}
});
Thread threadTwo = new Thread(new Runnable()
{
@Override
public void run()
{
testSync.testSyncVar();
}
});
threadOne.start();
threadTwo.start();
}
}
从上面的代码来看,synchronized使用的场景很广,既能够锁住类方法(static),又能够锁住对象方法(非static)。既能够对某个属性加锁,又能够对this加锁。那么,sychronized到底锁住了什么?另外,synchronized也是可重入的,那么,它与单独的ReentrantLock的区别是什么?
sychronized在使用的时候也是区分目标对象的,在这一点上其实非常像前面提到过的ReentrantLock的使用。上面的代码中,如果把testSync. syncVar =
testSync;这句话注释掉,那么最后运行所产生的结果就会不一样。如果要和ReentrantLock做对比的话,可以把synchronized所针对的目标对象理解成一个锁,针对的目标对象不一样,那么就是不同的锁。
sychronized的实现原理需要深入到jvm中才能找到答案。。可以参考《深入理解Java虚拟机》
在这里主要讨论一个使用上的问题,当我们使用sychronized锁住某个对象时,我们锁住的是这个引用本身,还是内存(堆)中的这个对象本身。对这个问题的一个延伸是,当我们在sychronized作用区域内,为这个引用附一个新值的时候,sychronized是否还有效?
先给出结论,sychronized锁住的是内存(堆)中的对象,当引用被附上新值的时候,则相当于旧对象的锁被释放。这里不做理论讨论,只是用程序进行验证。
用三个例子进行说明,
示例1、
public class TestSyncAndModify implements Runnable
{
private A syncA;
@Override
public void run()
{
synchronized (syncA )
{
System. out .println(Thread.currentThread().getName());
syncA = new A();
try {
Thread. sleep(3000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
System. out .println(Thread.currentThread().getName());
}
}
static class A
{
};
public static void main(String[]
args) {
TestSyncAndModify sync = new TestSyncAndModify();
A testA = new A();
sync. syncA =
testA;
Thread one = new Thread(sync);
Thread two = new Thread(sync);
one.start();
try {
Thread. sleep(1000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
two.start();
}
}
在sychronized作用域内对syncA做了修改,使它指向了一个新的对象,所以当这句话执行完之后,第二个线程就可以运行,因此输出的结果如下
Thread-0
Thread-1
Thread-0
Thread-1
示例2、就是最上面的那段代码SyncTest。在main函数内,有这样一句赋值testSync. syncVar =
testSync;使得syncVar成员变量,指向了和this相同的区域。因此,在sychronized(this)和synchronized(syncVar)就形成了竞争,使得后者被阻塞,因此输出结果如下
test sync this start
test sync this end
test sync var start
test sync var end
示例3
public class ThreadUseBase extends Thread
{
private Base baseObject ;
public ThreadUseBase(Base
boj) {
baseObject =
boj;
}
public void testSyncBase()
{
synchronized (baseObject )
{
System. out .println("enter
thread use base" );
try {
Thread. sleep(2000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
System. out .println("leave
thread use base" );
}
}
@Override
public void run()
{
testSyncBase();
}
static class ThreadUseChild extends Thread
{
private SyncObject childObj ;
public ThreadUseChild(SyncObject
sobj) {
childObj =
sobj;
}
public void testSyncChild()
{
synchronized (childObj )
{
System. out .println("enter
thread use child" );
try {
Thread. sleep(2000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
System. out .println("leave
thread use child" );
}
}
@Override
public void run()
{
testSyncChild();
}
}
static class Base
{
}
static class SyncObject extends Base
{
}
public static void main(String[]
args) {
SyncObject childObj = new SyncObject();
Base baseObj = childObj;
//Base baseObj = new Base();
ThreadUseBase threadBase = new ThreadUseBase(baseObj);
ThreadUseChild threadChild = new ThreadUseChild(childObj);
threadBase.start();
threadChild.start();
}
}
虽然是两个线程中,sychronized锁住的是不同的引用,一个是Base一个是SyncObject,但由于存在继承关系,在main函数中,我们让Base对象也指向了SyncObject内存区域。这样,就形成了两个不同线程之间的竞争,因此后者被阻塞,输出结果如下
enter thread use base
leave thread use base
enter thread use child
leave thread use child
参考论文如下
JVM的锁结构设计原理 --- https://www.usenix.org/legacy/event/jvm01/full_papers/dice/dice.pdf
本文深入探讨Java中的同步机制,包括synchronized关键字在类方法、对象方法、属性和对象实例上的应用,以及其在JVM中的实现原理。通过示例分析,解释synchronized如何锁定内存中的对象,以及在引用对象改变时对锁的影响。同时,对比synchronized与ReentrantLock的区别,并通过代码验证理论。最后,验证synchronized锁定的是内存中的对象,而非引用本身,以及引用改变时锁的有效性。

354

被折叠的 条评论
为什么被折叠?



