欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

synchronized同步方法

程序员文章站 2022-08-20 17:44:28
当多个线程同时访问同一对象中的实例变量时,就会出现非线程安全性,从而导致脏读取,即所检索的数据发生更改。线程安全性意味着所获得的实例变量的值是同步的。 方法内的变量是线程安全的 方法中的变量是线程安全的。非线程安全问题存在于实例变量中。如果它是一个方法中的私有变量,就不会有非线程安全问题。实例如下: ......

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

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

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

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