在java中,关键字synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能)

用法

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁;
  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁;
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
修饰实例方法
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
package thread;
public class ThreadSynchronized1 implements Runnable {
private static int i = 0;
private synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 2000; j++) {
increase();
}
}
public static void main(String[] args) {
ThreadSynchronized1 thread = new ThreadSynchronized1();
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (Exception e) {
}
System.out.println(i);
}
}

这里i是临界资源,i++不是原子操作,不论运行多少次,最终永远是4000。
但如果去掉synchronized关键字,那么结果每次运行可能都不一样。这里也有一个小坑,如果new了两个ThreadSynchronized1分别作为Runnable传到Thread中,那么结果也会错乱,就像这样,所以用在实例方法上一定要小心是哪个实例。

1
2
3
4
...
Thread thread1 = new Thread(new ThreadSynchronized1());
Thread thread2 = new Thread(new ThreadSynchronized1());
...

修饰静态方法

针对刚刚实例方法的修饰会有小坑,这里还有另一种解决办法,那就是把方法变成静态方法。

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
package thread;
public class ThreadSynchronized2 implements Runnable {
private static int i = 0;
private static synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 2000; j++) {
increase();
}
}
public static void main(String[] args) {
ThreadSynchronized2 thread = new ThreadSynchronized2();
Thread thread1 = new Thread(new ThreadSynchronized2());
Thread thread2 = new Thread(new ThreadSynchronized2());
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (Exception e) {
}
System.out.println(i);
}
}

这样就不会有问题了。

修饰代码块
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
package thread;
public class ThreadSynchronized3 implements Runnable {
private static int i = 0;
private void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 2000; j++) {
synchronized (this) {
increase();
}
}
}
public static void main(String[] args) {
ThreadSynchronized3 thread = new ThreadSynchronized3();
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (Exception e) {
}
System.out.println(i);
}
}

这里也要保证synchronized (this) 中的this是同一个对象,比如这样锁又会失效:

1
2
3
4
...
Thread thread1 = new Thread(new ThreadSynchronized3());
Thread thread2 = new Thread(new ThreadSynchronized3());
...

总结:synchronized很好用但是一定要保证用的是“同一把锁”。

synchronized原理

这里简单引用一下别人的文章

实现原理: JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。
其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

这几天在github上看到这个项目感觉很棒
https://github.com/crossoverJie/Java-Interview
里面分类目写了很多java内容,对查找资料很有帮助。