初学Java多线程:使用Synchronized块同步方法

Posted

tags:

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

参考技术A

  synchronized关键字有两种用法 第一种就是在《使用Synchronized关键字同步类方法》一文中所介绍的直接用在方法的定义中 另外一种就是synchronized块 我们不仅可以通过synchronized块来同步一个对象变量 也可以使用synchronized块来同步类中的静态方法和非静态方法

  synchronized块的语法如下

   public void method()

  

       … …

       synchronized(表达式)

      

           … …

      

  

   一 非静态类方法的同步

  从《使用Synchronized关键字同步类方法》一文中我们知道使用synchronized关键字来定义方法就会锁定类中所有使用synchronzied关键字定义的静态方法或非静态方法 但这并不好理解 而如果使用synchronized块来达到同样的效果 就不难理解为什么会产生这种效果了 如果想使用synchronized块来锁定类中所有的同步非静态方法 需要使用this做为synchronized块的参数传入synchronized块国 代码如下

  通过synchronized块同步非静态方法

   public class SyncBlock

  

         public void method ()

        

             synchronized(this)  // 相当于对method 方法使用synchronized关键字

            

                 … …

            

        

         public void method ()

        

             synchronized(this)  // 相当于对method 方法使用synchronized关键字

            

                 … …

            

        

         public synchronized void method ()

        

             … …

        

    

  在上面的代码中的method 和method 方法中使用了synchronized块 而第 行的method 方法仍然使用synchronized关键字来定义方法 在使用同一个SyncBlock类实例时 这三个方法只要有一个正在执行 其他两个方法就会因未获得同步锁而被阻塞 在使用synchronized块时要想达到和synchronized关键字同样的效果 必须将所有的代码都写在synchronized块中 否则 将无法使当前方法中的所有代码和其他的方法同步

  除了使用this做为synchronized块的参数外 还可以使用SyncBlock this作为synchronized块的参数来达到同样的效果

  在内类(InnerClass)的方法中使用synchronized块来时 this只表示内类 和外类(OuterClass)没有关系 但内类的非静态方法可以和外类的非静态方法同步 如在内类InnerClass中加一个method 方法 并使method 方法和SyncBlock的三个方法同步 代码如下

  使内类的非静态方法和外类的非静态方法同步

   public class SyncBlock

  

       … …

       class InnerClass

      

           public void method ()

          

               synchronized(SyncBlock this)

              

                   … …

              

          

      

       … …

  

  在上面SyncBlock类的新版本中 InnerClass类的method 方法和SyncBlock类的其他三个方法同步 因此 method method method 和method 四个方法在同一时间只能有一个方法执行

  Synchronized块不管是正常执行完 还是因为程序出错而异常退出synchronized块 当前的synchronized块所持有的同步锁都会自动释放 因此 在使用synchronized块时不必担心同步锁的释放问题

   二 静态类方法的同步

  由于在调用静态方法时 对象实例不一定被创建 因此 就不能使用this来同步静态方法 而必须使用Class对象来同步静态方法 代码如下

  通过synchronized块同步静态方法

   public class StaticSyncBlock

    

         public static void method ()

        

             synchronized(StaticSyncBlock class)

            

                 … …

            

        

         public static synchronized void method ()

        

             … …

        

    

  在同步静态方法时可以使用类的静态字段class来得到Class对象 在上例中method 和method 方法同时只能有一个方法执行 除了使用class字段得到Class对象外 还可以使用实例的getClass方法来得到Class对象 上例中的代码可以修改如下

  使用getClass方法得到Class对象

   public class StaticSyncBlock

  

       public static StaticSyncBlock instance;

       public StaticSyncBlock()

      

           instance = this;

      

       public static void method ()

      

          synchronized(instance getClass())

         

  

         

      

  

  

  在上面代码中通过一个public的静态instance得到一个StaticSyncBlock类的实例 并通过这个实例的getClass方法得到了Class对象(一个类的所有实例通过getClass方法得到的都是同一个Class对象 因此 调用任何一个实例的getClass方法都可以) 我们还可以通过Class对象使不同类的静态方法同步 如Test类的静态方法method和StaticSyncBlock类的两个静态方法同步 代码如下

  Test类的method方法和StaticSyncBlock类的method method 方法同步

   public class Test

  

       public static void method()

      

           synchronized(StaticSyncBlock class)

          

  

          

      

  

