cookie里面用到的关键字_晓龙吊打面试官系列:synchronized关键字入门(同步方法与同步代码块)...

文章目录

  • 一、 线程安全问题

  • 二、synchronized简介

    • 1) 原子性

    • 2) 可见性

    • 3) 有序性

    • 4)可重入

    • 1. 什么是synchronized

    • 2.什么是同步

    • 3.synchronized的特性

    • 4.synchronized的实现原理(了解即可)

  • 三、synchronized的用法

    • 1. 同步方法

    • 2. 同步代码块

  • 四、对象锁和类锁

    • 1)对象锁

    • 2)类锁

    • 1.对象锁的探索

    • 2.对象锁的简介

    • 1.对象锁的验证

    • 2.类锁的验证

    • 3.对象锁和类锁的区别

一、 线程安全问题

线程安全问题使我们平时面试中总避不开会谈论的一个点,通常情况下线程安全问题都是由于非方法内的实例变量引起的。就比如我们举个很简单的例子:

在描述线程的相关Demo中,我喜欢用银行相关的业务场景做举例,大多数和线程相关的知识点都能对应到银行需要用到的业务。就比如今天我会通过一个简单的银行排号系统的实现来解释今天的知识点。

代码实现:

public class BankLineUp {

// 设置一个当天最大服务人次
public static final Integer MAX_NUM = 500;
// 设置一个排号计数器
public static Integer count = 0;

// 创建一个内部类 实现runnable接口
class LineUp implements Runnable {
// 叫号逻辑实现
@Override
public void run() {
// 输出当前叫到的号码
call();
}
}

// 提供构造方法
public LineUp lineUp() {
return new LineUp();
}

public static void main(String[] args) {
BankLineUp bankLineUp = new BankLineUp();
new Thread(bankLineUp.lineUp(), "一号窗口").start();
new Thread(bankLineUp.lineUp(), "二号窗口").start();
new Thread(bankLineUp.lineUp(), "三号窗口").start();

}

// 通过synchronized修饰方法实现同步方法
public void call() {
while (true) {
if (count < MAX_NUM) {
try {
// 通过休眠一秒 模拟系统延时
Thread.sleep(1l);
System.out.println("有请" + ++count + "号顾客到" + Thread.currentThread().getName() + "办理业务!");
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}

}

运行结果:693d2d53228c09279436d856b7e85373.png
上面的案例我们模拟了因为系统可能存在的延时而导致了叫号系统处理了超过自己限制的数据量,这明显是线程不安全的,那么我们有没有简单的方式可以解决这个小问题呢?

二、synchronized简介

1. 什么是synchronized

synchronized的字面翻译就是同步,Java通过引入synchronized的关键字来保证同一时间只有一个线程可以运行被synchronized保护的代码。

2.什么是同步

同步指的是为了完成某种任务而建立的两个或多个进程,这些进程因为要在某些位置上协调他们的工作次序而瞪大,传递信息所产生的制约关系,因此同步又叫做直接制约关系。在Java线程中的同步主要就体现在如果该资源为同步资源程,为了防止多个线程访问该资源时对数据对象产生破坏,CPU在调度该资源时一次仅允许一个线程访问修改。

3.synchronized的特性

1) 原子性

所谓的原子性指的是同一个或者多个操作,要么全部执行,要么全部不执行

2) 可见性

可见性就是指多个线程访问同一个资源时,该资源状态及变化对其他线程可见。

3) 有序性

有序性指的是CPU对于线程的执行顺序与代码顺序相同。

JAVA允许编译器和处理器对于指令进行重排序,指令重排序并不会影响单线程的执行结果。但是在多线程中,由于存在半初始化线程,指令重排会造成很难排查的线程安全问题。

4)可重入

当一个线程师徒操作一个由其他线程持有锁的临界资源时,将会出于阻塞状态,当一个线程再次请求自己持有对象锁的资源时,无需等待,这种现象叫做可重入锁。

4.synchronized的实现原理(了解即可)

synchronized是在对象头里面存储一个ACC_SYNCHRONIZED标识进行实现的,当JVM进入和退出一个Monitor对象的时候,会判断此时的monitor是否被持有,如果被持有,则它将会处于锁定状态。

在汇编指令中,monitorenter和monitorexit分别代表获得和释放持有的monitor对象锁

