Syschronized可以保证方法或者代码块在运行时同一时刻只有一个线程能进入临界区,同时保证共享变量对其他线程的可见性

使用方法

synchronized关键字最主要的三种使用方式:

  • 普通方法,只对当前实例对象加锁
  • 静态方法,对类的所有对象加锁
  • 代码块
    • this :对当前实例对象加锁
    • Classname.class:对类的所有对象加锁

普通方法

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized void run() {
{
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程名:"+Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

注意:

  • 定义接口方法时不能使用synchronized关键字

  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步

  • synchronized关键字不能继承

    如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以

    还可以在子类方法中调用父类中相应的方法,虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了

    1
    2
    3
    4
    5
    6
    lass Parent {
    public synchronized void method() { }
    }
    class Child extends Parent {
    public synchronized void method() { }
    }
    1
    2
    3
    4
    5
    6
    class Parent {
    public synchronized void method() { }
    }
    class Child extends Parent {
    public void method() { super.method(); }
    }

静态方法

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
import java.util.ArrayList;
import java.util.List;

public class kt {
public static void main(String[] args) {
System.out.println("使用关键字静态synchronized");
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread implements Runnable {
private static int count;

public SyncThread() {
count = 0;
}

public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public synchronized void run() {
method();
}
}

syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁

代码块

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
28
29
30
31
32
33
34
import java.util.ArrayList;
import java.util.List;

public class kt {
public static void main(String[] args) {
System.out.println("使用关键字synchronized");
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程名:"+Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}

由于是同一个syncThread对象,所以加锁实现了同步

Classname.class

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
39
40
41
42
43
44
45
import java.util.ArrayList;
import java.util.List;

public class kt {
public static void main(String[] args) {
System.out.println("使用ClassName");
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
class ClassName {
public void method() {
synchronized(ClassName.class) {

}
}
}
class SyncThread implements Runnable {
private static int count;

public SyncThread() {
count = 0;
}

public static void method() {
synchronized(SyncThread.class) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public synchronized void run() {
method();
}
}

syncThread1和syncThread2是SyncThread的两个对象,但因为synchronized作用于类,类的所有对象都适用,所以能够同步

总结

  • 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
  • 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制

底层原理

Java对象头和monitor是实现synchronized的基础

同步代码块

使用monitorentermonitorexit指令实现的

monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置。任何对象都有一个monitor与之相关联,当一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁,monitorexit释放monitor

同步方法

依靠的是方法修饰符上的ACC_SYNCHRONIZED实现

同步方法的时候,一旦执行到这个方法,就会先判断是否有标志位,然后,ACC_SYNCHRONIZED会去隐式调用刚才的两个指令:monitorenter和monitorexit

归根究底,还是monitor对象的争夺

Monitor

Monitor是一种用来实现同步的工具,每个java对象都有一个Monitor与之对应

如果一个java对象被某个线程锁住

  • 该java对象的对象头的Mark Word字段中LockWord指向monitor的起始地址

  • Monitor的Owner字段会存放拥有相关联对象锁的线程id

可重入性

synchronized锁对象的时候有个计数器,他会记录下线程获取锁的次数,在执行完对应的代码块之后,计数器就会-1,直到计数器清零,就释放锁了