synchronized同步方法

Posted aishangjava

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized同步方法相关的知识,希望对你有一定的参考价值。

当多个线程同时访问同一对象中的实例变量时,就会出现非线程安全性,从而导致脏读取,即所检索的数据发生更改。线程安全性意味着所获得的实例变量的值是同步的。

方法内的变量是线程安全的

方法中的变量是线程安全的。非线程安全问题存在于实例变量中。如果它是一个方法中的私有变量,就不会有非线程安全问题。实例如下:

 1 class HasMethodPrivateNum {
 2     public void addI(String username){
 3         try {
 4             int num=0;
 5             if(username.equals("a")){
 6                 num=100;
 7                 System.out.println("a set over");
 8                 Thread.sleep(2000);
 9             }else{
10                 num=200;
11                 System.out.println("b set over");
12             }
13             System.out.println(username+" num = "+num);
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17     }
18 }
19 
20 class ThreadA extends Thread {
21     private HasMethodPrivateNum numRef;
22     public ThreadA(HasMethodPrivateNum numRef){
23         super();
24         this.numRef=numRef;
25     }
26 
27     @Override
28     public void run() {
29         super.run();
30         numRef.addI("a");
31     }
32 }
33 
34 class ThreadB extends Thread {
35     private HasMethodPrivateNum numRef;
36     public ThreadB(HasMethodPrivateNum numRef){
37         super();
38         this.numRef=numRef;
39     }
40 
41     @Override
42     public void run() {
43         super.run();
44         numRef.addI("b");
45     }
46 }
47 
48 public class Run {
49     public static void main(String[] args) {
50         HasMethodPrivateNum numRef=new HasMethodPrivateNum();
51         ThreadA threadA=new ThreadA(numRef);
52         threadA.start();
53         ThreadB threadB=new ThreadB(numRef);
54         threadB.start();
55     }
56 }

输出结果:

a set over
b set over
b num = 200
a num = 100

可以看出,该方法中的变量不存在非线性安全问题,并且是线程安全的。

实例变量非线程安全

实例变量是非线程安全的。如果多个线程联合访问对象中的实例变量,则可能出现非线程安全性问题。如果线程访问的对象中有多个实例变量,则运行结果可能会交叉,如果只有一个实例变量,则可能存在覆盖。在这种情况下,您需要将synchronized关键字添加到操作实例变量的方法中。当多个线程访问同一对象中的同步方法时,它必须是线程安全的。

修改上面的代码以将变量作为类中的成员变量放在第一类的addI()方法中:

 1 class HasSelfPrivateNum {
 2     private int num=0;
 3     synchronized public void addI(String username){
 4         try {
 5             if(username.equals("a")){
 6                 num=100;
 7                 System.out.println("a set over");
 8                 Thread.sleep(2000);
 9             }else{
10                 num=200;
11                 System.out.println("b set over");
12             }
13             System.out.println(username+" num = "+num);
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17     }
18 }

测试结果如下:

1 a set over
2 b set over
3 b num = 200
4 a num = 200

可以发现,得到的结果是存在线程安全问题的。当为addI()方法加上synchronized关键字之后,测试结果如下:

1 a set over
2 a num = 100
3 b set over
4 b num = 200

可以发现,不存在线程安全问题了。

synchronized关键字获取所有对象锁的锁,而不是使用一段代码或方法作为锁。当多个线程访问同一个对象时,哪个线程首先使用关键字执行方法,哪个线程持有方法所属对象的锁,而其他线程只能等待。但是,如果多个线程访问多个对象,JVM将创建多个锁。

当对象分别具有同步方法a和异步方法b、线程A和线程B访问方法a和方法B时,线程A首先持有对象的锁,但是线程B可以异步调用对象的异步方法B。但是如果两个方法都是同步的,那么当A访问方法a时,它已经持有对象的Lock锁。当B线程调用对象的另一个同步方法时,它也需要等待,即同步。示例代码如下:

 1 class MyObject {
 2     synchronized public void methodA(){
 3         try {
 4             System.out.println("begin methodA in thread: "+Thread.currentThread().getName());
 5             Thread.sleep(5000);
 6             System.out.println("end methodA in time:"+System.currentTimeMillis());
 7         } catch (InterruptedException e) {
 8             e.printStackTrace();
 9         }
10     }
11 
12     public void methodB(){
13         try {
14             System.out.println("begin methodB in thread: "+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
15             Thread.sleep(5000);
16             System.out.println("end methodB");
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20     }
21 
22 }
23 
24 class ThreadA extends Thread{
25     private MyObject object;
26     public ThreadA(MyObject object){
27         super();
28         this.object=object;
29     }
30 
31     @Override
32     public void run() {
33         super.run();
34         object.methodA();
35     }
36 }
37 
38 class ThreadB extends Thread{
39     private MyObject object;
40     public ThreadB(MyObject object){
41         super();
42         this.object=object;
43     }
44 
45     @Override
46     public void run() {
47         super.run();
48         object.methodB();
49     }
50 }
51 
52 public class Run {
53     public static void main(String[] args) {
54         MyObject object=new MyObject();
55         ThreadA a=new ThreadA(object);
56         a.setName("A");
57         ThreadB b=new ThreadB(object);
58         b.setName("B");
59         a.start();
60         b.start();
61     }
62 }

测试结果:

begin methodA in thread: A
begin methodB in thread: B time:1544263806800
end methodB
end methodA in time:1544263811800

如您所见,线程A首先获得对象对象的锁,但是线程B仍然异步调用异步方法。将同步关键字添加到MultB()之后,测试结果如下:

begin methodA in thread: A
end methodA in time:1544264023516
begin methodB in thread: B time:1544264023516
end methodB

可以看到,A线程先得到object的锁,B线程如果此时调用objcet中的同步方法需要等待。

脏读

同步关键字可用于调用相同方法时同步多个线程。虽然分配是同步的,但是当取值时,即当读取实例变量时,该值已被其他线程更改,可能会发生脏读取。因此,还需要同步方法来读取数据。

锁重入

同步锁重入:当使用synchronized时,当线程获得对象锁时,当再次请求对象锁时,它可以再次获得对象的锁。也就是说,他们可以再次获得自己的内部锁。当线程获取对象的锁时,不会释放锁,并且它希望获取对象的锁。如果不允许锁重新进入,就会发生死锁。示例代码:

 1 class Service {
 2     synchronized public void service1(){
 3         System.out.println("service1");
 4         service2();
 5     }
 6 
 7     synchronized public void service2(){
 8         System.out.println("service2");
 9         service3();
10     }
11 
12     synchronized public void service3(){
13         System.out.println("service3");
14     }
15 }
16 class MyThread extends Thread{
17     @Override
18     public void run() {
19         Service service=new Service();
20         service.service1();
21     }
22 }
23 public class Run {
24     public static void main(String[] args) {
25         MyThread t=new MyThread();
26         t.start();
27     }
28 }

测试结果:

service1
service2
service3

父子继承环境中还支持可重入锁。当存在父子类继承关系时,子类可以通过“可重新引入的锁”调用父类的同步方法。示例代码如下:

class Main {
    public int i=10;
    synchronized public void operateIMainMethod(){
        try {
            i--;
            System.out.println("main print i = "+i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Sub extends Main{

    public synchronized void operateISubMethod() {
        try {
            while(i>0){
                i--;
                System.out.println("sub print i="+i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        Sub sub=new Sub();
        sub.operateISubMethod();
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread t=new MyThread();
        t.start();
    }
}

测试结果为:

sub print i=9
main print i = 8
sub print i=7
main print i = 6
sub print i=5
main print i = 4
sub print i=3
main print i = 2
sub print i=1
main print i = 0

当线程执行异常代码时,它所持有的锁会自动释放。

同步不具有继承性

如果父类的方法是同步方法,但是子类在不添加同步关键字的情况下重写方法,那么子类的方法在调用子类的方法时仍然不是同步方法,并且需要将同步关键字添加到子类的E方法。

以上是关于synchronized同步方法的主要内容,如果未能解决你的问题,请参考以下文章

静态方法内的同步块将获取类级别锁或对象级别锁

JAVA高并发程序设计学习:Synchronized同步代码块具体使用方法

synchronized将任意对象作为对象监视器

Java的synchronized的同步代码块和同步方法的区别

关于Synchronized

关于Synchronized