最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不过是别人怎么用就跟着用,并没有搞清楚锁的概念。最近也是遇到一些问题,不搞清楚锁的概念,很容易碰壁,甚至有些时候自己连用没用对都不知道。

今天把一些疑惑都解开了,写篇文章分享给大家,文章还算比较全面。当然可能有小宝鸽理解得不够深入透彻的地方,如果说得不正确还望指出。

看之前有必要跟某些猿友说一下,如果看一遍没有看明白呢,也没关系,当是了解一下,等真正使用到了,再回头看。

本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁。特别的是希望能帮大家理清一些概念。

一、synchronized关键字

synchronized关键字有如下两种用法:

1、 在需要同步的方法的方法签名中加入synchronized关键字。

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

上面的代码修饰的synchronized是非静态方法,如果修饰的是静态方法(static)含义是完全不一样的。具体不一样在哪里,后面会详细说清楚。

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

2、使用synchronized块对需要进行同步的代码段进行同步。

  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. }

上面的代码块是synchronized (this)用法,还有synchronized (非this对象)以及synchronized (类.class)这两种用法,这些使用方式的含义也是有根本的区别的。我们先带着这些问题继续往下看。

二、Java中的对象锁和类锁

小宝鸽似乎并没有办法用清晰简短的语言来描述对象锁和类锁的概念。即便能用简单的语句概况,也会显得抽象。猿友们耐心看完自然会明白。

之前网上有找一些相关资料,有篇博客是这样描述的(看的是转载的,原创连接我也不知道):

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

这段话,除了最后一句,讲得都是挺合理的。”这样就保证了同步代码在统一时刻只有一个线程在执行。”这句话显然不对,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 虚拟机都会自动锁上对象或者类。

三、synchronized关键字各种用法与实例

看完了”二、Java中的对象锁和类锁”,我们再来结合”一、synchronized关键字”里面提到的synchronized用法。

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

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

因此,事实上synchronized关键字可以细分为上面描述的五种用法。

本文的实例均来自于《Java多线程编程核心技术》这本书里面的例子。

1、我们先看看非线程安全实例(Run.java):

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

运行结果为:

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

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

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

运行结果是线程安全的:

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

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

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

2、多个对象多个锁

就上面的实例,我们将Run改成如下:

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

运行结果为:

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

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

这里插播一下:同步不具有继承性

3、同步块synchronized (this)

我们先看看代码实例(Run.java)

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

运行结果:

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

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

需要注意的是synchronized (){}的{}前后的代码依旧是异步的

4、synchronized (非this对象)

我们先看看代码实例(Run.java)

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

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

现在有一个问题:一个类里面有两个非静态同步方法,会有影响么?

答案是:如果对象实例A,线程1获得了对象A的对象锁,那么其他线程就不能进入需要获得对象实例A的对象锁才能访问的同步代码(包括同步方法和同步块)。不理解可以细细品味一下!

5、静态synchronized同步方法

我们直接看代码实例:

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

运行结果:

  1. 线程名称为:A1466149372909进入printA
  2. 线程名称为:A1466149375920离开printA
  3. 线程名称为:B1466149375920进入printB
  4. 线程名称为:B1466149375920离开printB

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

6、synchronized (class)

对上面Service类代码修改成如下:

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

运行结果:

  1. 线程名称为:A1466149372909进入printA
  2. 线程名称为:A1466149375920离开printA
  3. 线程名称为:B1466149375920进入printB
  4. 线程名称为:B1466149375920离开printB

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

需要特别说明:对于同一个类A,线程1争夺A对象实例的对象锁,线程2争夺类A的类锁,这两者不存在竞争关系。也就说对象锁和类锁互补干预内政

静态方法则一定会同步,非静态方法需在单例模式才生效,但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。另外,有必要说一下的是Spring的bean默认是单例的。

