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,一经查实,立即删除!

相关文章

mac vscode 背景半透明_给 vscode 添加半透明毛玻璃效果

Electron 可以调用 MacOS 的毛玻璃效果&#xff0c;下面让我来带你给 VScode 添加毛玻璃效果。2. 创建一个 CSS 文件(CSS 是我自己写的&#xff0c;你可以按需修改)html {background: transparent !important;}.scroll-decoration {box-shadow: none !important;}.minimap {opa…

python队列精灵对战_python队列Queue

python2, 参考思路.QueueQueue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构&#xff0c;即队列&#xff0c;用来在生产者和消费者线程之间的信息传递基本FIFO队列class Queue.Queue(maxsize0)FIFO即First in First Out,先进先出…

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教…

mysql like_MySQL LIKE:模糊查询

在 MySQL 中&#xff0c;LIKE 关键字主要用于搜索匹配字段中的指定内容。其语法格式如下&#xff1a;[NOT] LIKE 字符串其中&#xff1a;NOT &#xff1a;可选参数&#xff0c;字段中的内容与指定的字符串不匹配时满足条件。字符串&#xff1a;指定用来匹配的字符串。“字符串…

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 jdbc 单利_java单利模式设计

java中单例模式是一种常见的设计模式&#xff0c;单例模式分三种&#xff1a;懒汉式单例、饿汉式单例、登记式单例三种。Singleton是一种创建型模式&#xff0c;指某个类采用Singleton模式&#xff0c;则在这个类被创建后&#xff0c;只可能产生一个实例供外部访问&#xff0c;…

vb 读取mysql所有表名_vb怎么列举出一个mdb数据库里面所有表名?

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼Private Sub Command1_Click()Dim conn As New ADODB.ConnectionDim rs As New ADODB.Recordset打开指定的服务器sql "ProviderSQLOLEDB.1;Persist Security InfoFalse;User IDfood;Password123456;Data Source192.168.1.251…

mysql行级锁升级_mysql innodb 行级锁升级

创建数据表test&#xff0c;表定义如下所示&#xff1a;CREATE TABLE test (id int(11) NOT NULL AUTO_INCREMENT,name varchar(20) NOT NULL,PRIMARY KEY (id),KEY idx_name (name)) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETlatin1;创建测试数据集&#xff0c;插入SQL语…

mysql触发器可以使用正则表达式,是否可以使用正则表达式在MySQL中强制执行数据检查...

Suppose I have an attribute called phone number and I would like to enforce certain validity on the entries to this field. Can I use regular expression for this purpose, since Regular Expression is very flexible at defining constraints.解决方案Yes, you can…

c 调用mysql密码为空_C语言连MySQL - osc_srnunz15的个人空间 - OSCHINA - 中文开源技术交流社区...

连接例程用C语言连接MySQL数据库包含两个步骤“初始化一个连接句柄结构&#xff1b;实际进行连接。初始化连接句柄#includeMYSQL *mysql_init(MYSQL *);通常传递NULL给这个例程&#xff0c;它会返回一个指向新分配的连接句柄结构的指针。如果传递一个已有的结构&#xff0c;它将…

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

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

mysql 多列union_Mysql联合查询UNION和UNION ALL的使用介绍

一、UNION和UNION ALL的作用和语法UNION 用于合并两个或多个 SELECT 语句的结果集&#xff0c;并消去表中任何重复行。UNION 内部的 SELECT 语句必须拥有相同数量的列&#xff0c;列也必须拥有相似的数据类型。同时&#xff0c;每条 SELECT 语句中的列的顺序必须相同.SQL UNION…

mysql导数据出指定数量_mysql导出指定数据或部份数据的方法

这个时候mysqldump可能就不大好使了&#xff0c;使用下面的方法则可以解决这个问题。方法一、insert和select结合使用1、新建一个表&#xff0c;比如new-table&#xff0c;包含所要导出的字段的名称&#xff0c;比如a,b,c2、使用insert into new-table (a,b,c) select a,b,c fr…

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…

mysql与django交互_django与mysql交互

查看ubuntu已安装的所有软件&#xff1a;查看软件安装的路径查看开机启动的软件&#xff0c;需要额外安装插件&#xff1a;安装mysql&#xff1a;django setting配置&#xff1a;在model模块中添加如下建表语句&#xff1a;vi app/models.pymodel模块在app中&#xff0c;其中定…

深度系统如何安装mysql_deepin 安装mysql apache

# 如果MYSQL安装成功&#xff0c;就会有my-medium.cnfcp /usr/local/src/mysql-5.5.17/support-files/my-medium.cnf/etc/my.cnf# 设置权限chmod x /usr/local/mysqlchown -R mysql:mysql /usr/local/mysql# 配置开机自启动cp support-files/mysql.server /etc/init.d/mysqldch…