`

Java中的锁

阅读更多

.按照锁的监听器划分对象锁/类锁  (synchronized举例)

 

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

一、synchronized关键字

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

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

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

 

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

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

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

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中的对象锁和类锁

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

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

一段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 虚拟机都会自动锁上对象或者类。

 

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

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

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

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

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

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

1、我们先看看非线程安全实例(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的对象实例的锁——对象锁。

2、多个对象多个锁

就上面的实例,我们将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的对象锁,他们并没有在获取锁上有竞争关系,因此,出现非同步的结果

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

3、同步块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 (){}的{}前后的代码依旧是异步的

4、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的对象锁才能访问的同步代码(包括同步方法和同步块)。不理解可以细细品味一下!

5、静态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();
    }
}

运行结果:

线程名称为:A1466149372909进入printA
线程名称为:
A1466149375920离开printA
线程名称为:B在
1466149375920进入printB
线程名称为:B在
1466149375920离开printB

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

6、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");
        }
    }
}

运行结果:

线程名称为:A1466149372909进入printA
线程名称为:
A1466149375920离开printA
线程名称为:B在
1466149375920进入printB
线程名称为:B在
1466149375920离开printB

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

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

 

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

分享到:
评论

相关推荐

    JAVA中锁概及运用.doc

    JAVA 中锁概及运用 JAVA 中锁是指在多线程环境下,用于控制对共享资源的访问的机制。锁是 Java 并发编程的核心概念之一,锁机制可以确保在多线程环境下,共享资源不会被多个线程同时访问,从而避免数据不一致和其他...

    JAVA中锁的解决方案.docx

    JAVA 中锁的解决方案 在 JAVA 中,锁是非常重要的概念,它可以帮助开发者解决多线程编程中出现的同步问题。在本文中,我们将深入介绍 JAVA 中锁的解决方案,包括乐观锁和悲观锁的定义、核心代码剖析以及使用场景。 ...

    改善Java中锁的性能_.docx

    在Java编程中,锁是多线程环境下保证数据一致性与安全性的重要机制。然而,不当的锁使用可能导致性能下降,甚至引发死锁等严重问题。本文将探讨如何通过优化策略改善Java中锁的性能。 首先,理解锁竞争是性能瓶颈的...

    java并发锁面试知识

    java中的乐观锁与悲观锁,synchronized与ReentrantLock重入锁的说明与比较

    彻底理解Java中的各种锁.pdf

    通过以上对Java锁机制的详细介绍,可以看出Java在并发控制方面具有丰富的工具和策略,它们能够帮助开发者在多线程编程中处理好资源竞争和线程协作的问题,从而编写出高效且线程安全的应用程序。

    java 中锁的性能提高办法

    【Java 中锁的性能提高办法】 在 Java 多线程编程中,锁是实现线程安全的关键机制。然而,不当的锁使用可能会导致性能瓶颈,甚至引发死锁等问题。以下是一些提高 Java 中锁性能的技术和策略: 1. **分离锁(Lock ...

    java 读写锁代码

    Java 读写锁是Java并发编程中的一种重要机制,它为多线程环境下的数据访问提供了更为精细的控制。在Java的`java.util.concurrent.locks`包中,`ReentrantReadWriteLock`类实现了读写锁的功能。这个锁允许多个读取者...

    java锁详解.pdf

    Java 锁是 Java 并发编程中的一种基本机制,用于确保线程安全和避免竞争条件。Java 锁可以分为两大类:synchronized 锁和 ReentrantLock 锁。 一、Synchronized 锁 1. 锁的原理:synchronized 锁是基于对象头的 ...

    java中锁知识的整理,文件是.xmind思维导图

    适合已经学过锁这方面知识的小伙伴进行学习,思维导图对各种锁进行了详细整理总结,采用思维导图的方式更加方便大伙们学习并记忆。

    java锁机制Synchronizedjava锁机制Synchronized

    Java 锁机制 Synchronized 是 Java 语言中的一种同步机制,用于解决多线程并发访问共享资源时可能出现的一些问题。 Java 锁机制 Synchronized 的概念 在 Java 中,每个对象都可以被看作是一个大房子,其中有多个...

    Java中锁的实现和内存语义浅析

    在Java并发编程中,锁的实现和内存语义是实现线程安全的关键机制之一。锁的机制能够保证在多线程环境中,对于共享资源的访问互斥执行,防止出现数据竞争和条件竞争的问题。而内存语义涉及到线程间如何通过主内存和...

    Java 中的悲观锁和乐观锁的实现

    ### Java中的悲观锁与乐观锁实现详解 #### 一、悲观锁(Pessimistic Locking) 悲观锁是一种基于对数据安全性的保守态度而设计的锁机制。它假设数据在处理过程中很可能被外界修改,因此在整个数据处理过程中都会将...

    java锁的释放与建立

    总之,Java锁的释放与建立是多线程编程中的核心概念,它们与_happens-before_关系紧密相连,确保了线程间的正确通信和数据一致性。正确理解和运用这些机制,能有效避免并发问题,提高程序的稳定性和性能。

    Java的锁机制的学习和使用

    #### 一、Java锁机制概览 Java中的锁机制主要用于解决多线程环境下的资源竞争问题。在并发编程中,为了保证数据一致性与正确性,通常需要采用各种锁来控制对共享资源的访问。Java语言提供了多种锁机制,包括`...

    Java中的锁分类与使用.docx

    Java中的锁机制是多线程编程中不可或缺的一部分,它们用于管理对共享资源的并发访问,确保数据的一致性和完整性。本文将深入探讨Java中的几种主要锁类型及其使用。 1. **乐观锁**与**悲观锁** - **乐观锁**假设...

    4种常用Java线程锁的特点,性能比较、使用场景.pdf

    4种常用Java线程锁的特点,性能比较、使用场景 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发...

    Java中的锁分类的详细介绍

    Java 中的锁分类可以分为多种类型,包括公平锁、非公平锁、可重入锁、独享锁、共享锁、互斥锁、读写锁、乐观锁、悲观锁、分段锁、偏向锁、轻量级锁、重量级锁、自旋锁等。这些锁的分类并不是指锁的状态,有的指锁的...

Global site tag (gtag.js) - Google Analytics