【注意】最后更新于 May 19, 2018,文中内容可能已过时,请谨慎使用。
java中的同步块使用synchronized标记,所有同步在一个对象上的同步块在同时只能被一个线程进入并执行,可保证其内部的共享变量实现多线程可见性,也叫内置锁
基本使用
1
2
3
4
5
|
public synchronized void test(){
}
|
执行方式
- 尝试获取锁
- 如果获得锁则执行方法体
- 如果无法获取锁则等待,并且不断尝试获得锁,一旦锁释放可能发生锁竞争问题
在什么时候获取锁?
线程在加入同步代码块之前获取锁
什么时候释放锁?
退出同步代码块时自动释放锁(不论是正常退出还是抛出异常)
缺点:锁竞争问题,在高并发、线程数量高时会引起 CPU 占用居高不下,或者直接宕机。
JMM 对 synchroized 相关规定
- 线程解锁前,必须将工作内存中数据刷新到主内存。
- 线程加锁时,必须先清空工作内存,然后将主内存数据刷新到工作内存
类锁和对象锁
类锁:作用在静态方法上,所有对象共享一把锁,存在锁竞争
对象锁:作用在非静态方法上,一个对象一把锁,多个对象之间不会锁竞争
对象锁
实例方法同步
java实例方法同步在拥有该方法的对象上
例子
1
2
3
|
public synchronized void add(int value){
this.count += value;
}
|
实例方法中的同步块
用来同步方法中的一部分,同步实例方法使用调用方法本身的实例作为监视器对象
例子
1
2
3
4
5
|
public void add(int value){
synchronized(this){
this.count += value;
}
}
|
使用this关键字即为调用add方法的实例本身
synchronized
里面除了传入this
还可以是任意对象
使用同步块的好处
类锁
synchronized (类.class)
1
2
3
4
5
6
7
|
public void test(){
synchronized(类名.class){
}
}
|
静态方法中的同步块
public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
和synchronized (类.class)效果相同
内置锁的同步和异步
第一种情况:同时执行同一个对象的两个对象锁方法,出现等待
第二种情况:同时执行同一个对象的一个对象锁方法和普通方法,不等待
第三种情况:同时执行同一个对象/两个对象的两个类锁方法,出现等待
第四种情况:一个类锁一个普通方法(不论是同一个对象还是两个对象),不等待
第五种情况:一个类锁和一个对象锁(不论是同一个对象还是两个对象),不等待
结论:
- 类锁和对象锁互不影响
- 对象锁只对
synchronized
修饰的方法同步执行,普通方法异步执行,类锁同理
synchronized的锁重入
- 同一个线程得到了一个对象的锁后,再次请求该对象时可以再次获得该对象的锁
- 父子类也可重入
锁失效问题
修改对象的引用锁会失效
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
|
public class TheardTest002 {
private int count0;
private String lock="lock";
public void doSomething() {
synchronized (lock){
count0++;
lock="";//修改对象
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "," + new Date().getTime() + ","+ count0);
}
}
public static void main(String[] args) {
TheardTest002 theardTest0 = new TheardTest002();
Thread thread0 = new Thread(new Runnable() {
@Override
public void run() {
theardTest0.doSomething();
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
theardTest0.doSomething();
}
});
thread0.start();
thread1.start();
}
}
|
输出
1
2
|
Thread-0,1522237994517,2
Thread-1,1522237994517,2
|
上面的代码如果不对lock的引用做修改则是线程安全的依次输出1,2,但是因为lock被改变了,变成两个对象锁了,线程2获取到了新的对象锁,不再互斥
死锁问题
两个或者两个以上线程互相持有锁,永远在互相等待
例子
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
46
47
48
|
public class TheardTest003 {
public synchronized void doSomething(TheardTest004 theardTest004) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("进入->doSomething");
//持有theardTest003的锁,尝试获取theardTest004的锁
theardTest004.doSomething2(this);
System.out.println("结束->doSomething");
}
public class TheardTest004 {
public synchronized void doSomething2(TheardTest003 theardTest003) {
//持有theardTest004锁,尝试获取theardTest003的锁
System.out.println("进入->doSomething2");
theardTest003.doSomething(this);
System.out.println("结束->doSomething2");
}
}
public static void main(String[] args) {
TheardTest003 theardTest003 = new TheardTest003();
TheardTest004 theardTest004 = new TheardTest003().new TheardTest004();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
theardTest003.doSomething(theardTest004);
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
theardTest004.doSomething2(theardTest003);
}
});
thread1.start();
thread2.start();
}
}
|