计算机基础
- 要想理解Java多线程,一定离不开计算机组成原理和操作系统,因为,java的多线程是JVM虚拟机调用操作系统的线程来实现的
/*Thread.start() 方法中调用了原生的start0()方法
*/
public synchronized void start() {if (threadStatus != 0)throw new IllegalThreadStateException();group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {}}}// 原生方法private native void start0();
- 首先,先来理解CPU(处理器)的结构
解析每一个部件的功能,具体的细节不展开
- ALU(算数逻辑单元):顾名思义是用来计算的,首先要知道一点,所有的信息在计算机看来都是0和1,通过加法运算,补运算, 异或运算等各种运算,完成数据的计算
- CU (控制单元) : 可以理解为控制ALU运算(比如运算什么,怎么运算,以及运算的次数)
- Register (寄存器): 用来存储ALU计算的中间数值
- pc(程序计数器):用来记录某个线程运行到那个指令了
- 程序的加载过程以及是如何运行的
理解:程序,进程,线程
举例:比如我现在点击了WX.exe的程序,计算机会将WX程序加载到内存,这是在内存中的就是一个WX进程,每一个进程都有一个main线程,ALU找到main线程开始执行
程序:就是一段待执行的代码
进程:将程序这段代码以及所需要的数据加载到内存
线程:CPU,调度的基本单位
-
再来理解一个常识性的问题:我们买的电脑几核几线程的概念
我们可以通过cmd的命令查看一下自己的电脑(华为matebookpro)
以我自己的电脑为例 四核八线程
wmic
获取cpu的名称 cpu get name
获取cpu的核心数 cpu get numberofcores
也可以通过更加直观的方式, 打开任务管理器
可以很直观的看出有一个插槽,四个内核,八个逻辑处理器
解释
一个插槽说明只有一个物理cpu
四个内核说明有四个ALU
八个逻辑处理器说明每个ALU可以在Register中切换,可以减少线程的挂起
以前的cpu没有使用多核和超线程技术,也就意味着一个物理cpu对应一个内核对应一个线程,效率是极其低下的
线程的安全性
-
什么是线程安全?
- 当多个线程访问某个类时,不管运行的环境(linux/windows)采用何种的调度方式或者这些线程将如何交替的运行,在主程序中不采用任何额外的同步或协同,这个类都可以表现出正确的行为。
- 无状态的对象一定是线程安全的(也就是说该对象中的属性被多个线程共享)
-
原子性
要想理解原子性首先要理解几个概念
竞态条件:
-
专业术语:由于不恰当的执行顺序而出现不正确的结果的这种情况
-
个人理解:线程由于cpu的调度,多个线程交替执行,要得到正确的结果需要运气成分
// 比如在单例模式中 if (instance == null) {instance = new Singletion(); }//这个过程是 先检查再执行,这个过程是可以被cpu打断的先检查再执行就是一个竞态条件
//比如 count++//count++ 不是一步执行完成的需要先从内存中读取,在做修改,在写回内存读取 - 修改 - 写入 也是一个竞态条件
复合条件:
- 专业术语:一组需要以原子的方式执行(不可分割)的操作
- 个人理解:就是不可以被cpu的调度所打断,必须执行完毕
数据竞争:
- 这个很好理解,就是多个线程可以同时去写入或读出一个变量
-
-
加锁机制
加锁机制可以保证原子性,也就是把一系列的操作变为原子的
-
内置锁
内置锁其实很简单,就是改变了,对象里的markword
// 使用这个依赖可以查看对象的结构 <dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version></dependency>
public class JustTest {private static class T {int i;}// 不加synchronizedpublic static void main(String[] args) {T t = new T();// 打印对象的结构System.out.println(ClassLayout.parseInstance(t).toPrintable());} }
public class JustTest {private static class T {int i;}public static void main(String[] args) {T t = new T();synchronized (t) {System.out.println(ClassLayout.parseInstance(t).toPrintable());}} }
-
重入锁
当一个线程请求一个未被持有锁的对象,JVM会记下所得持有者,并将计数器加一,若果同一线程再次获取锁,则计数器再加一,直到计数器的个数为0时被释放
如果不可重入,下面的代码可能就会死锁
// 父类持有锁,不能释放,而子类需要锁,二者僵持 public class Widget {public synchronized void doSomething() {} }class LoggingWidget extends Widget {@Overridepublic synchronized void doSomething() {System.out.println(toString());super.doSomething();} }
注:不是所有的数据都需要锁来保护,只有被多个线程同时访问的可变数据才需要通过锁来保护。
-
-
活跃性和性能
活跃性:安全性的含义:永远不要发生糟糕的事情,而活跃性则关注于:某个事情最终会发生,但由于如此,就可能出现没有得到锁而死等的现象,或者无意中造成的无限循环
性能:性能方面有很多问题例如,服务时间过长,响应不及时等,要在性能和安全编码之间相互的权衡,不要一味为了性能而去修改简单的并发程序