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