lishixinzhi/Article/program/Java/gj/201311/27374

java多线程——锁机制synchronized(同步方法)

synchronized

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

                                                        ——以上来源百度百科

一、方法内的变量为线程安全

“非线程安全”的问题存在于“实例变量”中,如果是方法内部的私有变量,则不会存在“非线程安全”问题,所得结果就是“线程安全”的了。

 MyService类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     public void add(String name) {
 5         try {
 6             int num = 0;
 7             if (name.equals("a")) {
 8                 num = 100;
 9                 System.out.println("a is over");
10                 Thread.sleep(1000);
11             } else {
12                 num = 200;
13                 System.out.println("b is over");
14             }
15             System.out.println(name + " num = " + num);
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }
19     }
20 }

线程类A

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadA extends Thread {
 4     private MyService service;
 5 
 6     public ThreadA(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.add("a");
14     }
15 }

线程类B

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadB extends Thread {
 4     private MyService service;
 5 
 6     public ThreadB(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.add("b");
14     }
15 }

运行类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyService service = new MyService();
 6         ThreadA threadA = new ThreadA(service);
 7         threadA.start();
 8         ThreadB threadB = new ThreadB(service);
 9         threadB.start();
10     }
11 }

结果

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

从运行结果来看,方法中的变量不存在非线程安全的问题,永远都是线程安全的,这事方法内部的变量是私有的特性造成的。

二、实例变量非线程安全

 MyService类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     private int num = 0;
 5     public void add(String name) {
 6         try {
 7             
 8             if (name.equals("a")) {
 9                 num = 100;
10                 System.out.println("a is over");
11                 Thread.sleep(1000);
12             } else {
13                 num = 200;
14                 System.out.println("b is over");
15             }
16             System.out.println(name + " num = " + num);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20     }
21 }

线程类和运行类同上,运行结果

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

产生这个结果的原因是两个线程同事访问同一个没有同步的方法,如果两个对象同时操作对象中的实例变量,可能会造成非线程安全的问题

最简单的解决方案是在方法的前面加个synchronized同步锁

MyService类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     private int num = 0;
 5     synchronized public void add(String name) {
 6         try {
 7             
 8             if (name.equals("a")) {
 9                 num = 100;
10                 System.out.println("a is over");
11                 Thread.sleep(1000);
12             } else {
13                 num = 200;
14                 System.out.println("b is over");
15             }
16             System.out.println(name + " num = " + num);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20     }
21 }

线程类和运行类同上,运行结果

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

在两个线程访问同一个对象中的同步方法时一定是线程安全的,上面的代码由于时同步访问,所以先打印出a,然后在打印出b

改一下运行类,其他类同上

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyService serviceA = new MyService();
 6         MyService serviceB = new MyService();
 7         ThreadA threadA = new ThreadA(serviceA);
 8         threadA.start();
 9         ThreadB threadB = new ThreadB(serviceB);
10         threadB.start();
11     }
12 }

结果

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

这是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的形式来执行的。

因为创建了两个业务对象,在系统中产生了两个锁,所以运行结果是异步的。

关键字synchronized所取得的锁都是对象锁,而不是把一段代码或者方法当作锁。

My Service类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     synchronized public void methodA() {
 5         try {
 6             System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
 7             Thread.sleep(3000);
 8             System.out.println("end methodA time = " + System.currentTimeMillis());
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12     }
13     
14     public void methodB() {
15         try {
16             System.out.println("begin methodB threadName = " + Thread.currentThread().getName());
17             Thread.sleep(3000);
18             System.out.println("end methodB time = " + System.currentTimeMillis());
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22     }
23 }

线程类A

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadA extends Thread {
 4     private MyService service;
 5 
 6     public ThreadA(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.methodA();
14     }
15 }

线程类B

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadB extends Thread {
 4     private MyService service;
 5 
 6     public ThreadB(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.methodB();
14     }
15 }

运行类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyService service = new MyService();
 6         ThreadA threadA = new ThreadA(service);
 7         threadA.start();
 8         ThreadB threadB = new ThreadB(service);
 9         threadB.start();
10     }
11 }

