• synchronize——对象锁和类锁


    最近在研究Java 多线程的只是,经常能看到synchronize关键字,以前只是一眼带过,没有细究,今天趁这个机会,整理下

    synchronize作为多线程关键字,是一种同步锁,它可以修饰以下几种对象:

    代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{ }里的代码,作用的对象是调用这个代码块的对象;

    方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象

    静态方法:作用的范围是整个静态方法,作用的对象是这个类的所有对象

    类:作用的范围是synchronize后面括号里的部分,作用的对象是这个类的所有对象

    一、synchronize关键字

    1.修饰方法

    1 synchronized public void getValue() {
    2     System.out.println("getValue method thread name="
    3             + Thread.currentThread().getName() + " username=" + username
    4             + " password=" + password);
    5 }

    2.修饰代码块

     1 public void serviceMethod() {
     2     try {
     3         synchronized (this) {
     4             System.out.println("begin time=" + System.currentTimeMillis());
     5             Thread.sleep(2000);
     6             System.out.println("end    end=" + System.currentTimeMillis());
     7         }
     8     } catch (InterruptedException e) {
     9         e.printStackTrace();
    10     }
    11 }

    3.修饰类

     1 public static void printA() {
     2         synchronized (Service.class) {
     3             try {
     4                 System.out.println("线程名称为:" + Thread.currentThread().getName()
     5                         + "在" + System.currentTimeMillis() + "进入printA");
     6                 Thread.sleep(3000);
     7                 System.out.println("线程名称为:" + Thread.currentThread().getName()
     8                         + "在" + System.currentTimeMillis() + "离开printA");
     9             } catch (InterruptedException e) {
    10                 e.printStackTrace();
    11             }
    12         }
    13 
    14     }

    二、Java中的对象锁和类锁

    借用网友对对象锁和类锁定义

    1 一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,
    2 在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁);
    3 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 
    4 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);
    5 线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。
    6 这样就保证了同步代码在统一时刻只有一个线程在执行。

    三、synchronize的用法与实例

    总结:

    synchronize修饰非静态方法、同步代码块的synchronize(this)和synchronize(非this对象)的用法锁的是对象,线程想要执行对应的同步代码,需要获得对象锁。

    synchronize修饰静态方法以及同步代码块的synchronize(类.class)用法锁是类,线程想要执行对应的同步代码,需要获得类锁。

    1.首先看下非线程安全例子

     1 public class Run {
     2 
     3     public static void main(String[] args) {
     4 
     5         HasSelfPrivateNum numRef = new HasSelfPrivateNum();
     6 
     7         ThreadA athread = new ThreadA(numRef);
     8         athread.start();
     9 
    10         ThreadB bthread = new ThreadB(numRef);
    11         bthread.start();
    12 
    13     }
    14 
    15 }
    16 
    17 class HasSelfPrivateNum {
    18 
    19     private int num = 0;
    20 
    21     public void addI(String username) {
    22         try {
    23             if (username.equals("a")) {
    24                 num = 100;
    25                 System.out.println("a set over!");
    26                 Thread.sleep(2000);
    27             } else {
    28                 num = 200;
    29                 System.out.println("b set over!");
    30             }
    31             System.out.println(username + " num=" + num);
    32         } catch (InterruptedException e) {
    33             // TODO Auto-generated catch block
    34             e.printStackTrace();
    35         }
    36     }
    37 
    38 }
    39 
    40 
    41 class ThreadA extends Thread {
    42 
    43     private HasSelfPrivateNum numRef;
    44 
    45     public ThreadA(HasSelfPrivateNum numRef) {
    46         super();
    47         this.numRef = numRef;
    48     }
    49 
    50     @Override
    51     public void run() {
    52         super.run();
    53         numRef.addI("a");
    54     }
    55 
    56 }
    57 
    58 
    59 
    60  class ThreadB extends Thread {
    61 
    62     private HasSelfPrivateNum numRef;
    63 
    64     public ThreadB(HasSelfPrivateNum numRef) {
    65         super();
    66         this.numRef = numRef;
    67     }
    68 
    69     @Override
    70     public void run() {
    71         super.run();
    72         numRef.addI("b");
    73     }
    74 
    75 }

    运行结果:

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

    修改HasSelfPrivateNum如下,方法用synchronize修饰如下:

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

    运行结果是线程安全的:

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

    由于sleep方法不会放弃对象锁,需要等到时间结束,释放锁,下一个进程才能获取到该对象锁

    实验结论:两个线程访问同一个对象中的同步方法是一定是线程安全的。本实现由于是同步访问,所以先打印出a,然后打印出b。

    这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。

    2.多个对象多个锁

     1 public class Run {
     2 
     3     public static void main(String[] args) {
     4 
     5         HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
     6         HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
     7 
     8         ThreadA athread = new ThreadA(numRef1);
     9         athread.start();
    10 
    11         ThreadB bthread = new ThreadB(numRef2);
    12         bthread.start();
    13 
    14     }
    15 
    16 }

    运行结果:

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

    这里是非同步的,因为线程athread获得是numRef1的对象锁,而bthread线程获取的是numRef2的对象锁,他们并没有在获取锁上有竞争关系,因此,出现非同步的结果。

    3.同步块synchronize(this)

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

    运行结果:

    1 begin time=1466148260341
    2 end    end=1466148262342
    3 begin time=1466148262342
    4 end    end=1466148264378

    这样也是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是ObjectService实例对象的对象锁了。

    4.synchronize(非this对象)

     1 public class Run {
     2 
     3     public static void main(String[] args) {
     4 
     5         Service service = new Service("xiaobaoge");
     6 
     7         ThreadA a = new ThreadA(service);
     8         a.setName("A");
     9         a.start();
    10 
    11         ThreadB b = new ThreadB(service);
    12         b.setName("B");
    13         b.start();
    14 
    15     }
    16 
    17 }
    18 
    19 class Service {
    20 
    21     String anyString = new String();
    22 
    23     public Service(String anyString){
    24         this.anyString = anyString;
    25     }
    26 
    27     public void setUsernamePassword(String username, String password) {
    28         try {
    29             synchronized (anyString) {
    30                 System.out.println("线程名称为:" + Thread.currentThread().getName()
    31                         + "在" + System.currentTimeMillis() + "进入同步块");
    32                 Thread.sleep(3000);
    33                 System.out.println("线程名称为:" + Thread.currentThread().getName()
    34                         + "在" + System.currentTimeMillis() + "离开同步块");
    35             }
    36         } catch (InterruptedException e) {
    37             // TODO Auto-generated catch block
    38             e.printStackTrace();
    39         }
    40     }
    41 
    42 }
    43 
    44 class ThreadA extends Thread {
    45     private Service service;
    46 
    47     public ThreadA(Service service) {
    48         super();
    49         this.service = service;
    50     }
    51 
    52     @Override
    53     public void run() {
    54         service.setUsernamePassword("a", "aa");
    55 
    56     }
    57 
    58 }
    59 
    60 
    61 class ThreadB extends Thread {
    62 
    63     private Service service;
    64 
    65     public ThreadB(Service service) {
    66         super();
    67         this.service = service;
    68     }
    69 
    70     @Override
    71     public void run() {
    72         service.setUsernamePassword("b", "bb");
    73 
    74     }
    75 
    76 }

    不难看出,这里线程争夺的是anyString的对象锁,两个线程有竞争同一对象锁的关系,出现同步。

    5.静态synchronize同步方法

     1 public class Run {
     2 
     3     public static void main(String[] args) {
     4 
     5         ThreadA a = new ThreadA();
     6         a.setName("A");
     7         a.start();
     8 
     9         ThreadB b = new ThreadB();
    10         b.setName("B");
    11         b.start();
    12 
    13     }
    14 
    15 }
    16 
    17 class Service {
    18 
    19     synchronized public static void printA() {
    20         try {
    21             System.out.println("线程名称为:" + Thread.currentThread().getName()
    22                     + "在" + System.currentTimeMillis() + "进入printA");
    23             Thread.sleep(3000);
    24             System.out.println("线程名称为:" + Thread.currentThread().getName()
    25                     + "在" + System.currentTimeMillis() + "离开printA");
    26         } catch (InterruptedException e) {
    27             e.printStackTrace();
    28         }
    29     }
    30 
    31     synchronized public static void printB() {
    32         System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
    33                 + System.currentTimeMillis() + "进入printB");
    34         System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
    35                 + System.currentTimeMillis() + "离开printB");
    36     }
    37 
    38 }
    39 
    40 
    41 class ThreadA extends Thread {
    42     @Override
    43     public void run() {
    44         Service.printA();
    45     }
    46 
    47 }
    48 
    49 
    50 class ThreadB extends Thread {
    51     @Override
    52     public void run() {
    53         Service.printB();
    54     }
    55 }

    运行结果:

    1 线程名称为:A在1466149372909进入printA
    2 线程名称为:A在1466149375920离开printA
    3 线程名称为:B在1466149375920进入printB
    4 线程名称为:B在1466149375920离开printB

    两个线程在争夺同一个类锁,因此同步。

    6.synchronize(class)

     1 class Service {
     2 
     3     public static void printA() {
     4         synchronized (Service.class) {
     5             try {
     6                 System.out.println("线程名称为:" + Thread.currentThread().getName()
     7                         + "在" + System.currentTimeMillis() + "进入printA");
     8                 Thread.sleep(3000);
     9                 System.out.println("线程名称为:" + Thread.currentThread().getName()
    10                         + "在" + System.currentTimeMillis() + "离开printA");
    11             } catch (InterruptedException e) {
    12                 e.printStackTrace();
    13             }
    14         }
    15 
    16     }
    17 
    18     public static void printB() {
    19         synchronized (Service.class) {
    20             System.out.println("线程名称为:" + Thread.currentThread().getName()
    21                     + "在" + System.currentTimeMillis() + "进入printB");
    22             System.out.println("线程名称为:" + Thread.currentThread().getName()
    23                     + "在" + System.currentTimeMillis() + "离开printB");
    24         }
    25     }
    26 }

    运行结果:

    1 线程名称为:A在1466149372909进入printA
    2 线程名称为:A在1466149375920离开printA
    3 线程名称为:B在1466149375920进入printB
    4 线程名称为:B在1466149375920离开printB

    两个线程依旧在争夺同一个类锁,因此同步。

    总结:

    A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 
    B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 
    C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制

  • 相关阅读:
    java项目中ehcache缓存最简单用法
    最简单的freemarker用法实例
    java从包package中获取所有的Class
    java获取properties配置文件中某个属性最简单方法
    java去除字符串中的空格、回车、换行符、制表符
    java获取中文汉字的所有拼音
    运行时给java对象动态的属性赋值
    java中把文件拷贝到指定目录下最简单几种方法
    在Springmvc普通类@Autowired注入request为null解决方法
    java导入excel很完美的取值的方法
  • 原文地址:https://www.cnblogs.com/zyxiaohuihui/p/9096882.html
Copyright © 2020-2023  润新知