Java 线程安全 与 锁
Java 线程安全 与 锁
多线程内存模型
- 线程私有栈内存
- 每个线程 私有的内存区域
- 进程公有堆内存
- 同一个进程 共有的内存区域
为什么会有线程安全问题?
- 多个线程同时具有对同一资源的操作权限,又发生了同时对该资源进行读取、写入的情况,那么就会出现重复操作的情况
如何解决线程安全问题呢? 加锁
什么是锁?
锁就是对于操作资源的一种权限
锁可以做什么?
对于一个资源加锁后,每次只能有一个线程对该资源进行操作,当该线程操作结束后,才会解锁。
解锁之后,所有的线程获得竞争此资源的机会。
什么情况下需要加锁?
- 读读 不需要加锁
- 写写 需要加锁
- 读写 需要加锁
加锁的两种方式(synchronized关键字与Lock对象)
第一种:synchronized关键字
方法前加synchronized关键字
- 功能:线程进入用synchronized声明的方法时就上锁,方法执行完自动解锁,锁的是当前类的对象
- 调用synchronized声明的方法一定是排队运行的
- 当A线程 调用object对象的synchronized声明的X方法时
- B线程可以调用其他非synchronized声明的方法
- B线程不能调用其他synchronized声明的非X方法
synchronized锁重入
- 锁重入的概念:自己可以重复获得自己的内部锁。即synchronized声明的方法,可以调用本对象的其他synchronized方法。
- 锁重入支持继承的环境,即子类的synchronized方法也可以调用父类的synchronized方法。
synchronized同步代码块
synchronized关键字与synchronized代码块的区别
- synchronized声明的方法是将当前对象作为锁
- synchronized代码块是将任意对象作为锁
当两个线程访问同一个对象的synchronized代码块时,只有一个线程可以得到执行,另一个线程只能等待当前线程执行完才能执行。
- 一半同步,一半异步
- 不在synchronized代码块中就是异步执行,在synchronized代码块中就是同步执行
- 一半同步,一半异步
下面对“一半同步,一半异步”进行代码验证
- 创建项目ltl0002 ,文件Task的代码如下:
package ltl0002;
public class Task {
public void doTask(){
for (int i = 0; i < 100; i++) {
System.out.println("no synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1));
}
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println("synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1));
}
}
}
}
- 两个线程类代码
package ltl0002;
public class MyThread1 implements Runnable{
private Task task = new Task();
public MyThread1(Task task){
this.task = task;
}
@Override
public void run() {
task.doTask();
}
}
package ltl0002;
public class MyThread2 implements Runnable{
private Task task = new Task();
public MyThread2(Task task){
this.task = task;
}
@Override
public void run() {
task.doTask();
}
}
文件Run.java代码如下:
package ltl0002;
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 myThread1 = new MyThread1(task);
MyThread2 myThread2 = new MyThread2(task);
Thread tr1 = new Thread(myThread1);
Thread tr2 = new Thread(myThread2);
tr1.start();
tr2.start();
}
}
程序运行结果如图所示

进入synchronized代码块之后,排队运行,运行结果如图所示

在第一张图我们可以看到,线程0 和 1交叉输出,说明是异步进行,而在第二张图可以看出线程0运行完之后,线程1才运行,说明它们是同步运行,验证完毕。
- 现有三个线程,线程一对num进行修改,线程二三对num进行读取,如何可以实现,线程一与线程二三同步执行,而线程二三异步执行呢?
现在创建项目ltl0003进行测试,Number文件代码如下
package ltl0003;
/**
* @author liTianLu
* @Date 2022/4/23 15:53
* @purpose 成员变量有int num,以及get set方法
*/
public class Number {
private int num;
private boolean change = false;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isChangeing(){
return change;
}
public void setChange(boolean change) {
this.change = change;
}
}
两个线程类的代码如下:
package ltl0003;
/**
* @author liTianLu
* @Date 2022/4/23 15:36
* @purpose 更改num的值
*/
public class MyThread01 implements Runnable{
static int num = 0;
Number number;
public MyThread01(Number num ){
this.number = num ;
}
@Override
public void run() {
synchronized (this){
number.setChange(true);
for (int i = 0; i < 10000; i++) {
number.setNum(num++);
}
number.setChange(false);
}
}
}
package ltl0003;
import static java.lang.Thread.sleep;
/**
* @author liTianLu
* @Date 2022/4/23 15:35
* @purpose 读取num的值
*/
public class MyThread02 implements Runnable{
Number number;
public MyThread02(Number num ){
this.number = num ;
}
@Override
public void run() {
for (int i = 0; i < 1000 ; i++) {
//如果number正在更改,就休眠1ms
while(number.isChangeing()){
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"的输出为: num = " + number.getNum());
}
}
}
主函数文件Run代码如下:
package ltl0003;
/**
* @author liTianLu
* @Date 2022/4/23 15:15
* @purpose 解决锁问题 线程一对num进行修改,线程二三对num进行读取,此代码要实现:线程一与线程二三同步执行,而线程二三异步执行。
*/
public class Run {
public static void main(String[] args) {
Number number = new Number();
number.setNum(0);
MyThread01 myThread01 = new MyThread01(number);
MyThread02 myThread02 = new MyThread02(number);
Thread tr1 = new Thread(myThread01);
Thread tr2 = new Thread(myThread02);
Thread tr3 = new Thread(myThread02);
tr1.start();
tr2.start();
tr3.start();
}
}
实验结果如图所示

