Java synchronized 的用法

同步学习,才能不落伍

最近好好整理下 Java 方面的知识点,在练习到线程方面时,对同步方面的知识还是模糊不清的,于是开学今天好好地看了相关博客,下面仅仅记录下以备以后参考查询!

一、同步、异步、synchronized

首先我们先了解下什么是 同步异步synchronzied

同步:在计算机领域,指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去!

异步:是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

synchronized: 是 Java 中的关键词,其意思也是同步,其主要用在多线程中需要同步的情况下!

二、synchronized 用法

我先收集了实际应用过程中用到的写法:

1
2
3
4
5
6
7
8
//1、修饰方法
public synchronized void method1(){};
//2、修饰类内部this引用
synchronized(this){}
//3、修饰对象
synchronized(Obj1){}
//4、修饰类
synchronized(A.class){}

我一开始的误区就是不知道同步的时候该用哪种写法,实际上 synchronized 的用法只有两种,主要分为:synchronized修饰方法synchronized修饰代码块。上面第一种是修饰方法的,后三种都是修饰代码块的!下面将分别对这两种用法说明!

1、synchronized 修饰方法

下面用实例来说明:

情况1:正常情况,未使用 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
public class A {
//a方法没有加synchronized关键字
public void a(){
int i=5;
while(i-->0){
System.out.println(Thread.currentThread()+":a:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final A ma=new A();
//下面两个线程分别在内部调用类A的对象ma的a方法
Thread thread1=new Thread(new Runnable(){
public void run() {
ma.a();//线程1调用a方法
}},"Thread1");
Thread thread2=new Thread(new Runnable(){
public void run() {
ma.a();//线程2也调用a方法
}},"Thread2");
thread1.start();
thread2.start();
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
Thread1:a:4
Thread2:a:4
Thread2:a:3
Thread1:a:3
Thread2:a:2
Thread1:a:2
Thread1:a:1
Thread2:a:1
Thread2:a:0
Thread1:a:0

我们可以发现 未加同步 时,两个线程是互相交替执行 a 方法的,由于不同步所以类 A 的对象 ma 两个进程可同时访问,而哪个先执行由 CPU 自动调度!

情况2:使用同步,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
public class A {
//a方法加了synchronized关键字
public synchronized void a(){
int i=5;
while(i-->0){
System.out.println(Thread.currentThread()+":a:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final A ma=new A();
//下面两个线程分别在内部调用类A的对象ma的a方法
Thread thread1=new Thread(new Runnable(){
public void run() {
ma.a();//线程1调用a方法
}},"Thread1");
Thread thread2=new Thread(new Runnable(){
public void run() {
ma.a();//线程2也调用a方法
}},"Thread2");
thread1.start();
thread2.start();
}
}

输出结果为:

1
2
3
4
5
6
7
8
9
10
Thread1:a:4
Thread1:a:3
Thread1:a:2
Thread1:a:1
Thread1:a:0
Thread2:a:4
Thread2:a:3
Thread2:a:2
Thread2:a:1
Thread2:a:0

我们发现加了 synchronized 后,对象 ma 中的 a 方法变成了同步输出了,必须等到 Thread1 线程使用完 ma 对象后,Thread2 才能再使用 ma 对象。

实际上 synchronized 的同步就是 在类的对象上加上一把“锁”,当一个线程获得该对象就会立即锁定该对象,其他线程必须等待使用该对象的线程释放对象锁才能使用此对象资源!

也因此当 A 类中如果有多个加了 synchronized 的方法时,其他线程还是不能调用同步的方法,但是对于类A中没有加同步的方法,其他线程是可以调用的!也就是说 类中所有的同步方法同一时刻只能有一个线程访问,而非同步方法则允许多个线程访问

2、synchronized 修饰代码块

分别说明 synchronized(this), synchronized(Obj), synchronized(A.class) 三种使用情景。

synchronized(this){}

此用法效果和 synchronized 修饰方法的效果完全一样,只不过它是写在方法里面的!如下面例子的写法。

但这里要说明下 this 的含义,此 this 不是指类A,而是指类A的对象synchronized(this) 表示将本类的对象加锁同步!其原型应该是synchronized(Obj) 也就是下一个使用情景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class A {
public void a(){
synchronized(this){
int i=5;
while(i-->0){
System.out.println(Thread.currentThread()+":a:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

synchronized(Obj){}

这一代码块修饰法是最特别也是与其他写法有最大不同的地方!

代码中 Obj 是一个对象,当是当前方法类本身的对象时,其用法就和synchronized(this) 完全一样,那当然也和 synchronized 修饰方法的情况用法一样。对于 synchronized(this) 和此写法关系,通常认为 synchronized(this)synchronized(Obj) 的一个特例

Obj 指代其他对象 ( 非本类对象 ) 时,那么它只限制 Obj 所指代的对象同步。此种情况的用法填补了前面几种方法 ( 包括 synchronized(A.class) ) 的缺陷,下面详细说明:

synchronized 修饰方法和修饰本类对象的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class B {
public synchronized void a(){
System.out.println("Bclass:a!before");
SynObject so=new SynObject();
so.testsy();
System.out.println("Bclass:a!after");
}
public synchronized void b(){
}
public synchronized void c(){
}
}
class SynObject{
public void testsy(){
System.out.println("SynObject:testsy!");
}
}

B 类里有多个同步方法 a, b, c 等,并且在 a 方法里 SynObject so=new SynObject(); 实例化了类 SynObject 得到 so 对象,并调用了 so.testsy();。那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。

如果这时用 synchronized(Obj) 来修饰代码块,修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class B {
public void a(){
System.out.println("Bclass:a!before");
SynObject so=new SynObject();
synchronized(so){//只修饰so对象
so.testsy();
}
System.out.println("Bclass:a!after");
}
public synchronized void b(){
}
public synchronized void c(){
}
}
class SynObject{
public void testsy(){
System.out.println("SynObject:testsy!");
}
}

那么这个方法加锁的对象是 so 这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

另外上面这个例子里面,B 类 a 方法中的第一句 print 语句在 synchronized(Obj) 代码块之前,也就是说外界阻塞也会在这句 print 语句打印完后才会阻塞进行同步。这我们应该感觉到与 synchronized 修饰方法的区别,synchronized 修饰方法会将整个方法内部所有语句都阻塞,而 synchronized(Obj) 只阻塞我们需要同步的类对象处!

synchronized(A.class){}

此用法实际上是为静态方法实现 synchronized 代码块而使用的,直接给大家一个类比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class C {
public synchronized void a1(){
}
public void a2(){
synchronized(this){
//...
}
}
public synchronized static void b1(){
}
public static void b2(){
synchronized(C.class){
//...
}
}
public void c(){
synchronized(C.class){
//...
}
}
}

a1 方法与 a2 方法效果一样,而 b1, b2, c 的效果也是一样的,

对于 synchronized(C.class) 只要在方法中申名,不管方法前面加没加 static 其意义都是和加了 static 一样的!

普通方法通过 synchronized(C.class) 可以转化为静态同步方法,而静态方法不能通过 synchronized(this) 转化为普通同步方法。

普通方法的同步与静态方法的同步互不冲突。也就是说 Thread1 获得该类的一个对象并调用了 a1 方法,Thread2 线程则不能调用 a1, a2 方法,但是可以调用 b1, b2, c 方法!

总结

一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得 A 类的对象锁时,它同时也可以获得 B 类的对象锁。

判断哪些方法可以调用,哪些资源可以访问主要看 synchronized 修饰的是什么!我画了张图仅供参考:

synchronized分类图

如果还是很难理解,http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html 这里面有例子大家可以参考看下!

苟且一下