结果

1 begin methodA threadName = Thread-1
2 begin methodB threadName = Thread-2
3 end methodB time = 1458400534384
4 end methodA time = 1458400534384

在My Service的methodB前面也加上关键字synchronized

My Service类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     synchronized public void methodA() {
 5         try {
 6             System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
 7             Thread.sleep(3000);
 8             System.out.println("end methodA time = " + System.currentTimeMillis());
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12     }
13     
14     synchronized public void methodB() {
15         try {
16             System.out.println("begin methodB threadName = " + Thread.currentThread().getName());
17             Thread.sleep(3000);
18             System.out.println("end methodB time = " + System.currentTimeMillis());
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22     }
23 }

结果

1 begin methodA threadName = Thread-1
2 end methodA time = 1458400619034
3 begin methodB threadName = Thread-2
4 end methodB time = 1458400622035

对比上面两次代码的运行结果。

在第一次运行时,当A线程先持有My Service的同步锁时,B线程可以已异步的方式去调用My Service对象中的非synchronized方法。

在第二次运行时,当A线程先持有My Service的同步锁时,当B想调用My Service对象中的synchronized,则需要先等A释放对象锁。

所以,synchronized锁住的时对象,而不是其他的一些东西。

三、synchronized锁重入

当一个线程获得一个对象锁后,再次请求此对象锁时是可以再次获得此对象锁的

My Service类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     synchronized public void methodA() {
 5         System.out.println("methodA");
 6         methodB();
 7     }
 8     
 9     synchronized public void methodB() {
10         System.out.println("methodB");
11         methodC();
12     }
13     
14     synchronized public void methodC() {
15         System.out.println("methodC");
16     }
17 }

线程类

1 package com.mythread.www.day8.testSyn.ep1;
2 
3 public class MyThread extends Thread {
4     @Override
5     public void run() {
6         MyService myService = new MyService();
7         myService.methodA();
8     }
9 }

运行类

1 package com.mythread.www.day8.testSyn.ep1;
2 
3 public class Run {
4     public static void main(String[] args) {
5         MyThread myThread = new MyThread();
6         myThread.start();
7     }
8 }

结果

1 methodA
2 methodB
3 methodC

自己还可以重新获得自己的内部锁,如果不可以的话,上面的这个Demo则会造成死锁现象

Main类

 1 package com.weishiyao.learn.day4.testThread;
 2 
 3 public class Main {
 4     public int i = 10;
 5     synchronized public void operateIMainMethod() {
 6         try {
 7             i--;
 8             System.out.println("main print i=" + i);
 9             Thread.sleep(100);
10         } catch (Exception e) {
11             e.printStackTrace();
12         }
13     }
14 }

Sub类

 1 package com.weishiyao.learn.day4.testThread;
 2 
 3 public class Sub extends Main{
 4     synchronized public void operateISubMethod() {
 5         try {
 6             while (i > 0) {
 7                 i--;
 8                 System.out.println("sub print i=" + i);
 9                 Thread.sleep(100);
10                 this.operateIMainMethod();
11             }
12         } catch (Exception e) {
13             e.printStackTrace();
14         }
15     }
16 }

线程类

1 package com.weishiyao.learn.day4.testThread;
2 
3 public class MyThread  extends Thread{
4     @Override
5     public void run() {
6         Sub sub = new Sub();
7         sub.operateISubMethod();
8     }
9 }

运行类

1 package com.weishiyao.learn.day4.testThread;
2 
3 public class Run {
4     public static void main(String[] args) {
5         MyThread t = new MyThread();
6         t.run();
7     }
8 }

结果

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

当存在父子类继承关系时,子类完全可以通过父类“可重入锁”调用父类的同步方法

 

以上是关于初学Java多线程:使用Synchronized块同步方法的主要内容,如果未能解决你的问题,请参考以下文章

java 多线程9 : synchronized锁机制 之 代码块锁

创作赢红包Java多线程:synchronized锁方法块

JAVA多线程synchronized详解

java多线程——锁机制synchronized(同步方法)

java多线程笔记--synchronized类,对象,方法,代码块

Java多线程-synchronized同步方法及同步块简述