在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
- 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
- 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
- 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
- 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
- 以上规则对其它对象锁同样适用.
先看一段代码:
class SynchronizedDemos{
private int index = 0;
private static int index2 = 0;
//non-static synchronized method, lock the 'this' Object.
public synchronized void synchronizedDemo1(){
index ++;
System.out.println(new Date().getTime() + ":" + Thread.currentThread().getName()
+ "--index : " + index);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//static synchronized method, lock the Class
public static synchronized void synchronizedDemo2(){
index2 ++;
System.out.println(new Date().getTime() + ":" + Thread.currentThread().getName()
+ "--index2 : " + index2);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void synchronizedDemo3(){
//synchronized block, lock the 'this' Object
synchronized (this) {
index ++;
System.out.println(new Date().getTime() + ":" + Thread.currentThread().getName()
+ "--synchronized block methods and index : " + index);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
当我们执行以下代码时,我们可以看到如下结果:
public static void main(String[] args) {
SynchronizedDemos sdm1 = new SynchronizedDemos();
SynchronizedDemos sdm2 = new SynchronizedDemos();
Thread t1 =new Thread(()->{
sdm1.synchronizedDemo1();
});
Thread t2 =new Thread(()->{
sdm1.synchronizedDemo3();
});
t1.start();
t2.start();
}
执行结果如下:
1515478807786:Thread-0--synchronized block methods and index : 1
1515478812791:Thread-1--index : 2
sdm1
对象的两个不同的同步方法,由于同时对sdm1
加锁,导致两个线程中的这两个方法不能同时执行,后面的线程必须等待前面的线程执行完成并释放Monitor才能得以执行。下面的代码也展示了同样的道理:
public static void main(String[] args) {
SynchronizedDemos sdm1 = new SynchronizedDemos();
SynchronizedDemos sdm2 = new SynchronizedDemos();
Thread t1 =new Thread(()->{
sdm1.synchronizedDemo1();
});
Thread t2 =new Thread(()->{
sdm1.synchronizedDemo1();
});
t1.start();
t2.start();
}
执行结果如下:
1515478991269:Thread-0--index : 1
1515478996273:Thread-1--index : 2
当然了,如果两个不同的对象,非static的synchronized方法执行互相不会影响。如下所示:
public static void main(String[] args) {
SynchronizedDemos sdm1 = new SynchronizedDemos();
SynchronizedDemos sdm2 = new SynchronizedDemos();
Thread t1 =new Thread(()->{
sdm1.synchronizedDemo1();
});
Thread t2 =new Thread(()->{
sdm2.synchronizedDemo1();
});
t1.start();
t2.start();
}
执行结果如下:
1515479257259:Thread-0--index : 1
1515479257259:Thread-1--index : 1
可以看出,这两个方法几乎是同时执行的,因为此时两个线程分别持有不同对象的Monitor,彼此之间不会同步,不受影响。
而当synchronized用来修饰静态方法时,当前类的Class对象会被Monitor锁住,而不是锁住对象,可以看下面这段代码:
public static void main(String[] args) {
SynchronizedDemos sdm1 = new SynchronizedDemos();
SynchronizedDemos sdm2 = new SynchronizedDemos();
Thread t1 =new Thread(()->{
sdm1.synchronizedDemo2();
});
Thread t2 =new Thread(()->{
sdm2.synchronizedDemo2();
});
t1.start();
t2.start();
}
执行结果如下:
1515488168001:Thread-0--index2 : 1
1515488173004:Thread-1--index2 : 2
可以看出,两个线程中的sdm1
和sdm2
是不同的对象实体,但是在执行静态方法时,线程2被线程1阻塞了,直到释放了锁。因为此时,Monitor锁住的是SynchronizedDemos
的类的对象。
使用synchronized关键字,这种机制也称为互斥锁机制,这就意味着同一时刻只能有一个线程能够获取到锁,获得的锁也被称为互斥锁。其他需要获取该互斥锁的线程只能被阻塞,直到获取到该锁的线程释放锁。在Java中,每个类都有一个内置锁,之所以如此,是因为Java并发专家认为这样可以避免显式创建锁。
对于使用synchronized关键字实现的同步机制由如下几点补充说明:
- 如果同一个方法有多个线程访问,那么每个线程都有自己的线程拷贝(拷贝存储在工作内存中)
- 类的实例都有自己的对象锁,如果一个线程成功获取到该实例的对象锁那么当其他线程需要获取该实例的对象锁时,便需要阻塞等待,直到该实例的对象锁被成功释放。对象锁可以作用在同步方法或者同步代码块中
- 如果不同的线程访问的是不同实例的对象锁,那么不会互相阻塞,因为不同实例的对象锁是不同的
- 获得实例的对象锁的线程会让其他想要获取相同对象锁的线程阻塞在synchronized代码外,比如由两个synchronized方法a()、b(),那么如果线程A成功进入了同步方法a(),那么该线程便获取了该实例(比如实例obj)的对象锁,而如果其他线程想要执行另一个同步方法b(),就会阻塞在外面,因为a()和b()持有的都是对象obj的对象锁
- 持有一个实例的对象锁不会阻止该线程被置换出来,也不会阻塞其他线程执行非synchronized方法,因为非synchronized方法执行的时候不需要获取实例的对象锁
- 使用同步代码块的时候,括号的对象可以为任意Object实例,当为this时,指的是当前对象的对象锁
- 类锁主要用于控制对static成员变量的并发访问
- synchronized块(可以是同步方法或者同步代码块)是可重入的,每次重入会把锁的计数器加1,每次退出将计数器减1,当计数器的值为0的时候,锁便被释放了
- Java SE 1.6 为了减少获得锁和释放锁的性能消耗引入了偏向锁和轻量级锁,所以使用synchronized也没有那么重量级了
参考:
https://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html
https://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html
http://blog.csdn.net/u011116672/article/details/51050961
网友评论