我们发现,线程2/3执行的时候,线程1已经执行完毕,且线程2、3异步进行。
第二种:Lock对象的使用
- ReentrantLock类可以达到与synchronized同样的效果。
- 用法:
ReentrantLock lock = new ReentrantLock ();
lock.lock();//加锁
lock.unlock();//解锁
//使用try catch finally 可以确保finally 中的代码执行,在finally中解锁
try{
while(true){
lock.lock ();
//操作代码
}
}catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock ();
}
Java 线程安全 与 锁的更多相关文章
- Java线程安全与锁优化
线程安全的严谨定义: 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交题执行,也不需要进行额外的同步,或者调用方法进行其他任何操作,调用这个对象的行为都可以或者正确的结果,那么这 ...
- 【Java线程安全】锁
Java都有哪些锁? synchronized 和 reentranlock是最常见的,其中前者又JVM提供实现,后者有专门对应的java.util.concurrent包提供:同时后者功能更加丰富. ...
- Java线程同步与锁
一.synchronized synchronized锁什么?锁对象.可能锁对象包括: this, 临界资源对象,Class类对象. 1,同步方法 synchronized T methodName( ...
- 【Thread】java线程之对象锁、类锁、线程安全
说明: 1.个人技术也不咋滴.也没在项目中写过线程,以下全是根据自己的理解写的.所以,仅供参考及希望指出不同的观点. 2.其实想把代码的github贴出来,但还是推荐在初学的您多亲自写一下,就没贴出来 ...
- Java线程安全与锁优化,锁消除,锁粗化,锁升级
线程安全的定义 来自<Java高并发实战>"当多个线程访问一个对象的时候,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法的时候进行任何 ...
- Java线程(二):线程同步synchronized和volatile
上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...
- Java线程专栏文章汇总(转)
原文:http://blog.csdn.net/ghsau/article/details/17609747 JDK5.0之前传统线程 Java线程(一):线程安全与不安全 Java线程 ...
- Java线程专栏文章汇总
转载自 http://blog.csdn.net/ghsau/article/details/17609747 JDK5.0之前传统线程 Java线程(一):线程安全与不安全 J ...
- java线程之二(synchronize和volatile方法)
要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现.拿上篇博文中的例子来说明,在多个线程之间共享了Count ...
随机推荐
- hdu5322 Hope(dp+FFT+分治)
hdu5322 Hope(dp+FFT+分治) hdu 题目大意:n个数的排列,每个数向后面第一个大于它的点连边,排列的权值为每个联通块大小的平方,求所有排列的权值和. 思路: 考虑直接设dp[i]表 ...
- Java9的模块化是什么
Java9新特性中的模块化到底是什么 Java9中的一个重大特性是增加了一种新型的程序设计组件 - 模块. 官方对模块的定义为:一个被命名的,代码和数据的自描述集合.( the module, whi ...
- Java的重载以及与重写的区别
一.什么是方法重载 方法的重载就是在同一个类中,有着若干个名字相同的方法.在具体调用这些方法的时候,通过传递参数的不同来调用这些重载方法. 二.为什么需要方法重载 方法名的定义需要做到见名知意,功能类 ...
- Java 中计算注意!!!
* 使用BigDecimal需要注意的事项: * 1.两个BigDecimal值不能使用" +, -, *, / " 进行加减乘除,要使用" add, substrac ...
- Python包装器
def func(): print("func body") def f1(arg): print("f1:",arg) def wrapper(b): pri ...
- 什么是线程池(thread pool)?
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内 存资源或者其它更多资源.在 Java 中更是如此,虚拟机将试图跟踪每一个对象, 以便能够在对象销毁后进行垃圾回收.所以提高服务程 ...
- java-設計模式-抽象工場模式
抽象工廠模式AbstractFactory 一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类. 工廠方法模式中考虑的是一类产品的生产,如畜牧场只养动物.电视机厂只生产电视机,同种类 ...
- Java 面试问题列表包含的主题?
多线程,并发及线程基础 数据类型转换的基本原则 垃圾回收(GC) Java 集合框架 数组 字符串 GOF 设计模式 SOLID 抽象类与接口 Java 基础,如 equals 和 hashcode ...
- python面向对象--快速入门
1面向对象基本用法 1.1基本格式 class 类名: def __init__(self,x): self.x = x def 方法名字 (self): #函数在类里称为方法,self就是固定参数, ...
- Spring工作原理:初识SpringMVC
1.SpringMVC简介 SpringMVC是Spring框架的一个模块.SpringMVC和Spring无需通过中间层进行整合.是一个轻量级的,基于请求响应的MVC框架. 2.1.什么是MVC? ...