Java-锁相关

news2024/10/6 0:26:01

线程不安全的原因

1.调度器随机调度,抢占式执行(无能为力)

举个例子 有一个int变量 叫count 就俩线程同时count++一万次 结果应该为两万 可多次运行程序 这结果每次都不一样(而且小于2w) 是为什么呢

因为count++这行代码是分三步运行的

 load 把数据读到cpu

 add  在cpu寄存器实现加法运算

 save 把运算完成的数据存回cpu

如果count为0 load(线程1) load(线程2) add(线程1) add(线程2) save(线程1) save(线程2) 他们load的时候count都为0 而且save的时候都把1存在了内存中 所以两次运算 count也只是1

2.多个线程修改一个变量(部分规避)

3.修改操作不是原子性的(锁操作)

4.内存可见性(锁操作)

5.指令重排序

后两种都是编译器/JVM/操作系统 误判了的原因 把不该优化的地方给优化了 就导致了bug的出现

可以通过volatile关键字解决(相当于禁止了编译器进行优化)

 synchronized加锁的几种方式

先说下加锁的意义,加锁就是:这个被加锁的线程结束,,其他线程才能拿到该资源进行执行线程!

还是用上面的count++操作举例子,如果这个线程被加锁了,那它的运行逻辑就是

(线程1) load add save (线程2)load add save   也就是说只有当这个线程1完成之后,才能运行线程2

public class Main {	
	 private static int a = 0;
	 private static int b = 0;
	 private static final int count = 10_0000;
	public synchronized static void increase(){
        for (int i = 0; i < count; i++) {
            a++;
        }
    }
	public static void increase1(){
        for (int i = 0; i < count; i++) {
            b++;
        }
    }
	 public static void main(String[] args)throws InterruptedException{
              Thread t1 = new Thread(()->{
            	  increase();
            	  increase1();
              });
              Thread t2 = new Thread(()->{
            	  increase();
            	  increase1();
              });
              t1.start();
              t2.start();
              t1.join();
              t2.join();
              System.out.println("a="+a);
              System.out.println("b="+b);
	    }
}

 (要加join  main也是一个线程(主线程) 不加join的话 它会在start之后 运行打印)

我们可以看出 加锁的方法 运行正确了 没加锁的方法就不对 

1. 修饰普通方法 相当于对this加锁

Public synchronized void increase(){

代码块

}

2.修饰静态方法,相当于对类对象加锁

Public synchronized static void increase(){

代码块

}

3.修饰代码块

public void method(){

     Synchronized(this){

代码块}

}

这个this是一个参数,是指对某个对象加锁,可以以实例对象,或者.class作为参数

 wait和notify

wait

调用wait的线程会进入阻塞等待状态,同时会释放锁(所以wait要和synchronized搭配使用)

进入调用wait后的流程

1.释放锁

2.等待通知(notify)

3.当通知到达之后 就会被唤醒 并且尝试重新获取锁

notify

唤醒处于wait状态的线程(也要和synchronized搭配使用)

举个例子

ublic static void main(String[] args)throws InterruptedException{
              Object lock = new Object();
              Thread t1 = new Thread(()->{
            	  synchronized (lock) {		  
                	  try {
                		System.out.println("准备调用wait");
						lock.wait();
						System.out.println("调用结束");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}                	  
				}	  
              });
              t1.start();
              System.out.println("准备notify");
              synchronized (lock) {
            	  lock.notify();
              }     
              System.out.println("notify结束");
             
	    }

 运行结果为:

我们可以看到是在notify结束之后才输出调用结束,也就是notify之后wait的线程才可以继续运行

可是为什么准备notify是在准备调用wait之前呢,明明t1.start()是在打印准备notify之前调用的,

因为main本身也是一个线程(主线程),在t1线程运行的时候主线程也在运行,打印notify又没被加锁

所以他们的调度顺序就是随机的了

 更极端的情况:

我们可以看到"调用结束"甚至没被输出,主线程都运行结束了,都没调度到t1线程的输出"调用结束",为了防止这种情况,可以加一个join,在主线程的最后加join,有效防止这种情况发生 

 顺便说下synchronized括号里面的参数和调用notify的对象必须是一个 否则会报错 

就像这个写法:

notify和wait应该在不同的线程中,因为这个线程wait了只能让其他线程notify(唤醒它)

如果有多个线程被wait,那notify会随机唤醒其中一个(notifyall可以唤醒全部线程)(同一个(被)锁对象),

同步和异步

同步 调用者自己来负责获取调用结果

异步 调用者自己不负责 被调用者主动推送

 常见锁策略

乐观锁&悲观锁

乐观锁:乐观的认为操作数据的时候非常乐观,认为别人不会同时修改数据,因此乐观锁默认是不会上锁的,只有在执行更新的时候才会去判断在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。

悲观锁:操作数据的时候比较悲观,认为别人一定会同时修改数据,因此悲观锁在操作数据时是直接把数据上锁,直到操作完成之后才会释放锁,在上锁期间其他人不能操作数据。

