1.0 线程状态
java把就绪状态,阻塞状态细分出更多的状态
- NEW: 安排了工作, 还未开始行动(Thread对象创建好了,但还没有start调用)
- RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. (就绪状态,比如线程内一直while(true)但是一直不调用cpu资源,这种是在排队,或者正在cpu上调度,这两种都是runnable状态)
- BLOCKED: 这几个都表示排队等着其他事情 (锁的原因阻塞)
- WAITING: 这几个都表示排队等着其他事情 (调用waiti方法而阻塞)
- TIMED_WAITING: 这几个都表示排队等着其他事情 (通过sleep方法而阻塞,或者待时间的join)
- TERMINATED: 工作完成了.(线程已经结束了,但是Thread对象还在)
方法
获取所有状态
代码演示
2.0 线程安全(重要)
在多线程下,发现由于多线程执行,导致的bug,统称为线程安全问题–>本质就是代码是否存在bug
虽然一个cpu核心上,寄存器就这么一组,但是两个线程,可以视为是各自有各自的一组寄存器,本质上是"分时复用"的
安全原因:
- 调度问题[根本原因],操作系统对线程的调度是随机的,和单线程不同的是,在多线程下,代码的执行顺序,产生了更多的变化,串行执行只需要考虑一个固定的顺序执行下的安全性,但是多线程则要考虑N中执行顺序下代码执行结果都正确,举例说明:,多个线程的调度在主存中调度是不确定的,可能导致计算共同处理某个数据的最终有差异(可能出现覆盖结果,可能a在load,刚load完,b又load,增加,写入,b后增加写入,则这个增加变量只增加一次被覆盖了)
- 同一变量修改,多个线程同时修改同一个变脸,容易产生线程安全
- 非原子性,进行修改的不是"原子的"也可能导致线程不安全,比如某个变量++,他要拆成load ++和save等多个指令,而指令就是计算机的原子,如果修改操作按照原子的方式来完成,此时也不会有线程安全问题
- 内存可见性
- 指令重排序引起的线程安全问题,后面会写这个
3.0 JAVA内存模型(JMM)
- 来自java语法规范
- java线程内存的修改都是在工作内存(这里工作内存就是指cache和寄存器,java为了跨平台搞出这个术语)(work memory)修改,修改完再通过load和save到主内存中
- JMM会造成线程安全的问题
4.0 锁
加锁的概念
锁的作用
- 可以让多个线程,有一部分代码是串行执行,有一部分代码是并发执行 => 仍然要比纯粹的串行执行效率高
- 能够互斥的使用相同的变量/资源,但是如果修改的不是同一个变量/资源,则不需要加锁,可以并行执行,不必要加锁串行
- 相当于把一组操作,打包成一个"原子&java 线程 锁基础知识#34;操作,mysql的事务的原子操作,主要靠回滚,而java中的原子,则是通过锁进行"互斥",A线程工作的时候,其他线程无法进行操作
- 代码中的锁,就是让多个线程,同一时刻,只有一个线程能使用这个变量,在某个方法前加上,synchronized关键词,进方法就会(lock),出方法就会解锁(unlock)
- c++,python加锁解锁往往是两个独立的方法,但是unlock容易忘写(多个判断条件退出时都要unlock或者用RAII的特殊方法实现synchronized),这样涉及不如java设计的好,java的synchronized是只要出了代码块就一定会结束锁
synchronized工作原理及作用范围
- 通常修饰的是代码块,按照代码块加锁解锁
- 进入 synchronized 修饰的代码块, 相当于 加锁
- 退出 synchronized 修饰的代码块, 相当于 解锁
- 比如:我们两个线程for循环500此,都对一个类的一个 synchronized修饰的函数调用500次,则调用一次就涉及加锁解锁,而不是一个for循环内都是锁的
- synchronized 可重锁 , 一个类的某个函数被上锁,不代表这个类被上锁,其他线程还可以把其他函数上锁,让另外像访问这个类的这个函数的线程变成阻塞
- synchronized 进行加锁解锁,其实是以"对象"位维度进行展的,synchronized的原始写法其实是指定了某个具体对象的加锁
- synchronized的原始写法其实是指定了某个具体对象的加锁,当synchronized直接修饰方法,此时就相当于针对this加锁,前面修饰方法其实就是这种写法的简化版,所以不存在所谓的 “同步方法” 的概念
- 唯一规则:,如果两个线程对同一个对象加锁,就会进行锁冲突(会有阻塞状态出现),如果两个线程争对不同对象进行加锁,则不会产生冲突 也就没有冲突
理解 “阻塞等待”
- 针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝 试进行加锁, 就加不上了, 就会阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的 线程, 再来获取到这个锁.
- 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 “唤醒”. 这 也就是操作系统线程调度的一部分工作.
- 假设有 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
还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/24648.html