计算机基础
- 要想理解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)
以我自己的电脑为例 四核八线程
也可以通过更加直观的方式, 打开任务管理器
可以很直观的看出有一个插槽,四个内核,八个逻辑处理器
解释
一个插槽说明只有一个物理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; } // 不加synchronized public 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 { @Override public synchronized void doSomething() { System.out.println(toString()); super.doSomething(); } }
注:不是所有的数据都需要锁来保护,只有被多个线程同时访问的可变数据才需要通过锁来保护。
-
-
活跃性和性能
活跃性:安全性的含义:永远不要发生糟糕的事情,而活跃性则关注于:某个事情最终会发生,但由于如此,就可能出现没有得到锁而死等的现象,或者无意中造成的无限循环
性能:性能方面有很多问题例如,服务时间过长,响应不及时等,要在性能和安全编码之间相互的权衡,不要一味为了性能而去修改简单的并发程序