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. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制

posted @ 2018-05-27 18:38  取什么昵称呢  阅读(16922)  评论(2编辑  收藏  举报