java 线程 锁基础知识

java 线程 锁基础知识1 0 线程状态 java 把就绪状态 阻塞状态细分出更多的状态 NEW 安排了工作 还未开始行动 Thread 对象创建好了 但还没有 start 调用 RUNNABLE 可工作的 又可以分成正在工作中和即将开始工作 就绪状态 比如线程内一直 while true 但是一直不调用 cpu 资源 这种是在排队 或者正在 cpu 上调度 这两种都是 runnable 状态 BLOCKED

大家好,我是讯享网,很高兴认识大家。



1.0 线程状态

java把就绪状态,阻塞状态细分出更多的状态

  • NEW: 安排了工作, 还未开始行动(Thread对象创建好了,但还没有start调用)
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. (就绪状态,比如线程内一直while(true)但是一直不调用cpu资源,这种是在排队,或者正在cpu上调度,这两种都是runnable状态)
  • BLOCKED: 这几个都表示排队等着其他事情 (锁的原因阻塞)
  • WAITING: 这几个都表示排队等着其他事情 (调用waiti方法而阻塞)
  • TIMED_WAITING: 这几个都表示排队等着其他事情 (通过sleep方法而阻塞,或者待时间的join)
  • TERMINATED: 工作完成了.(线程已经结束了,但是Thread对象还在)

方法

方法描述t.getState()无

获取所有状态

 
讯享网 

代码演示

讯享网

2.0 线程安全(重要)

在多线程下,发现由于多线程执行,导致的bug,统称为线程安全问题–>本质就是代码是否存在bug

虽然一个cpu核心上,寄存器就这么一组,但是两个线程,可以视为是各自有各自的一组寄存器,本质上是"分时复用"的

安全原因:

  1. 调度问题[根本原因],操作系统对线程的调度是随机的,和单线程不同的是,在多线程下,代码的执行顺序,产生了更多的变化,串行执行只需要考虑一个固定的顺序执行下的安全性,但是多线程则要考虑N中执行顺序下代码执行结果都正确,举例说明:,多个线程的调度在主存中调度是不确定的,可能导致计算共同处理某个数据的最终有差异(可能出现覆盖结果,可能a在load,刚load完,b又load,增加,写入,b后增加写入,则这个增加变量只增加一次被覆盖了)
  2. 同一变量修改,多个线程同时修改同一个变脸,容易产生线程安全
  3. 非原子性,进行修改的不是"原子的"也可能导致线程不安全,比如某个变量++,他要拆成load ++和save等多个指令,而指令就是计算机的原子,如果修改操作按照原子的方式来完成,此时也不会有线程安全问题
  4. 内存可见性
  5. 指令重排序引起的线程安全问题,后面会写这个

3.0 JAVA内存模型(JMM)

  • 来自java语法规范
  • java线程内存的修改都是在工作内存(这里工作内存就是指cache和寄存器,java为了跨平台搞出这个术语)(work memory)修改,修改完再通过load和save到主内存中
  • JMM会造成线程安全的问题

4.0 锁

加锁的概念

锁的作用

  1. 可以让多个线程,有一部分代码是串行执行,有一部分代码是并发执行 => 仍然要比纯粹的串行执行效率高
  2. 能够互斥的使用相同的变量/资源,但是如果修改的不是同一个变量/资源,则不需要加锁,可以并行执行,不必要加锁串行
  3. 相当于把一组操作,打包成一个"原子&java 线程 锁基础知识#34;操作,mysql的事务的原子操作,主要靠回滚,而java中的原子,则是通过锁进行"互斥",A线程工作的时候,其他线程无法进行操作
  4. 代码中的锁,就是让多个线程,同一时刻,只有一个线程能使用这个变量,在某个方法前加上,synchronized关键词,进方法就会(lock),出方法就会解锁(unlock)
  5. c++,python加锁解锁往往是两个独立的方法,但是unlock容易忘写(多个判断条件退出时都要unlock或者用RAII的特殊方法实现synchronized),这样涉及不如java设计的好,java的synchronized是只要出了代码块就一定会结束锁

synchronized工作原理及作用范围

  1. 通常修饰的是代码块,按照代码块加锁解锁
  2. 进入 synchronized 修饰的代码块, 相当于 加锁
  3. 退出 synchronized 修饰的代码块, 相当于 解锁
  4. 比如:我们两个线程for循环500此,都对一个类的一个 synchronized修饰的函数调用500次,则调用一次就涉及加锁解锁,而不是一个for循环内都是锁的

  5. synchronized 可重锁 , 一个类的某个函数被上锁,不代表这个类被上锁,其他线程还可以把其他函数上锁,让另外像访问这个类的这个函数的线程变成阻塞
  6. synchronized 进行加锁解锁,其实是以"对象"位维度进行展的,synchronized的原始写法其实是指定了某个具体对象的加锁
  7. synchronized的原始写法其实是指定了某个具体对象的加锁,当synchronized直接修饰方法,此时就相当于针对this加锁,前面修饰方法其实就是这种写法的简化版,所以不存在所谓的 “同步方法” 的概念
  8. 唯一规则:,如果两个线程对同一个对象加锁,就会进行锁冲突(会有阻塞状态出现),如果两个线程争对不同对象进行加锁,则不会产生冲突 也就没有冲突

理解 “阻塞等待”

  1. 针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝 试进行加锁, 就加不上了, 就会阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的 线程, 再来获取到这个锁.
  2. 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 “唤醒”. 这 也就是操作系统线程调度的一部分工作.
  3. 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能 获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.

类对象的生成

一个类的完整信息,最初是子.java文件中 (编译)=> .class 文件, JVM加载.class 就会解析里面的内容,构造出一个内存的对象,这就是类的类对象(相当于类的图纸,用图纸构造出对象,类对象就是class文件记录了.java文件的信息)

标准库中的加锁方法

例子

 

1.直接修饰普通方法: 锁的 SynchronizedDemo 对象

讯享网

2.修饰静态方法: 锁的 SynchronizedDemo 类的对象

 

3.修饰代码块: 明确指定锁哪个对象.

 

注意:

  • 所谓加锁操作,简单理解成给对象里面做一个标记,每个对象(Object 产生的实例),出了你代码中写的属性之外,还有一部分空间,存储的是"对象头",对象的属性数据中有一个标志位就相当于"加锁"
  • 这一位被"标记加锁"之后,此时其他线程也相对这个对象标记加锁就会阻塞
  • 但是,这里的"标记加锁"对于访问这个对象的成员是没有影响的.只影响到线程的访问

5.0 线程安全类

Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.

ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
StringBuilder

但是还有一些是线程安全的. 使用了一些锁机制来控制.

Vector (不推荐使用)
HashTable (不推荐使用)
ConcurrentHashMap
StringBuffer

还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的

小讯
上一篇 2024-12-25 17:12
下一篇 2024-12-30 16:09

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/1187.html