java中存在对多个对象加锁的情况_Java对象锁和类锁全面解析(多线程synchronized关键字)...

最近工作有用到一些多线程的东西,之前吧,有用到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内存,如果想了解可以看看博主的另外一篇文章:

方法区(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();

}

}

运行结果:

线程名称为:A在1466149372909进入printA

线程名称为:A在1466149375920离开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");

}

}

}

运行结果:

线程名称为:A在1466149372909进入printA

线程名称为:A在1466149375920离开printA

线程名称为:B在1466149375920进入printB

线程名称为:B在1466149375920离开printB

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/472752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线程间通信的几种方法_并发编程中的线程间通信

线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。线程通信常用的方式有:wait/notify 等待Volatile 内存共享CountDownLatch 并发工具使用 ReentrantLock 结合 Condition基本LockSupport实现线程间的阻塞和唤醒方式一&am…

【IOS】Target membership

Target membership是指XCode中,一个文件属于哪一个工程,在XCode左侧的工程面板中选中一个文件,在XCode右侧的属性面板中会显示其Target Membership,如下图。 当前的文件AppDelegate.m属于书谱这个Target。 Target Membership的一些…

LeetCode 1775. 通过最少操作次数使数组的和相等(贪心+双指针)

文章目录1. 题目2. 解题1. 题目 给你两个长度可能不等的整数数组 nums1 和 nums2 。 两个数组中的所有值都在 1 到 6 之间(包含 1 和 6)。 每次操作中,你可以选择 任意 数组中的任意一个整数,将它变成 1 到 6 之间 任意 的值&am…

pythonrequest得替代_python的扩展包requests的高级用法

Python 标准库中的 urllib2 模块提供了你所需要的大多数 HTTP 功能,但是它的 API 太渣了。它是为另一个时代、另一个互联网所创建的。它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务。Requests 完全满足如今网络的需求。国际化域…

画箭头

$Xexittrim($exit[0]); //始点x坐标$Yexittrim($exit[1]); //始点Y坐标$Xentertrim($enter[0]); //终点X坐标$Yentertrim($enter[1]); //终点Y坐标imageline($im,$Xexit,$Yexit,$Xenter,$Yenter,$green); //画根线if($Yexit $Yenter) {if($Xexit < $Xente…

LeetCode 1776. 车队 II(单调栈)

文章目录1. 题目2. 解题1. 题目 在一条单车道上有 n 辆车&#xff0c;它们朝着同样的方向行驶。 给你一个长度为 n 的数组 cars &#xff0c;其中 cars[i] [positioni, speedi] &#xff0c;它表示&#xff1a; positioni 是第 i 辆车和道路起点之间的距离&#xff08;单位&…

wpf计算字符大小占像素_LCD作为终端显示字符串的过程

LCD作为终端显示字符串的过程1.本文目的2.资源评估3.显示原理4.嵌入式上汉字处理5.结果验证与展示6.总结1.本文目的做嵌入式图形开发&#xff0c;我们往往都会利用到各种GUI进行交互设计&#xff0c;但是对于GUI的字符串处理与中文字库显示&#xff0c;也许并不会特别关注&…

java中对象作为参数_java中对象引用,特别作为参数时候注意事项

1、基础知识1Byte8bitByte和byte一个是对象&#xff0c;一个是基本数据类型而已&#xff0c;都是8bit2、java中对象引用和值传递&#xff1a;(1)、java中若一对象当作参数就是相当于&#xff0c;将变量对应的指向的地址内容传递进去了如下面代码&#xff1a;public class Test …

商城简单类图

转载于:https://www.cnblogs.com/stit/p/4125095.html

Java 自定义排序 Comparator

语法&#xff1a; class 比较器名 implements Comparator 重写 int compare(Object o1, Object o2) import java.util.*;class comparatorDemo implements Comparator {public int compare(Object o1, Object o2) {String a, b;a (String) o1;b (String) o2;return b.compar…

交换机的基本配置实验报告_无线网络设计配置即实验报告