Java对象锁和类锁全面解析(多线程synchronized关键字)的更多相关文章

  1. java线程同步以及对象锁和类锁解析(多线程synchronized关键字)

    一.关于线程安全 1.是什么决定的线程安全问题? 线程安全问题基本是由全局变量及静态变量引起的. 若每个线程中对全局变量.静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线 ...

  2. Java锁Synchronized,对象锁和类锁举例

    Java的锁分为对象锁和类锁. 1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内针对该对象的操作只能有一个线程得到执行.另一个线程必须 ...

  3. Java锁Synchronized对象锁和类锁区别

    java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁.线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁.获得内置锁的唯一途径就是进入这个锁的保 ...

  4. java的对象锁和类锁

    在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...

  5. java 对象锁和类锁的区别(转)

    java 对象锁和类锁的区别   转自; ) ); ; ) ); 上述的代码,第一个方法时用了同步代码块的方式进行同步,传入的对象实例是this,表明是当前对象,当然,如果需要同步其他对象实例,也不可 ...

  6. 【Thread】java线程之对象锁、类锁、线程安全

    说明: 1.个人技术也不咋滴.也没在项目中写过线程,以下全是根据自己的理解写的.所以,仅供参考及希望指出不同的观点. 2.其实想把代码的github贴出来,但还是推荐在初学的您多亲自写一下,就没贴出来 ...

  7. Java 中对象锁和类锁的区别? 关键字 Synchronized的用法?

    一  对象锁和类锁的关系 /* * 对象锁和[类锁] 全局锁的关系? 对象锁是用于对象实例方法,或者一个对象实例上的 this 类锁是用于类的静态方法或者一个类的class对象上的. Ag.class ...

  8. Synchronized方法锁、对象锁、类锁区别

    synchronized,这个东西我们一般称之为”同步锁“,他在修饰代码块的时候需要传入一个引用对象作为“锁”的对象. 在修饰方法的时候,默认是当前对象作为锁的对象 在修饰类时,默认是当前类的Clas ...

  9. synchronize——对象锁和类锁

    最近在研究Java 多线程的只是,经常能看到synchronize关键字,以前只是一眼带过,没有细究,今天趁这个机会,整理下 synchronize作为多线程关键字,是一种同步锁,它可以修饰以下几种对 ...

随机推荐

  1. 使用requirejs来管理angularJS依赖示例

    有关requirejs是什么在这里不做解释,只用纯代码实战让你感受requirejs依赖管理的强大. 一.首先要先下载require.js,然后整一个入口文件main.js包括了对其他js的引用. / ...

  2. 彻底弄懂JS的事件冒泡和事件捕获

      先上结论:在事件执行流中有两种执行方式.一种是事件冒泡(即事件的执行顺序是从下往上执行的) ;  另一种是捕获(即事件的执行顺序是从上往下执行的); 阻止事件冒泡:   return false; ...

  3. jacascript 立即执行函数(IIFE)与闭包

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 一直没搞清楚立即执行函数和闭包之间的关系,总结一下: 闭包有很多种理解:访问不到内部作用域,函数就是这样, ...

  4. Spring(3)——装配 Spring Bean 详解

    装配 Bean 的概述 前面已经介绍了 Spring IoC 的理念和设计,这一篇文章将介绍的是如何将自己开发的 Bean 装配到 Spring IoC 容器中. 大部分场景下,我们都会使用 Appl ...

  5. Python的hasattr() getattr() setattr() 函数使用方法详解 (转)

    来自:https://www.cnblogs.com/cenyu/p/5713686.html hasattr(object, name)判断一个对象里面是否有name属性或者name方法,返回BOO ...

  6. [SDOI 2010]外星千足虫

    Description 题库链接 给出 \(m\) 个 \(n\) 元的 \(0,1\) 方程,即系数非 \(0\) 即 \(1\) ,方程的结果为奇偶性. \(1\leq n\leq 1000,1\ ...

  7. [SCOI2014]方伯伯的玉米田

    Description 方伯伯在自己的农田边散步,他突然发现田里的一排玉米非常的不美. 这排玉米一共有N株,它们的高度参差不齐. 方伯伯认为单调不下降序列很美,所以他决定先把一些玉米拔高,再把破坏美感 ...

  8. [HNOI2008]明明的烦恼

    Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在 任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为 ...

  9. Go学习——go+channel实战(转)

    转载:http://studygolang.com/articles/2423 背景 在最近开发的项目中,后端需要编写许多提供HTTP接口的API,另外技术选型相对宽松,因此选择Golang + Be ...

  10. 【HDU 2669】Romantic

    Problem Description The Sky is Sprite.The Birds is Fly in the Sky.The Wind is Wonderful.Blew Throw t ...