当前位置:网站首页 > Java基础 > 正文

java 线程 锁基础知识



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

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

版权声明


相关文章:

  • java怎么算有基础2024-10-28 11:42:00
  • 有java基础怎么赚钱2024-10-28 11:42:00
  • 精通java基础能找到工作嘛2024-10-28 11:42:00
  • java基础语法网课2024-10-28 11:42:00
  • java基础篇第五章2024-10-28 11:42:00
  • java基础例题及答案2024-10-28 11:42:00
  • Java基础单元测试2024-10-28 11:42:00
  • java开发基础测试题2024-10-28 11:42:00
  • java基础进阶书2024-10-28 11:42:00
  • java基础扩展2024-10-28 11:42:00