美文网首页
Java 多线程

Java 多线程

作者: bowen_wu | 来源:发表于2021-07-09 14:30 被阅读0次

概述

Thread 线程 => 是操作系统能够进行运算调度的最小单位。大部分的情况下,它被包含在进程中,是进程中的实际运作单位

  • Java 的执行模型是同步 | 阻塞(block)的
  • JVM 默认情况下只有一个线程 => 具有严重的性能问题
  • Java 中只有 Thread 代表线程
  • 开启新线程 => new Thread(Runnable target).start() => run
  • start 方法才能开始并发执行
  • 每多开一个线程,就多一个执行流
  • 方法栈(局部变量)是线程私有的
  • 静态变量 | 类变量是被所有线程共享的
  • 多线程中的线程会被线程调度机制打断

线程状态

  • NEW => 初始态 => 新创建一个线程对象,但还没有调用 start() 方法
  • RUNNABLE => 运行
  • BLOCKED => 阻塞 => 表示线程阻塞于锁
  • WAITING | TIMED_WAITING => 等待 | 超时等待 => 调用锁 | monitor | 对象的 wait() 方法之后,就进入了 WAITING 状态
  • TERMINATED终止 => 表示该线程已经执行完毕

多线程

  • CPU 的速度远远大于内存的读取、硬盘的读取以及网络IO的速度,在读取相关数据的时候,CPU 可以做其他有意义的事情,故产生了多线程 => 尽可能的运用 CPU 的效率
  • 现代 CPU 都是多核的 => Q = I^2 *RT => CPU 大于 4GHz 之后发热量承受不住 => 推出多核 => 发热量并没有显著变化 => 多线程可以更大限度发挥多核 CPU 的好处

线程难的原因:需要看着同一份代码,想象不同的人在疯狂的以乱序执行它 => 多个线程同时访问同一个共享变量时,由于变量不是原子的,以致于过程是乱序的,不知道什么时候会发生不正常的,有可能是正常的,有可能是不正常的

  • 原子操作 -> atomic operation => 不会被线程调度机制打断的操作。
// 最终结果不是 1000
private static int j = 0;
public static void main(String[] args) {
    for (int i = 0; i < 1000; i++) {
        new Thread(() -> {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(++j);

        }).start();
    }
}
result

多线程适用场景

  • 对于 IO 密集型应用极其有用 => 网络 IO (通常包括数据库) | 文件 IO
  • 对于 CPU 密集型应用稍有折扣 => 多线程带来的提升有限 => CPU 密集型:计算机完成一项任务的时间是取决于 CPU 的速度,其 CPU 占用率高,也许在某段时间内保持 100% 占用率
  • 性能提升的上线 => 单核CPU 100% | 多核CPU N * 100%

线程不安全

线程不安全的表现

  • 数据错误
  • 死锁

数据错误 => 不是原子操作

  • i++
  • if-then-do
    private static final Map<Integer, Integer> map = new HashMap<>();
    
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int random = new Random().nextInt(10);
                if (!map.containsKey(random)) {
                    map.put(random, random);
                    System.out.println("put: " + random);
                }
            }).start();
        }
    }
    
result

死锁

synchronized => 同步 => 锁 => 同一个时刻只能有一个线程拿到同一把锁 => 在 Java 中任何一个对象都可以当成锁🔐

// Thread1 和 Thread2 使用了不同的顺序来获得资源锁
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public static void main(String[] args) {
    new Thread1().start();
    new Thread2().start();
}

static class Thread1 extends Thread {
    @Override
    public void run() {
        synchronized (lock1) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lock2) {
                System.out.println("");
            }
        }
    }
}

static class Thread2 extends Thread {
    @Override
    public void run() {
        synchronized (lock2) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lock1) {
                System.out.println("");
            }
        }
    }
}
死锁宏观表现

排查死锁问题

  1. jps => 查看当前所有的 Java 进程
  2. 找到死锁所运行的进程 ID
  3. jstack <java process id> > <file> => 将死锁的进程栈信息保存到指定文件
  4. 分析文件 | 排查调用栈,看每个 Thread 在哪个方法处停滞了

预防死锁产生的原则

所有的线程都按照相同的顺序获得资源的锁🔐

线程安全

实现线程安全的基本手段

  • 不可变类 => 数据错误「多个线程同时修改同一个数据」=> Integer | String | ...
  • synchronized 同步块
  • JUC 包

synchronized 🔐

// 在语句外加 synchronized
private static final Object lock = new Object();

synchronized(lock) {
    i += 1;
}

// 在方法上声明 synchronized 关键字
public static synchronized void modifySharedVariable () {}

问:同步块同步了什么东西?

  • synchronized(<Object>) => 将 Object 当成锁
  • static synchronized <method> => 将 Class 对象当成锁
  • synchronized <method> => 将 this 当成锁 => 将该实例当成锁
    Main object = new Main();
    new Thread(object::modifySharedVariable).start();
    
    private synchronized void modifySharedVariable() {
        // do something
    }
    
    // 等价于
    private void modifySharedVariable() {
        synchronized(this) {
            // do something
        }
    }
    

