Java synchronized关键字学习一

来源:互联网 发布:java的设计模式有哪些 编辑:程序博客网 时间:2024/06/05 22:46

介绍

synchronized是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。

调用关键字synchronized声明的方法一定是排队运行的。还需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,若不是共享资源则根本不需要同步的必要。在多个线程时,出现了数据交叉情况,一个线程获取结果时,另一个线程已经更改了却不自知,就会出现脏读(dirtyRead)现象。对于脏读可以通过synchronized关键字解决。

当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其它的非synchronized同步方法。 
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,就必须等A线程将X方法执行完,也就是释放对象锁后才可以调用,这时A线程已经执行了一个完整的任务。

synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象中其他方法的synchronized锁时,是可以再次得到该对象的锁的。这就证明在一个synchronized方法/块的内部调用本类的其它synchronized方法/块时,是永远可以得到锁的

package synchronizedLock;
 
public class Service {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2() {
System.out.println("service2");
service3();
}
synchronized public void service3() {
System.out.println("service3");
}
}
 
 
package synchronizedLock;
 
public class MyThread extends Thread{
@Override
public void run() {
Service service = new Service();
service.service1();
}
 
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
}
}

运行结果:

service1
service2
service3

从程序的运行结果可以更加深刻的理解“可重入锁”的概念:自身可以再次获取自己的内部锁。该锁业支持在父子类继承的环境中,也就是,当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

注意: 
1.线程出现异常,锁会自动释放。在两个线程时,其中一个获取到了锁,另一个等待,获得锁的线程若发生异常,则锁被释放,等待的线程将会获取到锁,正常执行。 
2.同步不具有继承性,也就是当父类方法被synchronized修饰时,子类继承该父类,但是子类中调用父类方法的所在方法并没有被synchronized关键字修饰时,就是非同步调用的。

synchronized同步语句块

当一个线程调用同步方法执行一个长时间的任务,那么其他线程则必须等待比较长的时间,这就是用synchronized关键字声明方法存在的弊端,此时就可以使用synchronized同步语句块来解决。synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。

不在synchronized块中就是异步执行,在synchronized块中就是同步执行; 在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。 
用代码测试一下,该观点:

package synchronizedLock;
 
public class ObjectService {
public void serviceMethodA() {
try {
synchronized(this) {
System.out.println("A begin time="+ System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end end="+ System.currentTimeMillis());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public void serviceMethodB(){
synchronized (this) {
System.out.println("B begin time="+System.currentTimeMillis());
System.out.println("B end end="+System.currentTimeMillis());
}
}
}
 
package synchronizedLock;
 
public class ThreadA extends Thread {
private ObjectService service;
public ThreadA(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodA();
}
}
 
 
package synchronizedLock;
 
public class ThreadB extends Thread{
private ObjectService service;
public ThreadB(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodB();
}
}
 
 
package synchronizedLock;
 
public class MainTest {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
 
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}

根据代码可以先进行反证下,若有多个“对象监视器”,则当第一个线程a调用serviceMethodA方法执行到睡眠时,b线程必然会执行serviceMethodB方法。那么实际运行结果是什么呢?如下:

A begin time=1488787827707
A end end=1488787829707
B begin time=1488787829707
B end end=1488787829707

由运行结果可以发现,直到a线程睡眠所在synchronized代码块执行完毕时,b线程才开始执行。这就验证了上边说的,同一个object中的synchronized使用一个“对象监视器”。

synchronized(非this对象)

那么此时可能会有人问,synchronized同步代码块只能支持this这一种“对象监视器”吗?答案是否定的。 
其实Java还支持对“任意对象”作为“对象监视器”来实现同步的功能,该“任意对象”大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。此时就可以使用锁非this对象来使得一个类中的多个synchronized方法与同步方法异步执行,不与其他this锁争抢锁,以提高运行效率。 
将上步的ObjectService代码进行修改如下:

public class ObjectService {
public void serviceMethodA() {
try {
synchronized(this) {
System.out.println("A begin time="+ System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end end="+ System.currentTimeMillis());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public void serviceMethodB(){
String str2 = new String();
synchronized (str2) {
System.out.println("B begin time="+System.currentTimeMillis());
System.out.println("B end end="+System.currentTimeMillis());
}
}
}

运行结果:

A begin time=1488789210581
B begin time=1488789210581
B end end=1488789210582
A end end=1488789212581

可见,同一个类中使用多个synchronized时,使用多个不同的“对象监视器”。运行结果就是异步调用,就会交叉运行;若均使用同一个“对象监视器”,则就同步执行、阻塞。

总结以上过程: 
多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(…)同步代码块时,调用的效果就是按顺序执行,也就是同步的、阻塞的。 
不管是synchronized同步方法还是synchronized(…)同步代码块都遵循以下几点:

       1. 对其他synchronized同步方法或synchronized(this) 同步代码块调用呈阻塞状态;

       2. 同一时间只有一个线程可以执行synchronized同步方法或synchronized(this)同步代码块中的代码;

       3. 在同一个类中,存在多个对象监视器,也就是采用了多个synchronized(非this对象)同步代码块,并且非this对象不一致则可以异步执行。

0 0
原创粉丝点击