多线程基础

原因

  • 多核+分布式架构
  • 多CPU核心意味着同时操作系统有更多的并行计算资源可以使用,以线程作为基本调度单元
  • 总线架构
    • SMP Architecture(SymmetricalMultiProcessing 对称多处理)处理同一块内存效率低
    • NUMA Architecture(NonUniformMemoryAccess 非统一内存访问)可部分绑定,可扩展

CPU架构.png

Java多线程

线程创建过程

线程创建过程.png

线程与进程

  • 应用基本运行单位是进程
  • CPU调度的单位是线程
  • 进程里面包含N个线程,系统资源共享
  • 线程独享自己的资源

    线程方法

    ```java public static void main(String[] args){ Runnable task = new Runnable(){
    1. @Override
    2. public void run(){
    3. Thread t = Thread.currentThread();
    4. try{
    5. System.out.println("threadName is");
    6. System.out.println("threadName is"+t.getName());
    7. System.out.println("threadName is");
    8. Thread.sleep(5000);
    9. }catch(InterruptedException e){
    10. e.printStackTrace();
    11. }
    12. System.out.println("threadName is"+t.getName());
    13. }
    }; Thread thread = new Thread(task); thread.setName(“ThisIsMyThreadName”); thread.setDaemon(true); // 如果是守护线程,那JVM会在没有前台线程时退出整个进程不再执行 thread.start();
// 线程的优先级默认是5,从1-10,越大优先级越高
System.out.println(Thread.activeCount()); // 2
Thread.currentThread().getThreadGroup().list();
// java.lang.ThreadGroup[name=main,maxpri=10]
//     Thread[main,5,main]
//     Thread[Monitor Ctrl-Break,5,main]


System.out.println(Thread.currentThread().getThreadGroup().getParent().activeGroupCount()); // 1
Thread.currentThread().getThreadGroup().getParent().list();
// java.lang.ThreadGroup[name=system,maxpri=10]
// Thread[Reference Handler,10,system]
// Thread[Finalizer,8,system]
// Thread[Single Dispatcher,9,system]
// Thread[Attach Listener,5,system]
// java.lang.ThreadGroup[name=main,maxpri=10]
//     Thread[main,5,main]
//     Thread[Monitor Ctrl-Break,5,main]

}

```java
// 接口定义
public interface Runnable{
    public abstract void run();
}

// 重要实现
Thread implements Runnable...

// 辨析
Thread.start(); // 创建线程
Thread.run();   // 调用线程
Thread类重要属性/方法 说明
volatile String name; 线程名称-诊断分析使用
boolean daemon = false; 后台守护线程标志-决定JVM优雅关闭
Runnable target; 任务(只能通过构造函数传入)
synchronized void start(); [协作]启动新线程并自动执行
void join(); [协作]等待某个线程执行完毕(来汇合)
static native Thread currentThread(); 静态方法:获取当前线程信息
static native void sleep(long millis); 静态方法:释放CPU,不释放锁
Object#方法 说明
void wait(); 放弃锁+等待0ms+尝试获取锁(释放CPU,释放锁)
void wait(long timeout, int nanos) 放弃锁+wait+到时间自动唤醒/中途唤醒(精度:nanos>0则timeout++)
native void wait(long timeout); 放弃锁+wait+到时间自动唤醒/中途唤醒(唤醒之后需要自动获取锁)
native void notify(); 发送信号通知1个等待线程
native void notifyAll(); 发送信号通知所有等待线程
  • Thread.sleep(long millis)
    • 一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁
    • millis时间后线程自动苏醒进入就绪状态
    • 作用:给其他线程执行机会的最佳方式
  • Thread.yield()
    • 一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态
    • 不会导致阻塞,该方法与sleep类似,只是不能由用户指定暂停多长时间
    • 作用:让相同优先级的线程再次被CPU选择调用
  • t.join()
    • 当前线程里调用其他线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态
    • 当前线程不会释放已经持有的对象锁,t线程会释放锁
    • 线程t执行完毕或者millis时间到,当前线程进入就绪状态
  • obj.wait()
    • 当前线程调用对象的wait(),当前线程释放对象锁,进入等待队列
    • 依靠notify()/notifyAll()唤醒或timeout时间到自动唤醒
  • obj.notify()
    • 唤醒在此对象监视器上等待的单个线程,选择是任意的
  • obj.nofityAll()
    • 唤醒再次对象监视器上等待的所有线程
  • sleep与wait

package java0.conc0301.op;