读取频繁使用乐观锁(多线程读 不设计修改 线程安全),写入频繁使用悲观锁

synchronized既是悲观锁,又是乐观锁,当前锁冲突概率不大 以乐观锁方式运行,一旦发现冲突概率大了 就以悲观锁方式运行

普通互斥锁&读写锁 

普通互斥锁 两个加锁操作会发生竞争

读写锁:把加锁操作细化了加锁分成了 加读锁 加写锁

a尝试加读锁

b尝试加读锁

ab不产生竞争 锁相当于没加

a尝试加读锁

b尝试加写锁 ab产生竞争 和普通锁没区别

synchronized是普通互斥锁

重量级锁&轻量级锁

重量级锁 锁开销大 做的工作比较多

轻量级锁 锁开销小 做的工作比较少

悲观锁经常是重量级锁

乐观锁经常是轻量级锁(不绝对)

sychronized是自适应的锁 既是重量级又是轻量级

公平锁&非公平锁

符合先来后到的规则才是公平,非公平锁 机会均等反而是不公平的

synchronized是非公平锁

自旋锁&挂起等待锁

自旋锁(轻量级锁的具体实现 乐观锁)

 当发现冲突的时候 不会挂起等待 会迅速再来尝试看这个锁能不能获取到

1.一旦锁被释放 就可以第一时间获取到

2.如果锁一直不释放 就会消耗大量的CPU

挂起等待锁 (重量级锁 悲观锁)

发现锁冲突就挂起等待

1.一旦锁被释放不能第一时间获取到

2.在锁被其他线程占用的时候 会放弃cpu资源

sychronized作为轻量级锁时 内部是自旋锁

sychronized作为重量级锁时 内部是挂起等待锁

可重入锁&不可重入锁

可重入锁:在内部记录这个锁是哪个线程获取到的 如果发现当前加锁的线程和持有锁的线程是用一个 则不挂起等待

同时在内部引入计数器 记录第几次加锁控制什么时候释放锁(不会提前释放锁)

不可重入锁:

private synchronized static void func(){

      func1();

}

private synchronized static void func1(){

}

这个func已经获取到锁了 然后func1需要获取锁才能运行 但是func1不运行完func还获取不到 就死锁了

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

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

相关文章

一、计算机系统基础

// 本章节内容根据下列代码的生命周期来讲解计算机系统的各个部分 hello.c #include <stdio.h>int main {printf("hello, world\n");return 0; }文章目录 1.1信息 位 上下文1.2程序的编译过程1.3系统的硬件组成1.4运行hello程序1.5高速缓存1.6操作系统管理硬…

小程序用什么开发?

近年来&#xff0c;随着智能手机的普及和移动互联网的发展&#xff0c;小程序成为了一种备受关注的新型应用。那么&#xff0c;小程序用什么开发呢&#xff1f; 首先&#xff0c;小程序可以使用多种技术进行开发&#xff0c;其中比较流行的有两种方式&#xff1a;一种是借助微…

( 背包问题) 1049. 最后一块石头的重量 II ——【Leetcode每日一题】

❓1049. 最后一块石头的重量 II 难度&#xff1a;中等 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x &…

尾调用优化

尾调用优化 最近遇到一个堆栈溢出的问题&#xff0c;分析后发现可收敛为递归边界问题。结合“红宝书”中相关内容和ES6规范中的一些优化机制&#xff0c;整理记录如下。 前言 程序运行时&#xff0c;计算机会为应用程序分配一定的内存空间。应用程序会自行分配所获得的内存空…

SpringBoot @JsonProperty + @JsonMixin注解 实现返回json数据key的转换

参考资料 Springboot 一个注解搞定返回参数key转换 【实用】Spring Boot 2.7新特性&#xff1a;JsonMixin 目录 一. 需求二. 前期准备三. 解决方式一: JsonProperty注解三. 解决方式二: JsonProperty JsonMixin注解3.1 方式1 混入单个类3.1.1 创建一个被JsonMixin注解修饰的抽…

ChatGPT初学者最佳实践

2022年11月底&#xff0c;ChatGPT引爆了新一轮AI的革命&#xff0c;也让人们意识到AI真的能够大幅度提高人们的工作效率&#xff0c;甚至有人担心自己的工作会因为AI不保。这种居安思危的意识是正确的&#xff0c;但是正如锛凿斧锯的出现&#xff0c;并没有让木匠这个行业消失&…

音频格式及转换代码

音频信号的读写、播放及录音 python已经支持WAV格式的书写&#xff0c;而实时的声音输入输出需要安装pyAudio(http://people.csail.mit.edu/hubert/pyaudio)。最后我们还将使用pyMedia(http://pymedia.org)进行Mp3的解码和播放。 音频信号是模拟信号&#xff0c;我们需要将其…

纯前端绘制的下雨效果

