在JAVA中有很多锁,都可以实现多线程编程中实现数据的锁定,防止并发问题,本章节主要简单介绍下 synchronized
.
synchronized
是用于修饰用的加锁关键词,可以用于方法和代码块中,可以简单理解为锁住对象对应的指针地址,只要区分好指针对象是否同一个地址,就可以判断两个线程的锁是否互斥。
synchronized
是可重入的,意思就是当前线程获得锁之后,其他线程就无法获得锁进入,但是当前线程自己还可以再次获得锁多次进入。
由于使用比较简单,下面就简单列举一张表格,表示锁的应用场景以及说明。
修饰对象 | 被锁住对象 | 是否全局唯一 | 简单例子 |
---|---|---|---|
类的类型 | 类的类型 | √ |
synchronized(Example.class) {...}
, 全局锁,其他线程无法进入 |
类的实例 | 类的实例 | × |
synchronized(this){...}
或者 final A a = new A() synchronized(a){...} |
普通成员函数 | 类的实例 | × |
public synchronized void f() {...}
锁住类的实例 |
静态成员函数 | 类的类型 | √ |
public static synchronized void f() {...}
锁住类的类型 |
(静态)成员变量 | 成员对象所指向的实例,一定要用final修饰,不然重新赋值后就不是同一个对象了 | × |
final (static) A a = new A() synchronized(a){...} |
快速记忆法:可以简单理解成锁住对象是对象指针值。
以下的代码,请观察哪些是互斥的
// http://www.easysb.cn/2019/05/341.html public class LockTest { @Getter private final A a = new A(); private static int TICK = 5; public void bFun1() { synchronized (a) { int count = 0; while (count++ < TICK) { System.out.println("hello from LockTest::bFun1"); ThreadUtils.sleepQuitely(1000); } } } public void bFun2() { synchronized (a.getClass()) { int count = 0; while (count++ < TICK) { System.out.println("hello from LockTest::bFun2"); ThreadUtils.sleepQuitely(1000); } } } @Data public static class A { private final Object a = new Object(); private synchronized void aFun1() { int count = 0; while (count++ < TICK) { System.out.println("hello from A::aFun1"); ThreadUtils.sleepQuitely(1000); } } private void aFun2() { synchronized(this) { int count = 0; while (count++ < TICK) { System.out.println("hello from A::aFun2"); ThreadUtils.sleepQuitely(1000); } } } private synchronized static void aFun3() { int count = 0; while (count++ < TICK) { System.out.println("hello from A::aFun3"); ThreadUtils.sleepQuitely(1000); } } private synchronized static void aFun4() { synchronized (A.class) { int count = 0; while (count++ < TICK) { System.out.println("hello from A::aFun4"); ThreadUtils.sleepQuitely(1000); } } } } public static void main(String[] args) { LockTest t = new LockTest(); List<Thread> list = Lists.newArrayList(); list.add(new Thread(() -> { t.bFun1(); })); list.add(new Thread(() -> { t.bFun2(); })); list.add(new Thread(() -> { t.getA().aFun1(); })); list.add(new Thread(() -> { t.getA().aFun2(); })); list.add(new Thread(() -> { t.getA().aFun3(); })); list.add(new Thread(() -> { t.getA().aFun4(); })); for (Thread thread : list) { thread.start(); } ThreadUtils.sleepQuitely(1000 * 30); } }
按照上面地址的快速记忆理解方案,那么可以简单归纳如下:
LockTest::bFun1
锁住的对象是成员变量 a
地址。 LockTest::bFun2
锁住的对象是A的类对象 a.getClass()
,也就是 A.class
地址,是全局唯一的。 A::aFun1
修饰普通成员函数,锁住类对象实例的地址,也就是 a
地址。 A::aFun2
普通成员函数 this
,锁住类对象实例的地址,也就是 a
地址。 A::aFun3
静态成员函数,锁住A的类对象,也就是 A.class
地址。 A::aFun4
成员函数,锁住类对象实例的地址 A.class
,全局唯一。 从上可以看出互斥的方法如下:
a
地址: LockTest::bFun1
, A::aFun1
, A::aFun2
A.class
地址: LockTest::bFun2
, A::aFun3
, A::aFun4
输出的结果可能有多种,以下是一种输出结果
hello from LockTest::bFun2 hello from LockTest::bFun1 hello from LockTest::bFun2 hello from LockTest::bFun1 hello from LockTest::bFun2 hello from LockTest::bFun1 hello from LockTest::bFun2 hello from LockTest::bFun1 hello from LockTest::bFun1 hello from LockTest::bFun2 hello from A::aFun2 hello from A::aFun4 hello from A::aFun4 hello from A::aFun2 hello from A::aFun4 hello from A::aFun2 hello from A::aFun2 hello from A::aFun4 hello from A::aFun4 hello from A::aFun2 hello from A::aFun1 hello from A::aFun3 hello from A::aFun1 hello from A::aFun3 hello from A::aFun3 hello from A::aFun1 hello from A::aFun3 hello from A::aFun1 hello from A::aFun1 hello from A::aFun3