public class Join {

public static void main(String[] args) {
    Object oo = new Object();

    MyThread thread1 = new MyThread("thread1 -- ");
    thread1.setOo(oo);
    thread1.start();

    // synchronized (oo) { 
    synchronized (thread1) {
        for (int i = 0; i < 100; i++) {
            if (i == 20) {
                try {
                    thread1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " -- " + i);
        }
    }
}

}

class MyThread extends Thread {

private String name;
private Object oo;

public void setOo(Object oo) {
    this.oo = oo;
}

public MyThread(String name) {
    this.name = name;
}

@Override
public void run() {
    synchronized (this) {
        for (int i = 0; i < 100; i++) {
            System.out.println(name + i);
        }
    }
}

}

<a name="cWzx9"></a>
## 线程中断与异常

- 线程内部自己处理异常,因为不会溢出到外层
- 如果线程被Object.wait/Thread.join/Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt(),那么该线程将抛出一个InterruptedException中断异常,从而提早终结被阻塞的状态。如果线程没有被阻塞,这时调用interrupt()将不起作用,直到执行到wait/join/sleep时才会马上抛出InterruptedException
- 子线程内部调用Thread.interrupted()可以重置中断状态
- 如果是计算密集型的操作需要分段处理,每个片段检查一下状态,是不是要终止
<a name="e7qra"></a>
## 线程状态
![线程状态.png](https://cdn.nlark.com/yuque/0/2020/png/1491874/1605532548239-8cf73f4f-6b78-47ba-864c-88eda31e6383.png#align=left&display=inline&height=932&margin=%5Bobject%20Object%5D&name=%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81.png&originHeight=932&originWidth=2020&size=306639&status=done&style=none&width=2020)<br />![线程状态图.png](https://cdn.nlark.com/yuque/0/2020/png/1491874/1605582573818-154c0843-29c6-4406-a776-cd362a3ecf4e.png#align=left&display=inline&height=1584&margin=%5Bobject%20Object%5D&name=%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%9B%BE.png&originHeight=1584&originWidth=2342&size=1107359&status=done&style=none&width=2342)
<a name="6wwqL"></a>
# 线程安全
<a name="H9Whq"></a>
## 多线程执行问题

- 多个线程竞争同一资源时,如果对资源的访问顺序敏感,就是存在竞态条件
- 导致竞态条件发生的代码区称作临界区
- 不进行恰当的控制,会导致线程安全问题
- 恰当的控制
   - 同步
   - 加锁
<a name="EyyQF"></a>
## 并发
<a name="P4m0i"></a>
### 原子性

- 原子操作,注意和事务ACID里原子性的区别与联系
- 原子性操作不可被中断,要么执行,要么不执行
- 对基本数据类型的变量的读取和赋值操作是原子性操作
```java
x=10; // 原子性操作
y=x;  // 非原子性操作
x++;  // 非原子性操作
x=x+1;// 非原子性操作

可见性

  • volatile关键字保证可见性,但不保证原子性
  • 当一个共享变量被volatile修饰时,会保证修改的值立即被更新到主存,当有其他线程需要读取时,会去内存中读取新值
  • 通过synchronized和Lock也能保证可见性,这两个关键字保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前对变量的修改刷新到主存当中

    有序性

  • Java允许编译器和处理器对指令进行重排,重排过程不会影响到单线程的执行,但会影响到多线程的并发

  • 可通过volatile/synchronized/Lock保证一定的有序性
  • happens-before原则(先行发生原则):

    • 程序次序规则:一个线程内,按照代码先后顺序
    • 锁定规则:一个unLock操作先行发生于后面对同一个锁的Lock操作
    • volatile变量规则:对一个变量的写操作先行发生后面对这个变量的读操作
    • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出A先于C
    • 线程启动规则:start方法先发生于线程的每一个动作
    • 线程中断规则:对线程的interrupt()先行发生于被中断线程代码检测到中断事件的发生
    • 线程终结规则:线程中所有的操作都先于线程的终止检测,Thread.join()方法结束、Thread.isAlive()检测是否终止
    • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法

      synchronized

      实现机制

  • 使用对象头标记字(Object monitor)

  • Synchronized 方法优化
  • 偏向锁:BiaseLock

对象头.png
对象头锁标识.png

三种用法

public class SyncCounter{
    private int sum = 0;
    // 锁方法,专有指令,影响所有调用此方法的对象
    public synchronized  int incAndGet(){
        return ++sum;
    }
    public int addAndGet(){
        // 锁对象,只影响当前对象
        synchronized (this){
            return ++sum;
        }
        // 锁代码块,粒度最小
        synchronized {

        }
    }
    public int getSum(){
        return sum;
    }
}

volatile

  • 每次读取都强制从主内存刷数据
  • 适用场景:单个线程写,多个线程读,也可以做指令重排屏障,阻止上下两部分的指令混排,做屏障分割
  • 原则:能不用就不用,不确定的时候也不用
  • 替代方案:Atomic原子操作类

    final

  • 常量替换

  • 写代码时最大化用final是个好习惯 | final定义类型 | 说明 | | —- | —- | | final class | 不允许继承 | | final 方法 | 不允许Override | | final 局部变量 | 不允许修改 | | final 实例属性 |
    - 构造函数/初始化块/之后不允许变更;
    - 只能赋值一次
    - 安全发布:构造函数结束返回时,final域最新的值被保证对其他线程可见
    | | final static属性 | 静态块执行后不允许变更;
    只能赋值一次; |