三、synchronized的用法

1. 同步方法

在刚刚的例子里,我们发现了共享变量在多线程访问的情况下会出现线程安全问题,之后又引出了synchronized关键字,那么这个关键字该如何用来解决线程安全问题?我们可以看下面的一段代码

package xiao.thread.synchronize;

/**
* @Title: BankLineUp.java
* @Package xiao.thread.synchronize
* @Description: TODO
* @author: 晓
* @date: 2020年11月30日 上午10:15:36
* @version V1.0
* @Copyright: com.cdzg.com
*
*/
public class BankLineUp {

// 设置一个当天最大服务人次
public static final Integer MAX_NUM = 500;
// 设置一个排号计数器
public static Integer count = 0;

// 创建一个内部类 实现runnable接口
class LineUp implements Runnable {
// 叫号逻辑实现
@Override
public void run() {
// 输出当前叫到的号码
call();
}
}

// 提供构造方法
public LineUp lineUp() {
return new LineUp();
}

public static void main(String[] args) {
BankLineUp bankLineUp = new BankLineUp();
new Thread(bankLineUp.lineUp(), "一号窗口").start();
new Thread(bankLineUp.lineUp(), "二号窗口").start();
new Thread(bankLineUp.lineUp(), "三号窗口").start();

}

// 通过synchronized修饰方法实现同步方法
public synchronized void call() {
while (true) {
if (count < MAX_NUM) {
try {
// 通过休眠一秒 模拟系统延时
Thread.sleep(1l);
System.out.println("有请" + ++count + "号顾客到" + Thread.currentThread().getName() + "办理业务!");
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}

}

执行结果:caf308475e16a554832f645703076ad9.png
代码变化:027debbfa5a2d0738ecb2c6044ec984e.png
我们仅仅是在call的方法体上加了一个synchronized关键字就实现了线程变量的保护,从而达到线程安全的目的,但是新的问题新出现了:760459743f450a78cf36cbc98a29eb39.png
由于call方法被设置为了同步方法,此时二号线程和三号线程一直在等待一号线程执行完才能拿到cpu的执行权,但是所有的变量此时都被一号线程处理完毕,二号和三号线程并没有实际的参与到代码的运行中,由此可见当我们在将方法添加synchronized后,同步方法的锁力度太大了,已经影响了我们的运行效率,那么此时有没有办法对这个问题进行简单的优化呢?

2. 同步代码块

public class BankLineUp {

// 设置一个当天最大服务人次
public static final Integer MAX_NUM = 500;
// 设置一个排号计数器
public static Integer count = 0;

// 创建一个内部类 实现runnable接口
class LineUp implements Runnable {
// 叫号逻辑实现
@Override
public void run() {
// 输出当前叫到的号码
call();
}
}

// 提供构造方法
public LineUp lineUp() {
return new LineUp();
}

public static void main(String[] args) {
BankLineUp bankLineUp = new BankLineUp();
new Thread(bankLineUp.lineUp(), "一号窗口").start();
new Thread(bankLineUp.lineUp(), "二号窗口").start();
new Thread(bankLineUp.lineUp(), "三号窗口").start();
}

// 通过synchronized修饰方法实现同步方法
public void call() {
while (true) {
synchronized (this) {
if (count < MAX_NUM) {
try {
// 通过休眠一秒 模拟系统延时
Thread.sleep(1l);
System.out.println("有请" + ++count + "号顾客到" + Thread.currentThread().getName() + "办理业务!");
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}

运行结果:19f8b5032b79a0f5abb148ba7baef43d.png
在我们将call方法的同步粒度从整个方法体变成方法中的一个代码块的时候,有效的解决了线程等待方法运行结束才能获得方法锁的问题。那么此时有个小的疑问困扰着我,同步方法和同步方法体使用的是否是统一把锁来控制同步代码的运行???

四、对象锁和类锁

1.对象锁的验证

1.对象锁的探索

为了验证我的疑问,首先需要写一个小的demo

public class ObjectLock {

public static void main(String[] args) {
ObjectLock objectLock = new ObjectLock();
new Thread(() -> {
objectLock.call();
}, "一号线程").start();
new Thread(() -> {
objectLock.speak();
}, "二号线程").start();
}

// 同步方法 通过死循环使同步方法不会退出
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + ":开始运行");
while (true) {
}
}

// 同步代码块
public void speak() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + ":开始运行");
// 通过sleep延长方法执行时间
Thread.sleep(100_000);
System.out.println(Thread.currentThread().getName() + ":运行结束");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

然后打开cmd窗口,输入jps命令,拿到此时执行线程的线程号ea003b4e3db72a3148fa8b5ceb25fec2.png
然后输入jstack pid 命令查看线程状态:51c63f63c670650e75f6cfe122159d43.png
在这里我们可以看到同步方法call()获取了<0x0000000780676b40>这把锁,而此时使用了同步代码块的方法speak()也在等待获取<0x0000000780676b40>这把锁,因此可以看出在一个对象中,同步方法和同步代码块使用的是同样的锁。在同步代码块中,我们传入了一个参数this,它代表了我们为当前的对象添加了锁,也就是给同步代码块上了对象锁。

类声明后,我们可以 new 出来很多的实例对象。这时候,每个实例在 JVM 中都有自己的引用地址和堆内存空间,这时候,我们就认为这些实例都是独立的个体,很显然,在实例上加的锁和其他的实例就没有关系,互不影响了。

2.对象锁的简介

对象锁也叫方法锁,是针对一个对象实例的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,而并不会对其他对象实例的锁产生任何影响。为了验证对象锁是否会影响其他对象这个事情,我们将main方法的对象实例进行更换

	public static void main(String[] args) {
ObjectLock objectLock = new ObjectLock();
ObjectLock objectLock2 = new ObjectLock();
new Thread(() -> {
objectLock.call();
}, "一号线程").start();
new Thread(() -> {
objectLock2.speak();
}, "二号线程").start();
}

运行结果:d11849fd114f668b70a1d5b5b2b82b51.png
此时二号线程可以无视一号线程持有锁进行的死循环而直接运行,也就证明了我们说的对象锁的影响范围仅为当前对象。或者我们还可以采用下面将参数this更换为其他对象的方式解决:

	public void speak() {
synchronized (new String()) {
try {
System.out.println(Thread.currentThread().getName() + ":开始运行");
// 通过sleep延长方法执行时间
Thread.sleep(100_000);
System.out.println(Thread.currentThread().getName() + ":运行结束");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

这里需要注意的一点:我在这里使用的是new String() 的方式来创建新的对象当做锁,如果此时直接传入的是一个字面量为:abc的字符串,那么此时该同步代码块所持有的将不再是对象锁,而是类锁。

2.类锁的验证

那么什么是类锁?按照惯例,我们还是写个小demo来做验证:

public class ObjectLock {
public static void main(String[] args) {
//分别创建两个对象来调用同步方法 以避免对象锁的干扰
ObjectLock objectLock01 = new ObjectLock();
ObjectLock objectLock02 = new ObjectLock();
new Thread(()->{objectLock01.test();},"一号线程").start();
new Thread(()->{objectLock02.test();},"二号线程").start();
}

public void test() {
synchronized (ObjectLock.class) {
System.out.println(Thread.currentThread().getName()+": start!");
while(true) {

}
}
}
}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

还是用jstack命令来观察:dcb6655be39f8a5bbd5c32f27bd4badc.png
此时二号线程和一号线程持有的锁相同,证明此时所有ObjectLock对象的实例都持有该锁。

类锁是加载类上的,而类信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的。

3.对象锁和类锁的区别

参考文章:https://zhuanlan.zhihu.com/p/98145713

1)对象锁

通常我们使用实例锁的方式有下面三种:

1、 锁住实体里的非静态变量:
非静态变量是实例自身变量,不会与其他实例共享,所以锁住实体内声明的非静态变量可以实现对象锁。锁住同一个变量的方法块共享同一把锁。ecfe02af51755260422afc1c1b053d5d.png

2、锁住 this 对象:
this 指的是当前对象实例本身,所以,所有使用 synchronized(this)方式的方法都共享同一把锁。bfd6a1c0095d070ae02df456d970c7c2.png3、直接锁非静态方法9c38bb55fbd2bb24b7df0f24a317736e.png

2)类锁

类锁是所有线程共享的锁,所以同一时刻,只能有一个线程使用加了锁的方法或方法体,不管是不是同一个实例。类锁主要应用在下面的情况中:1、锁住类中的静态变量
因为静态变量和类信息一样也是存在方法区的并且整个 JVM 只有一份,所以加在静态变量上可以达到类锁的目的。c8f432fcfc26aaadda447ad2906f5634.png2、直接在静态方法上加 synchronized

因为静态方法同样也是存在方法区的并且整个 JVM 只有一份,所以加在静态方法上可以达到类锁的目的。d2aabb2cf64e4071050da6776faf2f2d.png3、锁住 xxx.class

对当前类的 .class 属性加锁,可以实现类锁。25c443d27a1470fa71fd5857c4a662f5.png

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

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

相关文章

mysql 函数返回查询结果_MySQL数据库中常用查询函数简介

MYSQL中的常用函数count(*)---相当于统计表的行数&#xff0c;在统计结果的时候&#xff0c;不会忽略列值为NULL的记录。 select count(*) from yinxiong;Count(列名&#xff09;表示统计此列当中总行数&#xff08;不计算null所在的行&#xff09;Count(distinct 列名&#xf…

属性值动态调整_这可能是你见过最牛的CAD粗糙度动态块了!

好课推荐&#xff1a;零基础CAD&#xff1a;点我CAD家装&#xff1a;点我 周站长CAD&#xff1a;点我CAD机械&#xff1a;点我revit教程&#xff1a;点我CAD建筑&#xff1a;点我CAD三维&#xff1a;点我全屋定制&#xff1a;点我 ps教程&#xff1a;点我苹果版CAD:点我 3dmax教…

bootstrap mysql源码_Django+Bootstrap+Mysql 搭建个人博客 (六)

6.1.comments插件(1)安装pip install django-contrib-comments(02)settingsINSTALLED_APPS [django.contrib.sites,django_comments,]SITE_ID 1(3)website/urlurl(r^comments/, include(django_comments.urls)),(4)修改源码django_comments/abstracts.py第36行原代码site mode…

mysql 查看索引 命令_MySQL命令篇之库、表、索引、用户、视图及SELECT查询

大纲一、库管理二、表管理三、索引管理四、用户管理五、视图管理六、SELECT查询一、库管理(1)、创建数据库CREATE DATABASE db_name [CHARACTER SET [] charset_name] [COLLATE [] collation_name];mysql> CREATE DATABASE IF NOT EXISTS testdb CHARACTER SET gbk COLLATE …

mysql 数据修改记录日志_mysql对数据的更新操作记录在哪个日志中?

mysql对数据的更新操作记录在通用查询日志和二进制日志中。通用查询日志用来记录用户的所有操作&#xff0c;包括启动和关闭 MySQL 服务、更新语句和查询语句等&#xff1b;二进制日志会以二进制的形式记录数据库的各种操作&#xff0c;但不记录查询语句。(推荐教程&#xff1a…

mysql insert 二进制_MYSQL 插入二进制数的 2 种方法。

方法 1、insert into TableName set column ;方法 2、insert into TableName .... values(.....);------------------------------------------------------------------------------------------------------------------------------------------create table T(x bit(8));方…

mysql怎么分组查询所有数据库_Mysql-4 分组查询与子查询

1、查询结果的分组操作a、分组允许把数据分为多个组&#xff0c;以便能对每个组进行聚集计算b、分组是在select语句的group by 子句中建立的注意&#xff1a;group by 只是创建分组&#xff0c;但并不保证分组里面的数据的排列顺序&#xff0c;需要使用order by 子句对分组里面…

mysql update upper_MySQL数据处理函数upper、abs、date

1. 数据处理函数文本处理函数upper()转换大写函数SQL> select vend_name, upper(vend_name) as vend_name_upcase from vendors order by vend_name;soundex()发音类似函数SQL> select cust_name, cust_contact from customers where cust_contact Y. Lie;SQL> selec…

java 获取类方法_Java之反射机制三:获取类的方法

一.实体类BigDog.javapackage reflex;public class BigDog extends Dog {private Integer age;public String name;public BigDog(){}private void getDog(){}private BigDog(Integer age, String name) {this.age age;this.name name;}public String getName() {return name…

java语言50到100之间素数和_用JAVA语言编写一程序,求100以内的所有素数

满意答案fdewj5902017.02.13采纳率&#xff1a;40% 等级&#xff1a;9已帮助&#xff1a;316人12345678910111213141516171819public static void main(String[] args) { for (int i 2; i < 100; i) { int temp (int) Math.sqrt(i); …

java矩阵面积_Java基础 矩阵面积

提供 数据结构与算法题目 的平台是LintCode&#xff0c;参考链接是&#xff1a;http://www.lintcode.com/zh-cn/问题描述&#xff1a;参考代码&#xff1a;public class Rectangle {/** Define two public attributes width and height of type int.*/// write your code herep…

java判断线程是否wait_Java并发编程之线程间通讯(上)wait/notify机制

线程间通信如果一个线程从头到尾执行完也不和别的线程打交道的话&#xff0c;那就不会有各种安全性问题了。但是协作越来越成为社会发展的大势&#xff0c;一个大任务拆成若干个小任务之后&#xff0c;各个小任务之间可能也需要相互协作最终才能执行完整个大任务。所以各个线程…

python土味情话_Python 将土味情话语录设置为桌面壁纸

本文编写于 128 天前&#xff0c;最后修改于 128 天前&#xff0c;其中某些信息可能已经过时。41041-3yfokd0irbe.png38220-tlrmwji3zwo.pngimport osimport tempfileimport timeimport requestsimport win32apiimport win32conimport win32guifrom PIL import Image, ImageDra…

Day70力扣打卡

打卡记录 收集足够苹果的最小花园周长&#xff08;找规律 二分&#xff09; 链接 class Solution:def minimumPerimeter(self, neededApples: int) -> int:l, r 1, 10 ** 5while l < r:mid (l r) >> 1if 2 * (2 * (mid ** 3) 3 * (mid ** 2) mid) > nee…

java计数器策略模式_java设计模式(二十一)--策略模式

对于策略模式,我在很多面试题上看到过考察这一类的问题,这种模式也的确比较好用。 我感觉这种模式就是将不同实现的方法放到一个接口中,然后通过实现这个接口来实现不同的运行结果,这种模式有三部分构成: 策略接口 策略实现类 策略作用类(使用策略的类) 网络上的专业解释:此模式…

linux setuid函数_setuid函数解析

在讨论这个setuid函数之前&#xff0c;我们首先要了解的一个东西就是内核为每个进程维护的三个UID值。这三个UID分别是实际用户ID(real uid)、有效用户ID(effective uid)、保存的设置用户ID(saved set-user-ID)。首先说这个实际用户ID&#xff0c;就是我们当前以哪个用户登录了…

java中asl_带你认识绕不开的ASLR

微软从windows vista/windows server 2008(kernel version 6.0)开始采用ASLR技术&#xff0c;主要目的是为了防止缓冲区溢出ASLR技术会使PE文件每次加载到内存的起始地址随机变化&#xff0c;并且进程的栈和堆的起始地址也会随机改变。ASLR(Address space layout randomization…

Java 捕获 mybatis异常_3 springboot集成mybatis和全局异常捕获

mybatis有两种方式&#xff0c;一种是基于XML&#xff0c;一种是基于注解springboot集成mybatis首先先创建表&#xff0c;这里都简化了DROP TABLE IF EXISTS user;CREATE TABLE user (id int(11) NOT NULL auto_increment,username varchar(255) default NULL,PRIMARY KEY (id)…

java applet 访问文件_使用JavaApplet访问数据库

使用Java Applet访问数据库学习任何的程序语言&#xff0c;当然都得与数据库&#xff0c;Java刚刚诞生的时候&#xff0c;对数据库的支持并不是很好&#xff0c;经过这几年的发展&#xff0c;它对数据库的支持也已经完全达到了成熟的境地。由于这里主要是介绍Java Applet小程序…

与java线程有关的,线程多少和什么有关?大神们表示有话要说!

原标题&#xff1a;线程多少和什么有关&#xff1f;大神们表示有话要说&#xff01;来源&#xff1a;importnew.com/10780.htmlEddie的回答:Charlie Martin的回答:benjismith的回答:Neil Coffey的回答:McGovernTheory在StackOverflow提了这样一个问题:Java虚拟机最多支持多少个…