深入理解Java-synchronized关键字
理解Java中的synchronized关键字
问题思考:我们可以带着问题理解 synchronized
问题1:
有如下一个类A
class A {
public synchronized void a() {
}
public synchronized void b() {
}
}
然后创建两个对象
A a1 = new A();
A a2 = new A();
然后在两个线程中并发访问如下代码:
Thread1 Thread2
a1.a(); a2.a();
请问二者能否构成线程同步?
问题2:
如果A的定义是下面这种呢? 可以构成线程同步
class A {
public static synchronized void a() {
}
public static synchronized void b() {
}
}
1.理解synchronized 的含义
synchronized 是Java多线程中的同步锁机制可以对方法、对象或代码块进行加锁,保证在同一时间只有一个线程操作对应的资源,避免多线程同时访问相同的资源发生冲突。简单来说,synchronized 就是用来控制线程同步的,被加锁的代码块,不能被多个线程同时执行。
2.synchronized 主要修饰的对象有以下三种
1. 修饰普通方法。一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象,多个线程并发访问同一个对象的该方法可以保证线程同步,若多个线程不同对象访问该方法无法保证线程同步。
举个例子:
public class Syncthread implements Runnable{
private static int count;
public Syncthread() {
count = 0;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
test(name);
}
public synchronized void test(String a) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + count++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1> 多个线程同一个对象访问test方法
Syncthread syncthread = new Syncthread();
Thread thread1 = new Thread(syncthread, "a");
Thread thread2 = new Thread(syncthread, "b");
thread1.start();
thread2.start();
输出结果为:
a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9
从结果可以看出当thread1 执行完后,才会执行thread2 ,synchronized修饰的方法,同一时间只能有一个线程进行访问
2> 另一种情况,多个线程不同对象访问test 方法
Syncthread syncthread = new Syncthread();
Syncthread syncthread2 = new Syncthread();
Thread thread1 = new Thread(syncthread, "a");
Thread thread2 = new Thread(syncthread2, "b");
thread1.start();
thread2.start();
输出结果为:
a:0
b:1
a:2
b:2
b:3
a:3
a:4
b:4
b:5
a:6
从结果看出,test方法同一时间被多个线程同时访问,多个线程不同对象访问被加锁的方法,是无法保证线程同步的。
注意:
1.在定义接口方法时不能使用synchronized关键字。
2.造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
问题1的答案可以呼之欲出了,是不能构成线程同步的。
2.修饰静态方法。由于静态方法是类方法,所以这种情况加锁的是这个类对象,这样多个线程不同对象访问该静态方法,也是可以保证同步的。
看一下代码,我们将test方法加上static关键字
public synchronized static void test(String a)
Syncthread syncthread = new Syncthread();
Syncthread syncthread2 = new Syncthread();
Thread thread1 = new Thread(syncthread, "a");
Thread thread2 = new Thread(syncthread2, "b");
thread1.start();
thread2.start();
输出结果为:
a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9
从结果看出,test方法同一时间被多个线程同时访问,多个线程不同对象访问被加锁的静态方法,是可以保证线程同步的。
问题2的答案是:可以线程同步。
3.修饰代码块。其中普通代码块 如synchronized(obj) obj可以是类中的属性也可以是当前对象,它的同步效果和修饰普通方法是一样的;synchronized(obj.class)静态代码块它的同步效果和修饰静态方法类似。
用代码解释更容易理解,看一下代码
1>synchronized(obj.class) 修饰代码块,我们将test 方法改为
public void test(String a) {
synchronized (Syncthread.class) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + count++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在多个线程同一个对象,或者多个线程不同对象的情况下。
看下输出结果:
a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9
果然,两种情况都可以保证线程同步,和修饰静态法的效果是一样的。
2> synchronized(obj) 修饰代码块,将test方法改为
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + count++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在多个线程同一个对象情况下,输出结果为:
a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9
可以看出,在多个线程同一个对象情况下,可以保证线程同步。
在多个线程,不同对象的情况下,输出结果为:
b:0
b:1
a:2
a:3
b:4
b:5
a:6
a:7
b:8
多个线程,不同对象的情况下,不能保证线程同步,和修饰普通方法的效果是一样的。
这里在说一下,synchronized(obj) 修饰代码块
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁。如:
private byte[] lock = new byte[0];//特殊的变量
public void method(){
synchronized(lock){
//同步代码块
}
}
说明:零长度的byte数组对象创建起来比任何对象都要好,只需要3条操作码,而object 则需要7行操作码
总结
synchronized 方法控制方位较大,它会同步对象中所有的synchronized方法的代码。
synchronized 代码块控制方位较小,它只会控制代码块中的代码,而位于代码块之外的代码是可以被多个线程访问的。
实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。