手记

Java对象锁和类锁全面解析(多线程synchronized关键字)


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

一、synchronized关键字

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

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

1

2

3

4

5

synchronized public void getValue() {

    System.out.println("getValue method thread name="

            + Thread.currentThread().getName() + " username=" + username

            + " password=" + password);

}

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

1

2

3

4

5

synchronized static public void getValue() {

    System.out.println("getValue method thread name="

            + Thread.currentThread().getName() + " username=" + username

            + " password=" + password);

}

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

1

2

3

4

5

6

7

8

9

10

11

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)这两种用法,这些使用方式的含义也是有根本的区别的。我们先带着这些问题继续往下看。

二、Java中的对象锁和类锁

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

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

1

2

3

4

5

6

一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,

在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁);

如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 

取到锁后,他就开始执行同步代码(被synchronized修饰的代码);

线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。

这样就保证了同步代码在统一时刻只有一个线程在执行。

这段话,除了最后一句,讲得都是挺合理的。”这样就保证了同步代码在统一时刻只有一个线程在执行。”这句话显然不对,synchronized并非保证同步代码同一时刻只有一个线程执行,同步代码同一时刻应该可以有多个线程执行。

上面提到锁,这里先引出锁的概念。先来看看下面这些啰嗦而必不可少的文字。

多线程的线程同步机制实际上是靠锁的概念来控制的。

在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:

1)保存在堆中的实例变量

2)保存在方法区中的类变量

这两类数据是被所有线程共享的。

(程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的。)

这里插播一下广告:关于JVM内存,如果想了解可以看看博主的另外一篇文章:

方法区(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)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。

©著作权归作者所有:来自51CTO博客作者无鳍的鱼的原创作品,如需转载,请注明出处,否则将追究法律责任


0人推荐
随时随地看视频
慕课网APP