当前位置: 移动技术网 > IT编程>开发语言>Java > synchronized同步方法

synchronized同步方法

2018年12月10日  | 移动技术网IT编程  | 我要评论
当多个线程同时访问同一对象中的实例变量时,就会出现非线程安全性,从而导致脏读取,即所检索的数据发生更改。线程安全性意味着所获得的实例变量的值是同步的。 方法内的变量是线程安全的 方法中的变量是线程安全的。非线程安全问题存在于实例变量中。如果它是一个方法中的私有变量,就不会有非线程安全问题。实例如下: ...

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

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

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

 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方法。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网