先上效果&#xff1a; 再上代码&#xff1a; <!--黏糊糊的菜单--> <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><meta name"viewport" content"widt…

Golang笔记:使用os.Args和flag包编写命令行界面(CLIs)

文章目录 目的os.ArgsflagFlagSet总结 目的 命令行界面&#xff08;Command-line Interfaces&#xff09;是比较常用的一种软件形式。对于大部分开发运维人员来说很多时候CLIs可能比图形界面更加方便。软件开发时也经常会有需要开发命令行界面形式软件的情况&#xff0c;使用G…

Maven基础篇

Maven基本概念 Maven是什么 maven的本质是一个项目管理工程&#xff0c;将项目开发和管理过程抽象成一个项目对象模型&#xff08;POM&#xff09; POM&#xff08;Project Object Model&#xff09;&#xff1a;项目对象模型 作用 项目构建&#xff1a;提供标准的、跨平台…

什么是Selenium?如何使用Selenium进行自动化测试

目录 什么是 Selenium&#xff1f; Selenium 的优势是什么? 软件测试的需要 手动测试的挑战 自动化测试胜过手动测试 Selenium 对比 QTP 和 RFT Selenium 工具套件 Selenium 有哪些组件? Selenium RC &#xff08;远程控制&#xff09; Selenium IDE&#xff08;集成…

MIT6824——lab2(实现一个Raft库)的一些实现,问题,和思考

MIT 6824 关于lab2的实现&#xff0c;由于开源许可的问题&#xff0c;代码暂时不开源&#xff0c;下面是自己在实现过程中的思路&#xff0c;遇到的问题&#xff0c;以及总结 1 总结 1.1 raft整个流程 应用程序&#xff1a;kv数据库启动raft库&#xff0c;选举leader&#xf…

跳槽前,把自己逼成卷王...

前段时间席卷全互联网行业的内卷现象&#xff0c;想必有不少人都深陷其中。其实刚开始测试行业人才往往供不应求&#xff0c;而在发展了十几年后&#xff0c;很多人涌入这个行业开始面对存量竞争。红利期过去了&#xff0c;仅剩内部争夺。 即便如此&#xff0c;测试行业仍有许…

AspNetCore中的配置文件详解

1 配置文件 程序开发中&#xff0c;有些信息是要根据环境改变的&#xff0c;比如开发环境的数据库可能是本地数据&#xff0c;而生产环境下需要连接生产数据库&#xff0c;我们需要把这些信息放到程序外面&#xff0c;在程序运行时通过读取这些外部信息实现不改变程序代码适应…

计算机图形学-GAMES101-8

引言 着色是针对某一个点(片段)的应用&#xff0c;这里需要考虑着色的频率。  漫反射项代表光向四面八方均匀的反射出去&#xff0c;和观察方向无关。  Blinn-Phong反射模型结构如下&#xff1a; ) 一、Blinn-Phong模型 &#xff08;1&#xff09;Specular 什么时候才能看到…

SpringBoot实操篇1

一、工程打包与运行&#xff08;windows版&#xff09; 在浏览器中就可以访问到了&#xff0c;此时IDEA并没有启动。服务器就是命令行窗口。 跳过测试&#xff1a;可以看到多了很多数据&#xff0c;是因打包的时候将功能测试了一遍。在IDEA中可以关掉。 注意&#xff1a;必须…

nginx+php+mysql安装以及环境的搭建

目录 一、nginx的安装 二、php的下载安装 1.进入到/usr/local/下&#xff0c;下载php的安装包 2.解压 3.进入到php-8.2.6下&#xff0c;安装需要的依赖包 4.预编译php 5.编译 6.为php提供配置文件 7.为php-fpm提供配置文件 8.添加用户和用户组 9.修改php-fpm.conf配置…

JavaScript全解析-this指向

this指向&#xff08;掌握&#xff09; ●this 是一个关键字&#xff0c;是一个使用在作用域内的关键字 ●作用域分为全局作用域和局部作用域&#xff08;私有作用域或者函数作用域&#xff09; 全局作用域 ●全局作用域中this指向window 局部作用域 ●函数内的 this, 和 函…

OS之作业调度算法

目录 一、基本概念 二、先来先服务算法(FCFS) 三、短作业算法(SJF/SPF) 四、轮转调度算法(RR) 五、优先级调度算法 六、多级反馈队列调度算法 一、基本概念 T(周转)T(完成)-T(到达) 二、先来先服务算法(FCFS) 不利于短作业&#xff0c;非抢占式算法 算法思想&#xff…

Linux日志文件服务器搭建

文章目录 Linux日志文件服务器搭建节点规划案例实施(1)修改主机名(2)配置日志服务器(3)重新启动查看rsyslogd(4)配置客户端(5)测试 Linux日志文件服务器搭建 节点规划 IP主机名节点192.168.100.10serverlog日志服务器192.168.100.20clientlog日志客户端 必须两台机器可以ping…