# 为什么需要多线程
## 充分利用CPU资源
- 在多处理器/多核系统上 单线程程序只能使用一个CPU资源,而多线程程序可以同时在多个处理器上/不同核心上同时执行
- 在某个线程被IO阻塞时 可以切换到其他线程继续执行任务,不浪费CPU执行时间
**但多线程不是银弹**,当CPU核数小于系统线程数时必然会发生线程的切换,线程上下文的切换也存在较大的开销,一个进程开启过多的线程导致线程频繁切换可能会得不偿失。
> 插曲:某些应用场景
例如,Remote Method Invocation 远程方法调用
**简单对比一下RMI和RPC的区别**:
RPC更为灵活,使用NIO模型,是一种网络服务协议,与操作系统和实现语言无关,返回的是外部数据表示(例如json);而RMI只用于Java,使用的是BIO,返回的是对象类型或者基本数据类型。
# 线程安全性
## 共享和可变
线程安全问题是由于同一个进程内不同线程存在**共享**内存区域,而线程对其进行的**修改**操作可能会相互影响,**导致了**程序的执行结果与我们所预期的出现了不一致,这就是线程安全问题
- 共享意味着变量可以被多个线程同时访问 -->加锁/同步机制打破
- 可变意味着变量的值在其生命周期内可以变化 --> final修饰打破
当程序的内存空间同时存在共享和可变条件时,我们称程序是非线性安全的。
## 线程安全类
当多个线程访问某个类时,无需在外部代码中添加额外的同步机制,这个类都可以表现出正确的行为(或者说不会产生不确定的结果),那么这个类就是线程安全的。
> 断言:无状态的对象一定是线程安全的 √
理由:等同于打破了前面所提的**可变**条件,如果对象没有状态等同于对象不可变,多线程下对**该对象**的任意操作都不会改变**该对象**的状态,每个线程访问这个对象的获得的状态都是一致的,所以是线程安全的
## 竞态条件
竞态条件指的是 多个线程运行输出的结果取决于它们的交替执行时序 这样一个现象,也就是说竞态条件指**要获得正确的结果 必须取决于线程执行的先后交替顺序**。
### 以单例模式的懒加载为例
```java
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
```
在这里存在竞态条件,假如有线程a和b同时调用`getInstance()`,A看到`instance`为空然后实例化`Singleton`,然而线程b调用的时机不可预测,假如b看到`instance`仍然为空它也会去生成额外一个`Singleton`,可能造成更新丢失等错误(A的Singleton被B覆盖);假如b判断`instance==null`时a已完成实例化,就会返回实例化好的单例。
双重检查锁的修改方式是
```java
public class Singleton{
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
```
- 第一次判空的作用是降低锁的粒度,只有未初始化单例的线程才会走入同步代码,当单例已初始化好以后直接返回实例
- 第二次判空的作用避免反复实例化(类似上面的问题)
- `volatile`作用禁止指令重排序,`instance = new Singleton`不是一个原子操作,它的步骤有1、分配内存,2、调用构造器初始化成员变量,3、将实例化的对象指向分配好的内存空间; 当线程a进入同步代码块执行时指令顺序可能会被重排为132,返回一个未初始化的`Singleton`
# 加锁机制
## 内置锁
Java提供了一种内置的锁机制:同步代码块(Synchronized Block),它的形式如下:
```java
synchronized(lock){//锁的对象引用
//由这个锁 保护的代码块
···
}
```
当`synchronized`关键字直接修饰方法的时候就说一个横跨整个方法体的同步代码块,该代码块的锁对象就是**该方法调用所在的对象实例**,静态的`synchronized`方法的锁对象以Class对象为锁;
```java
class A{
synchronized methodB(){}
}
A a = new A();
a.methodB(); //这里锁的对象就是a
```
每个Java对象都可以作为一个实现同步的锁,这被称为**内置锁**或监视器锁;线程在进入同步代码块之前自动获得锁,退出时自动释放锁;并且它是一种互斥锁,最多只有一个线程能持有它。当线程a尝试获得一个线程b拥有的锁时,a必须自旋等待或阻塞,直到b释放这个锁;
## 重入锁
ReentrantLock可重入锁,也可以理解为可递归锁;它的意思是说,允许同一个线程多次获取同一把锁。我们知道当某个线程请求一个由其他线程持有的锁,发出请求的线程会等待或阻塞,当某个线程请求一个由它**自己拥有**的锁时,如果这个操作能够成功,就叫可重入锁,否则是不可重入锁;
比如一个递归函数里有加锁操作,递归过程中对于可重入锁它可以申请成功获取到锁,而不可重入锁在这种情况下则会陷入死锁。因为这个原因可重入锁也叫做可递归锁。
> 使用锁的时候要注意锁的粒度,特别要尽量避免IO过程中持有锁的行为。
《Java并发编程实战》读书笔记一:线程安全性