工程师ACK接到一个小型图书馆的网络组建项目&#xff0c;根据目前流行的网络组建以及项目地点的使用需要便捷性&#xff0c;采用AC控制的瘦AP模式。不需要AC来管理网络的AP是胖AP需要单独配置&#xff0c;有多少个AP就要配置多少次。组建模式&#xff1a;一楼划分20个VLAN,二楼…

java异步处理同步化_java 异步查询转同步多种实现方式:循环等待,CountDownLatch,Spring EventListener,超时处理和空循环性能优化...

异步转同步业务需求有些接口查询反馈结果是异步返回的&#xff0c;无法立刻获取查询结果。正常处理逻辑触发异步操作&#xff0c;然后传递一个唯一标识。等到异步结果返回&#xff0c;根据传入的唯一标识&#xff0c;匹配此次结果。如何转换为同步正常的应用场景很多&#xff0…

PHP上传文件大小限制的问题(转)

在用PHP进行文件上传的操作中&#xff0c;需要知道怎么控制上传文件大小的设置&#xff0c;而文件可传大小是受到多种因素制约的&#xff0c;现总结如下&#xff1a;1、php.ini:upload_max_filesize 所上传的文件的最大大小。默认值2M。2、php.ini:memory_limit 本指令设定了一…

Java 集合框架(List、Set、Map、Iterator、Stack、Properties)

文章目录1. ArrayList2. LinkedList3. HashSet4. TreeSet5. Iterator、ListIterator6. HashMap7. TreeMap8. Stack9. Properties 类读写简单 数据库相关文献&#xff1a;https://www.runoob.com/java/java-collections.html 1. ArrayList 类似动态数组 ArrayList al new Arr…

js获取当前url_javascript如何获取当前URL的主机名?

在javascript中可以使用location对象的hostname属性或者host属性来获取当前URL的主机名&#xff0c;下面本篇文章就来带大家认识这两种属性&#xff0c;希望对大家有所帮助。使用location hostname属性location hostname属性是用于返回当前URL的主机名&#xff1b;它会返回一个…

java访问数据库方式_java数据库访问(二)—JDBC方式(配合连接池)

上文记录了最基础的JDBC连接数据库的方法&#xff0c;但能看出一个问题&#xff0c;就是要不断的重复去创建connection和关闭connection&#xff0c;如果在对数据库的访问比较频繁的情况下&#xff0c;这种处理方式方式在性能方面是不合适的&#xff0c;下面使用JDBC配合数据库…

超频真的不难!G3258超频4.5GHz全攻略

奔腾G3258搭配主板详解【pconline 应用】目前DIY市场上最火热的装机组合莫过于奔腾20周年纪念版处理器G3258搭配B85芯片组主板&#xff0c;只要通过适当的超频&#xff0c;相对较低投入也能来不错的性能体验&#xff0c;因此在奔腾G3258还没上市时&#xff0c;业界对其充满期待…

python算法入门_GitHub标星2.6万!Python算法新手入门大全

问耕 发自 凹非寺量子位 出品 | 公众号 QbitAI今天推荐一个Python学习的干货。几个印度小哥&#xff0c;在GitHub上建了一个各种Python算法的新手入门大全&#xff0c;现在标星已经超过2.6万。这个项目主要包括两部分内容&#xff1a;一是各种算法的基本原理讲解&#xff0c;二…

Java enum枚举

文章目录1. 枚举例子2. 接口的实现3. 枚举中定义抽象方法enum 定义了枚举类型&#xff0c;其继承于 Enum 枚举类 1. 枚举例子 import java.util.EnumMap; import java.util.EnumSet; import java.util.Iterator; import java.util.Map;class EnumDemo {public enum Color{RED…

mysql写什么不同_mysql - 编译配置PHP时,两种配置写法有什么不同

在编译PHP时,--with-扩展库DIR--enable-扩展库这两种配置有什么不同回复内容&#xff1a;在编译PHP时,--with-扩展库DIR--enable-扩展库这两种配置有什么不同很明显是取值的不同&#xff1a;with 后面可以为空&#xff0c;即默认值;可以是路径;可以是功能名称enable 后面不能有…