🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄
🌹简历模板、学习资料、面试题库、技术互助
🌹文末获取联系方式 📝
往期热门专栏回顾
专栏 | 描述 |
---|---|
Java项目实战 | 介绍Java组件安装、使用;手写框架等 |
Aws服务器实战 | Aws Linux服务器上操作nginx、git、JDK、Vue |
Java微服务实战 | Java 微服务实战,Spring Cloud Netflix套件、Spring Cloud Alibaba套件、Seata、gateway、shadingjdbc等实战操作 |
Java基础篇 | Java基础闲聊,已出HashMap、String、StringBuffer等源码分析,JVM分析,持续更新中 |
Springboot篇 | 从创建Springboot项目,到加载数据库、静态资源、输出RestFul接口、跨越问题解决到统一返回、全局异常处理、Swagger文档 |
Spring MVC篇 | 从创建Spring MVC项目,到加载数据库、静态资源、输出RestFul接口、跨越问题解决到统一返回 |
华为云服务器实战 | 华为云Linux服务器上操作nginx、git、JDK、Vue等,以及使用宝塔运维操作添加Html网页、部署Springboot项目/Vue项目等 |
Java爬虫 | 通过Java+Selenium+GoogleWebDriver 模拟真人网页操作爬取花瓣网图片、bing搜索图片等 |
Vue实战 | 讲解Vue3的安装、环境配置,基本语法、循环语句、生命周期、路由设置、组件、axios交互、Element-ui的使用等 |
Spring | 讲解Spring(Bean)概念、IOC、AOP、集成jdbcTemplate/redis/事务等 |
系列文章目录
第一章 Java线程池技术应用
第二章 CountDownLatch和Semaphone的应用
第三章 Spring Cloud 简介
第四章 Spring Cloud Netflix 之 Eureka
第五章 Spring Cloud Netflix 之 Ribbon
第六章 Spring Cloud 之 OpenFeign
第七章 Spring Cloud 之 GateWay
第八章 Spring Cloud Netflix 之 Hystrix
第九章 代码管理gitlab 使用
第十章 SpringCloud Alibaba 之 Nacos discovery
第十一章 SpringCloud Alibaba 之 Nacos Config
第十二章 Spring Cloud Alibaba 之 Sentinel
第十三章 JWT
第十四章 RabbitMQ应用
第十五章 RabbitMQ 延迟队列
第十六章 spring-cloud-stream
第十七章 Windows系统安装Redis、配置环境变量
第十八章 查看、修改Redis配置,介绍Redis类型
第十九章 Redis RDB AOF
第二十章 Spring boot 操作 Redis
第二十一章 Java多线程安全与锁
文章目录
- 往期热门专栏回顾
- 系列文章目录
- 前言
- 1、Java多线程安全与锁
- 1.1、多线程安全问题
- 1.2、线程安全问题三方面
- 1.2.1、共享数据在jvm中的表现
- 1.2.2、内存抽象模型
- 1.3、Java中的锁
- 1.3.1、同一个JVM内锁
- 1.3.1.1、synchronized 关键字
- 1.3.1.2、volatile关键字
- 1.3.1.3、volatile和synchronized区别
- 1.3.1.4、volatile能替代synchronized吗
前言
本章节介绍Java多线程安全与锁
1、Java多线程安全与锁
1.1、多线程安全问题
当多个线程同时操作同一个数据时,可能会出现数据不一样的情况,这就是线程安全问题。线程安全机制用于保证多个线程访问数据时的一致性.
1.2、线程安全问题三方面
- 原子性
一个线程对数据的操作对于其他的线程来说是原子的,要么操作完成,要么什么也没做;当一个线程在操作数据时,不允许其他的线程参与. - 可见性
线程对共享数据的访问是否对其他的线程可见 - 有序性
指令重排序与内存重排序指令重排序是指CPU执行指令的顺序与程序的顺序可能不一样; 内存重排序是指内存访问顺序与感知顺序可能不一样。
1.2.1、共享数据在jvm中的表现
- 每个线程都有独立的线程栈,不能相互访问线程栈的内容
- 所有线程都能访问堆中的数据
- 多个线程访问同事访问一个对象实例或者静态变量时,会出现安全问题。
1.2.2、内存抽象模型
- 每个线程都有自己独立的工作内存
- 线程1无法访问线程2的工作内存
- 线程在访问共享数据时,会把主内存中的共享变量复制到自己的工作内存中,线程操作的是工作内存中数据的副本
1.3、Java中的锁
(多线程并发操作同一个数据可能会引发线程安全问题)
锁就是把多个线程对数据的并发操作转换为串行操作。
1.3.1、同一个JVM内锁
1.3.1.1、synchronized 关键字
- 同步代码块
- 同步实例方法
- 同步类的静态方法
当线程释放锁时,JMM(java内存模型)会把该线程对应的工作内存中的共享变量刷新到主内存中。当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内 存中读取共享变量。
**代码 使用synchronized **
package com.xxxx.reids.thread;
import lombok.AllArgsConstructor;
import lombok.Data;
/***
* @title Account
* @desctption 账户
* @author Kelvin
* @create 2023/5/29 11:43
**/
@Data
@AllArgsConstructor
public class Account {
/**
* 余额
*/
private Integer balance;
}
package com.xxxx.reids.thread;
import lombok.AllArgsConstructor;
import lombok.Data;
/***
* @title Person
* @desctption 人员信息
* @author Kelvin
* @create 2023/5/29 11:44
**/
@Data
@AllArgsConstructor
public class Person {
/**
* 姓名
*/
private String name;
/**
* 取款金额
*/
private Integer drawAccount;
}
package com.hqyj.reids.thread;
import java.util.concurrent.locks.Lock;
/***
* @title Draw
* @desctption 取款
* @author Kelvin
* @create 2023/5/29 11:49
**/
public class Draw implements Runnable{
private Account account;
private Person person;
public Draw(Account account,Person person){
this.account = account;
this.person = person;
}
@Override
public void run() {
synchronized (this.account){
while (true){
//余额
Integer balance = this.account.getBalance();
if(balance < this.person.getDrawAccount()){
//余额不足
System.out.println(this.person.getName() + ",账户余额不足");
break;
}else{
/* try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//取款,减余额
this.account.setBalance(balance - this.person.getDrawAccount());
System.out.println(this.person.getName() + "取了"+this.person.getDrawAccount()+"钱,账户余额:" + this.account.getBalance());
}
}
}
}
}
测试
package com.xxxx.redis;
import com.xxxx.reids.thread.Account;
import com.xxxx.reids.thread.Draw;
import com.xxxx.reids.thread.Person;
import org.junit.Test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/***
* @title ThreadLockTest
* @desctption 测试类
* @author Kelvin
* @create 2023/5/29 11:50
**/
public class ThreadLockTest {
@Test
public void lock() {
Lock lock = new ReentrantLock();
Account account = new Account(100);
Person zhangSan = new Person("张三", 20);
Person liSi = new Person("李四", 15);
new Thread(new Draw(account,zhangSan)).start();
new Thread(new Draw(account,liSi)).start();
}
}
**代码2 使用Lock **
package com.hqyj.reids.thread;
import java.util.concurrent.locks.Lock;
/***
* @title Draw
* @desctption 取款
* @author Kelvin
* @create 2023/5/29 11:49
**/
public class Draw implements Runnable{
private Account account;
private Person person;
private Lock lock ;
public Draw(Account account,Person person,Lock lock){
this.account = account;
this.person = person;
this.lock = lock;
}
@Override
public void run() {
lock.lock();
//取款操作
Integer drawAccmount = this.person.getDrawAccount();
Integer balanceInit = this.account.getBalance();
//System.out.print("原始值:" + balanceInit + " | ");
if(balanceInit < drawAccmount){
//余额不足,不给取款
System.out.println("取款余额不足");
//break;
}else{
Integer balance = balanceInit - drawAccmount;
this.account.setBalance(balance);
System.out.println(this.person.getName() + "成功取款:" + drawAccmount + ",账户余额:" + balance);
}
lock.unlock();
}
}
import com.xxxx.reids.thread.Account;
import com.xxxx.reids.thread.Draw;
import com.xxxx.reids.thread.Person;
import org.junit.Test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/***
* @title ThreadLockTest
* @desctption 测试类
* @author Kelvin
* @create 2023/5/29 11:50
**/
public class ThreadLockTest {
@Test
public void lock() {
Lock lock = new ReentrantLock();
Account account = new Account(110);
Person zhangSan = new Person("张三", 17);
Person liSi = new Person("李四", 12);
new Thread(new Draw(account,zhangSan,lock)).start();
new Thread(new Draw(account,liSi,lock)).start();
new Thread(new Draw(account,zhangSan,lock)).start();
new Thread(new Draw(account,liSi,lock)).start();
new Thread(new Draw(account,zhangSan,lock)).start();
new Thread(new Draw(account,liSi,lock)).start();
new Thread(new Draw(account,zhangSan,lock)).start();
new Thread(new Draw(account,liSi,lock)).start();
}
}
死锁的例子
Java 死锁产生的四个必要条件:
- 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
/***
* @title DieLock
* @desctption 死锁案例
* @author Kelvin
* @create 2023/5/29 15:36
**/
import java.util.concurrent.TimeUnit;
public class DieLock {
private static Object object1 = new Object();
private static Object object2 = new Object();
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
synchronized (object1){
System.out.println("Thread1 get object1");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println("thread1 get object2");
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (object2){
System.out.println("Thread2 get object2");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1){
System.out.println("thread2 get object1");
}
}
}
}.start();
}
}
1.3.1.2、volatile关键字
volatile是java虚拟机提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 保证有序性
什么时候去使⽤Volatile?
- 某个属性被多个线程共享,其中有⼀个线程修改了此属性,其他线程可以⽴即得到修改后的值,⽐如作为触发器,状态量标记,实现轻量级同步
- volatile可以在单例双重检查中实现可⻅性和禁⽌指令重排序,可以解决单例双重检查对象初始化代码执⾏乱序问题,从⽽保证安全性。
public class SingletonObject {
private static volatile SingletonObject singletonObject = new SingletonObject();
private SingletonObject(){
}
public static synchronized SingletonObject getInstance(){
if(singletonObject == null){
singletonObject = new SingletonObject();
}
return singletonObject;
}
}
1.3.1.3、volatile和synchronized区别
- volatile只能修饰实例变量和类变量,只能作⽤于属性,⽽synchronized可以修饰⽅法和代码块。
- volatile保证数据的可⻅性,⽤于禁⽌指令重排序,但是不保证原⼦性;⽽synchronized是⼀种互斥的机制。
- volatile属性的读写操作都是⽆锁的,不需要花费时间在获取锁和释放锁上,所以说它是低成本的
- volatile可以看做是轻量版的synchronized,volatile不保证原⼦性,但是如果是对⼀个共享变量进⾏多个线程的赋值,⽽没有其他的操作,那么就可以⽤volatile来代替synchronized,因为赋值本身是有原⼦性的,⽽volatile⼜保证了可⻅性,所以就可以保证线程安全了。
1.3.1.4、volatile能替代synchronized吗
- volatile属性的读写操作都是⽆锁的,
- 它没有提供原⼦性和互斥性,它不能替代synchronized
资料获取,更多粉丝福利,关注下方公众号获取