版权声明:本文为博主原创文章,转载注明出处http://blog.csdn.net/u013142781java
最近工做有用到一些多线程的东西,以前吧,有用到synchronized同步块,不过是别人怎么用就跟着用,并无搞清楚锁的概念。最近也是遇到一些问题,不搞清楚锁的概念,很容易碰壁,甚至有些时候本身连用没用对都不知道。spring
今天把一些疑惑都解开了,写篇文章分享给你们,文章还算比较全面。固然可能有小宝鸽理解得不够深刻透彻的地方,若是说得不正确还望指出。编程
看以前有必要跟某些猿友说一下,若是看一遍没有看明白呢,也不要紧,当是了解一下,等真正使用到了,再回头看。安全
本文主要是将synchronized关键字用法做为例子来去解释Java中的对象锁和类锁。特别的是但愿能帮你们理清一些概念。多线程
1、synchronized关键字异步
synchronized关键字有以下两种用法:ide
一、 在须要同步的方法的方法签名中加入synchronized关键字。性能
synchronized public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); }
上面的代码修饰的synchronized是非静态方法,若是修饰的是静态方法(static)含义是彻底不同的。具体不同在哪里,后面会详细说清楚。this
synchronized static public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); }
二、使用synchronized块对须要进行同步的代码段进行同步。.net
public void serviceMethod() { try { synchronized (this) { System.out.println("begin time=" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("end end=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } }
上面的代码块是synchronized (this)用法,还有synchronized (非this对象)以及synchronized (类.class)这两种用法,这些使用方式的含义也是有根本的区别的。咱们先带着这些问题继续往下看。
2、Java中的对象锁和类锁
小宝鸽彷佛并无办法用清晰简短的语言来描述对象锁和类锁的概念。即使能用简单的语句概况,也会显得抽象。猿友们耐心看完天然会明白。
以前网上有找一些相关资料,有篇博客是这样描述的(看的是转载的,原创链接我也不知道):
一段synchronized的代码被一个线程执行以前,他要先拿到执行这段代码的权限, 在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 若是这个时候同步对象的锁被其余线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码); 线程执行完同步代码后立刻就把锁还给同步对象,其余在锁池中等待的某个线程就能够拿到锁执行同步代码了。 这样就保证了同步代码在统一时刻只有一个线程在执行。
这段话,除了最后一句,讲得都是挺合理的。”这样就保证了同步代码在统一时刻只有一个线程在执行。”这句话显然不对,synchronized并不是保证同步代码同一时刻只有一个线程执行,同步代码同一时刻应该能够有多个线程执行。
上面提到锁,这里先引出锁的概念。先来看看下面这些啰嗦而必不可少的文字。
多线程的线程同步机制其实是靠锁的概念来控制的。
在Java程序运行时环境中,JVM须要对两类线程共享的数据进行协调:
1)保存在堆中的实例变量
2)保存在方法区中的类变量
这两类数据是被全部线程共享的。
(程序不须要协调保存在Java 栈当中的数据。由于这些数据是属于拥有该栈的线程所私有的。)
这里插播一下广告:关于JVM内存,若是想了解能够看看博主的另一篇文章:
Java内存管理:http://blog.csdn.net/u013142781/article/details/50830754
方法区(Method Area)与Java堆同样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,可是它却有一个别名叫作Non-Heap(非堆),目的应该是与Java堆区分开来。
栈:在Java中,JVM中的栈记录了线程的方法调用。每一个线程拥有一个栈。在某个线程的运行过程当中,若是有新的方法调用,那么该线程对应的栈就会增长一个存储单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。
堆是JVM中一块可自由分配给对象的区域。当咱们谈论垃圾回收(garbage collection)时,咱们主要回收堆(heap)的空间。
Java的普通对象存活在堆中。与栈不一样,堆的空间不会随着方法调用结束而清空。所以,在某个方法中建立的对象,能够在方法调用结束以后,继续存在于堆中。这带来的一个问题是,若是咱们不断的建立新的对象,内存空间将最终消耗殆尽。
在java虚拟机中,每一个对象和类在逻辑上都是和一个监视器相关联的。
对于对象来讲,相关联的监视器保护对象的实例变量。
对于类来讲,监视器保护类的类变量。
(若是一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。)
为了实现监视器的排他性监视能力,java虚拟机为每个对象和类都关联一个锁。表明任什么时候候只容许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。
可是若是线程获取了锁,那么在它释放这个锁以前,就没有其余线程能够获取一样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)
类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会建立一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
一个线程能够屡次对同一个对象上锁。对于每个对象,java虚拟机维护一个加锁计数器,线程每得到一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被彻底释放了。
java编程人员不须要本身动手加锁,对象锁是java虚拟机内部使用的。
在java程序中,只须要使用synchronized块或者synchronized方法就能够标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。
3、synchronized关键字各类用法与实例
看完了”2、Java中的对象锁和类锁”,咱们再来结合”1、synchronized关键字”里面提到的synchronized用法。
事实上,synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,须要得到对象锁。
synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,须要得到类锁。
所以,事实上synchronized关键字能够细分为上面描述的五种用法。
本文的实例均来自于《Java多线程编程核心技术》这本书里面的例子。
一、咱们先看看非线程安全实例(Run.java):
public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef); athread.start(); ThreadB bthread = new ThreadB(numRef); bthread.start(); } } class HasSelfPrivateNum { private int num = 0; public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("a"); } } class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("b"); } }
运行结果为:
a set over! b set over! b num=200 a num=200
修改HasSelfPrivateNum以下,方法用synchronized修饰以下:
class HasSelfPrivateNum { private int num = 0; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
运行结果是线程安全的:
b set over! b num=200 a set over! a num=100
实验结论:两个线程访问同一个对象中的同步方法是必定是线程安全的。本实现因为是同步访问,因此先打印出a,而后打印出b
这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。
二、多个对象多个锁
就上面的实例,咱们将Run改为以下:
public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef1); athread.start(); ThreadB bthread = new ThreadB(numRef2); bthread.start(); } }
运行结果为:
a set over! b set over! b num=200 a num=200
这里是非同步的,由于线程athread得到是numRef1的对象锁,而bthread线程获取的是numRef2的对象锁,他们并无在获取锁上有竞争关系,所以,出现非同步的结果
这里插播一下:同步不具备继承性
三、同步块synchronized (this)
咱们先看看代码实例(Run.java)
public class Run { public static void main(String[] args) { ObjectService service = new ObjectService(); ThreadA a = new ThreadA(service); a.setName("a"); a.start(); ThreadB b = new ThreadB(service); b.setName("b"); b.start(); } } class ObjectService { public void serviceMethod() { try { synchronized (this) { System.out.println("begin time=" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("end end=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread { private ObjectService service; public ThreadA(ObjectService service) { super(); this.service = service; } @Override public void run() { super.run(); service.serviceMethod(); } } class ThreadB extends Thread { private ObjectService service; public ThreadB(ObjectService service) { super(); this.service = service; } @Override public void run() { super.run(); service.serviceMethod(); } }
运行结果:
begin time=1466148260341 end end=1466148262342 begin time=1466148262342 end end=1466148264378
这样也是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是ObjectService实例对象的对象锁了。
须要注意的是synchronized (){}的{}先后的代码依旧是异步的
四、synchronized (非this对象)
咱们先看看代码实例(Run.java)
public class Run { public static void main(String[] args) { Service service = new Service("xiaobaoge"); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); } } class Service { String anyString = new String(); public Service(String anyString){ this.anyString = anyString; } public void setUsernamePassword(String username, String password) { try { synchronized (anyString) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块"); Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("a", "aa"); } } class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("b", "bb"); } }
不难看出,这里线程争夺的是anyString的对象锁,两个线程有竞争同一对象锁的关系,出现同步
如今有一个问题:一个类里面有两个非静态同步方法,会有影响么?
答案是:若是对象实例A,线程1得到了对象A的对象锁,那么其余线程就不能进入须要得到对象实例A的对象锁才能访问的同步代码(包括同步方法和同步块)。不理解能够细细品味一下!
五、静态synchronized同步方法
咱们直接看代码实例:
public class Run { public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("A"); a.start(); ThreadB b = new ThreadB(); b.setName("B"); b.start(); } } class Service { synchronized public static void printA() { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } }
运行结果:
线程名称为:A在1466149372909进入printA 线程名称为:A在1466149375920离开printA 线程名称为:B在1466149375920进入printB 线程名称为:B在1466149375920离开printB
两个线程在争夺同一个类锁,所以同步
六、synchronized (class)
对上面Service类代码修改为以下:
class Service { public static void printA() { synchronized (Service.class) { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void printB() { synchronized (Service.class) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } } }
运行结果:
线程名称为:A在1466149372909进入printA 线程名称为:A在1466149375920离开printA 线程名称为:B在1466149375920进入printB 线程名称为:B在1466149375920离开printB
两个线程依旧在争夺同一个类锁,所以同步
须要特别说明:对于同一个类A,线程1争夺A对象实例的对象锁,线程2争夺类A的类锁,这二者不存在竞争关系。也就说对象锁和类锁互补干预内政
静态方法则必定会同步,非静态方法需在单例模式才生效,可是也不能都用静态同步方法,总之用得很差可能会给性能带来极大的影响。另外,有必要说一下的是spring的bean默认是单例的。