JUC 包 java.util.concurrent

  • java.util.concurrent.ConcurrentHashMap => 任何使用 HashMap 有线程安全问题的地方,都无脑使用 ConcurrentHashMap 替换即可
  • java.util.concurrent.atomic.AtomicBoolean | java.util.concurrent.atomic.AtomicInteger | java.util.concurrent.atomic.AtomicLong => a += 1 不是原子操作 => AtomicInteger 中的 addAndGet() 方法是原子操作
  • java.util.concurrent.locks.ReentrantLock => 可重入锁 => 与 synchronized 不同点:ReentrantLock 可以在一个地方加锁,在另一个地方解锁
  • java.util.concurrent.CountDownLatch

java.lang.Object

  • Java 从一开始就把线程作为语言特性,提供语言级的支持

为什么 Java 中所有对象都可以成为锁?

  • Object.wait() | Object.wait(long) | Object.wait(long, int) => Causes the current thread to wait until another thread invokes the java.lang.Object#notify() method or the java.lang.Object#notifyAll() method for this object. The current thread must own this object's monitor. => Error java.lang.IllegalMonitorStateException =>
  • Object.notify => Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation.
  • Object.notifyAll() => Wakes up all threads that are waiting on this object's monitor. The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.

合理的使用 wait + notify 就可以达到调度不同线程的目的 => wait() 之后 notify 进程才会一直走下去,如果先 notifywait 那么进程将会一直等待 notify

synchronized(obj) {
    while(condition does no hold) {
        obj.wait();
    }
}

synchronized(obj) {
    obj.notify();
}

线程池

线程是非常昂贵的 => Java 线程模型的缺陷 => Java 的线程调度完全依赖于操作系统的依赖调度,所以线程是非常昂贵的,每个线程都要占用操作系统一部分资源

线程池是预先定义好的若干个线程 => 省去每次创建 | 销毁线程的开销 => java.concurrent.Executors + java.util.Callable + java.util.concurrent.Future

  • java.util.Callable => 用于描述一个抽象任务的 interface => 泛型 + 有返回值 + 可以抛出异常
  • java.util.concurrent.Future
    ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
    Future submit = executorService.submit(Callable);
    submit.get();
    

创建多线程方法

  • for loop + new Thread()
  • 线程池 => java.concurrent.Executors + java.util.Callable + java.util.concurrent.Future
  • java.util.concurrent.ForkJoinPool
    • Arrange async execution => execute method
    • Await and obtain result => invoke method
    • Arrange exec and obtain Future => submit method
  • java.util.Collection.parallelStream() => Collection.parallelStream().forEach(item => // do something)

调度线程方法

知识点

  1. 如何查看一个数据是否是线程安全的?
    进入方法后,搜索 thread => <strong>Note that this implementation is not synchronized.</strong> => 线程不安全
  2. HashMap 死循环
  3. 除非特意提及线程安全,不然都是线程不安全的
  4. Collections.synchronized 方法可以将一些线程不安全的类 | 方法变为 synchronized
  5. monitor => 在 Java 世界中,使用 monitor 代表 synchronized 锁住的对象
  6. Runnable 中的 run
    • 没有返回值
    • 没有声明抛出的异常
  7. // TODO 广度优先算法???实现广度优先遍历一棵树 => 使用队列,可以学习队列的相关知识
  8. ArrayList 从尾部删除更有效率
  9. SpotBugs => 自动化字节码 Bug 检查工具
  10. == 在 Java 中永远不会被自动处理成 .equals()
    Integer i = 1;
    if(i == 1) {} // Integer 自动拆箱,调用 intValue() 方法,导致空指针异常
    
  11. flyway => 数据库迁移工具 => 数据库结构的版本控制 => 可以方便的生成数据库结构
  12. ORM => Object Relation Mapping => 对象关系映射 => ORM 框架 mybatis
  13. DAO => Database Access Object
  14. DateInstant 都可以用来表示时间戳,Instant 是 1.8 出来的,尽可能的使用 Instant
  15. BufferedReaderreadLine 方法,必须套 while(true)
    BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        // do something
    }
    
  16. 遇到 interface 实现即可
  17. Optional => A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

相关文章

  • 带你搞懂Java多线程(五)

    带你搞懂Java多线程(一)带你搞懂Java多线程(二)带你搞懂Java多线程(三)带你搞懂Java多线程(四) ...

  • 带你搞懂Java多线程(六)

    带你搞懂Java多线程(一)带你搞懂Java多线程(二)带你搞懂Java多线程(三)带你搞懂Java多线程(四)带...

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • java多线程--Callable

    **移步[java多线程系列文章]Java多线程(二十二)---LockSupport工具Java 停止线程 一、...

  • android 多线程 — 线程的面试题和答案

    这里都是我从各个地方找来的资料,鸣谢: Java多线程干货系列—(一)Java多线程基础 JAVA多线程和并发基础...

  • 5月份第一周学习安排

    学习内容: java多线程及线程同步的方法(使用) java多线程各种同步方法的原理和优缺点 java多线程设计模...

  • 带你搞懂Java多线程(四)

    带你搞懂Java多线程(一)带你搞懂Java多线程(二)带你搞懂Java多线程(三) 什么是线程间的协作 线程之间...

  • Java基础(六)

    多线程 Java多线程并发 1.1 JAVA 并发知识库 1.2 JAVA 线程实现/创建方式 1.2.1 继承 ...

  • (五) volatile关键字

    Java多线程目录 1 背景 理解Java多线程的内存抽象逻辑请阅读java多线程内存模型,当代操作系统,处理器为...

  • Java多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

网友评论

      本文标题:Java 多线程

      本文链接:https://www.haomeiwen.com/subject/ftnmultx.html