当我们在使用synchronized时,通常有下面几种方式:
synchronized(this){}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class SynchronizedTest {
public static class RunnableThread implements Runnable{
@Override public void run() { synchronized (this){ System.out.println(this); for(int i = 0; i < 50; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
@Test public void synchronizedThisTest(){ RunnableThread r1 = new RunnableThread();
Thread t1 = new Thread(r1, "t1"); Thread t2 = new Thread(r1, "t2"); t1.start(); t2.start(); }
}
|
1 2 3 4 5 6
| com.jzl.javabase.SynchronizedTest$RunnableThread@4c5986b6 t1:0 ... com.jzl.javabase.SynchronizedTest$RunnableThread@4c5986b6 t2:0 ...
|
此时我们可以看到syschronized(this)中this指的是同一对象, 这段代码块同一时刻只能有同一线程运行。
再来看这种形式:
1 2 3 4
| RunnableThread r1 = new RunnableThread(); RunnableThread r2 = new RunnableThread(); Thread t1 = new Thread(r1, "t1"); Thread t2 = new Thread(r2, "t2");
|
1 2 3 4
| com.jzl.javabase.SynchronizedTest$RunnableThread@4c5986b6 com.jzl.javabase.SynchronizedTest$RunnableThread@22419e86 t2:0 ...
|
此时this指的不是同一个对象, 所以这种情况syschronized代码块并不能保证同一时刻只有同一线程运行。
再来看看这种情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public static class CommonClass{
public void lockMethod() { synchronized (this){ for(int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public void notLockMethod() { for(int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
@Test public void test2() throws InterruptedException { CommonClass cc = new CommonClass(); Thread t1 = new Thread(() -> {cc.lockMethod();}, "t1"); Thread t2 = new Thread(() -> {cc.notLockMethod();}, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); }
|
运行结果:
很显然,当一个线程访问这个对象的synchronized(this)方法时, 其他线程仍然可以访问这个对象的非synchronized(this)方法。
public synchronized void lockMethod()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public static class LockClass{
public synchronized void lockMethod(){ for(int i = 0; i < 5; i ++){ System.out.println(Thread.currentThread().getName() + ":" + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
@Test public void lockClassTest() throws InterruptedException { LockClass l1 = new LockClass();
Thread t1 = new Thread(() -> {l1.lockMethod();}, "t1"); Thread t2 = new Thread(() -> {l1.lockMethod();}, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); } @Test public void lockClassTest() throws InterruptedException { LockClass l1 = new LockClass(); LockClass l2 = new LockClass(); Thread t1 = new Thread(() -> {l1.lockMethod();}, "t1"); Thread t2 = new Thread(() -> {l2.lockMethod();}, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); }
|
通过对比运行结果发现, 这种方式与synchronized(this)方式相同。第二种虽然方法是同步的, 但是锁住的却不是一个对象, 所以不能保证同一时刻只有一个线程运行此代码块。
如果要求不同对象间也只能有一个线程运行某个方法, 有两种方法可以实现:
- synchronized(Object.class){// 同步代码块}
- public synchronized static void lockMethod(){// 同步代码块}
- public void lockMethod2(SomeObject so){synchronized(so){// 同步代码块}}
对于第一种方法, 通常定一个静态byte数组实现:
1 2 3 4 5 6
| private static final byte[] mutex = new byte[0]; ... synchronzied(mutex){ ...
synchronzied(SomeObject.class){
|
注:使用byte数组创建起来比其他对象更加经济–生成零长度的byte数组只需要三行操作码,而new Object()需要7行。
总结
在使用synchronized锁时,我们要注意锁住的对象到底是什么,然后在假设同时有多个线程同时进入代码块可能会发生的情况。这样我们就能设计更安全的多线程程序。