Java 多线程
本文借鉴 https://github.com/ZhongFuCheng3y/3y
- 并行
- 多个任务在多个处理器同时进行
- 并发
- 多个任务轮流 在CPU进行执行。
- Thread,Runnable
- Thread不可以共享实例,Runnable可以共享
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期。
)]
-
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
-
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
-
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
-
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
-
创建一个线程
Java 提供了三种创建线程的方法:
- 通过实现 Runnable 接口;
-
通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
-
继承ThreadFactory接口 实现方法newThread 返回一个线程
-
Thread 方法
下表列出了Thread类的一些重要方法:
序号 | 方法描述 |
---|---|
1 | public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() 中断线程。 |
8 | public final boolean isAlive() 测试线程是否处于活动状态。 |
9 | join |
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。
序号 | 方法描述 |
---|---|
1 | public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
2 | public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
3 | public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
4 | public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 |
5 | public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 |
-
线程的几个主要概念
在多线程编程时,你需要了解以下几个概念:
- 线程同步
- 线程间通信
- 线程死锁
- 线程控制:挂起、停止和恢复
-
线程结束
-
执行exit()方法,run放方法执行完成或者抛出异常
-
线程属性
-
线程优先级
-
默认子线程跟父级线程拥有相同的优先级
-
可以通过setPriority设置优先级
-
static int MIN_PRIORITY
-
-
-
守护线程
- Deamon 为其他线程提供服务,等待其他线程执行完后自动结束。
-
t.setDeamon(true) 必须在线程启动之前设置线程,启动后设置会报错
-
public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { //这里 throw new IllegalThreadStateException(); } daemon = on; }
-
-
未捕获异常处理器
-
Thread.UnCaughtExcetionHander thread的内部接口
-
void uncaughtException(Thread t, Throwable e) 方法
-
-
可以通过thread对象的setUncachedExcetion设置,或者通过Thread的静态方法setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
-
-
控制线程
- 对象方法 join线程
- 让所在的程序等待调用join()方法的线程先执行,实现是调用了wait()方法
- 后台线程
- Deamon 为其他线程提供服务,等待其他线程执行完后自动结束。
- 线程睡眠
- sleep 让当前执行的线程进行阻塞状态。
- 线程让步
- yield 使当前线程进入就绪状态
线程同步和锁
-
同步监视器 synchronized 使用对象的内置锁进行加锁 ,也叫互斥锁
-
同时保证了 原子性和可见性
-
synchronized(对象){} 同步代码块 public synchronized void xxxx(){} 同步方法,锁的调用当前方法的对象
-
释放监视器的锁定
- 同步监视器的代码执行完全,或者是监视器对象的wait()方法,或者是出现异常。
-
-
同步锁 Lock接口 ,支持中断、超时不获取、是非阻塞的。
- 实现类
- ReentrantLock 可重入锁
- 内部类
Sync
继承AbstractQueuedSynchronizer
实现
- 内部类
- 允许显示调用Lock对象进行加锁 lock.lock() ,但是要进行解锁。
- 支持Condition对象
AQS有称同步器
-
为阻塞锁和同步器提供了一个框架,依赖于先进先出的等待队列和一个int类型的statu表示锁的释放和获取。
-
AbstractQueuedSynchronizer
Lock包下的三个抽象类之一 -
基于先进先出的等待双向队列(CLH),和int 的原子statu,定义了ConditionOject内部类,启动两种线程模式(独占,共享)。CAS算法实现修改statu值。
-
Node结点,每一个获取锁失败的都成为一个Node结点
-
acquire方法用于获取资源,release()用户释放资源。
-
AQS是ReentrantReadWriteLock和ReentrantLock的基础,因为默认的实现都是在内部类Syn中,而Syn是继承AQS的
Callable
-
带有返回类型的任务执行和接口
Future
** -
Callable
可以带有返回值,和抛出异常 -
通常结合
ExecutorService
来创建一个线程,通过返回的Future
来获取返回值. -
Future
代表任务的生命周期,可以用来接受Callable的call()的返回。- 子类
FutureTask
可以通过构造方法 加入一个任务 ,使用run方法执行。
- 子类
线程池
-
java提供内置线程池
-
Executor工厂类提供静态方法创建 线程池
ExecutorService
- 常见线程池 CachedThreadPool,FixedThreadPool,SingleThreadPool
-
通过线程池 submit()添加 线程返回Future对象 ,execute()执行线程
-
ForkJoinPool线程池
- 其主要的不同在于采用了工作窃取算法(work-stealing):所有池中线程会尝试找到并执行已被提交到池中的或由其他线程创建的任务。这样很少有线程会处于空闲状态,非常高效。这使得能够有效地处理以下情景:大多数由任务产生大量子任务的情况;从外部客户端大量提交小任务到池中的情况。
-
- 线程池的状态
- running 可接受新任务
- shutdown 不接受任务,并执行完剩下任务
- stop 终止所有任务
- ditying 表示任务以执行完毕
- terminated 终止线程池
-
-
ThreadPoolExecutor
-
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 指定核心线程数量,指定最大线程数量,允许线程空闲时间,时间对象,阻塞队列,线程工厂,任务拒绝策略
+ **线程数量要点**: - 如果运行线程的数量**少于**核心线程数量,则**创建新**的线程处理请求 - 如果运行线程的数量**大于**核心线程数量,**小于**最大线程数量,则当**队列满的时候才创建新**的线程,否则入队 - 如果核心线程数量**等于**最大线程数量,那么将**创建固定大小**的连接池 - 如果设置了最大线程数量为**无穷**,那么允许线程池适合**任意**的并发数量 + **排队策略要点**: - 同步移交:**不会放到队列中,而是等待线程执行它**。如果当前线程没有执行,很可能会**新开**一个线程执行。 - 无界限策略:**如果核心线程都在工作,该线程会放到队列中**。所以线程数不会超过核心线程数 - 有界限策略:**可以避免资源耗尽**,但是一定程度上减低了吞吐量 + **拒绝任务策略:** - 直接抛出异常 - 使用调用者的线程来处理 - 直接丢掉这个任务 - 丢掉最老的任务 + **关闭线程池** + 调用shutdown()后,线程池状态立刻**变为SHUTDOWN**,而调用shutdownNow(),线程池状态**立刻变为STOP**。 + shutdown()**等待任务执行完**才中断线程,而shutdownNow()**不等任务执行完**就中断了线程
-
-
异步线程池
CompletionService
只有一个实现类ExecutorCompletionService
-
public interface CompletionService<V> { // 提交 Future<V> submit(Callable<V> task); Future<V> submit(Runnable task, V result); // 获取 Future<V> take() throws InterruptedException; //有阻塞 Future<V> poll(); //没阻塞 返回null Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException; }
-
-
原子性
- 除
double
和long
的基本类型外,其他基本类型的简单操作都可以认为具有原子性的
- 除
-
Threadlocal
管理和创建线程本地存储,会在不同线程出现不同的值。使不同线程拥有自己的值- 常用方法 get( ) set() setInitialValue() 取值,设置值,初始化值
- 实现原理 ,每一个Thread对象有一个ThreadLocalMap对象,ThreadLocalMap通过Entry来存储对应Threadlocal对象和值.
-
中断
- 对象的interrupte() 方法会提供打断机制,如果进程被阻塞会抛出interrupt异常,且设置中断标志为false
- interrupte() 会对thread对象设置标识位,可以用interrupted()来判断是否有中断标志。
- 静态方法interrupted()–>会清除中断标志位
- 实例方法isInterrupted()–>不会清除中断标志位
- interrupte() 会对thread对象设置标识位,可以用interrupted()来判断是否有中断标志。
- 也可以通过线程池的 Future.cancel()方法打断单个,Executor.shutdownNow()打断所有
- 对象的interrupte() 方法会提供打断机制,如果进程被阻塞会抛出interrupt异常,且设置中断标志为false
-
线程协作
- Object的wait ,notify , lock.newContidion的await(),signal()
- wait()会将线程挂起,同时释放锁,等待被notity唤醒。必须用在同步锁 syncho代码块里
-
volatile :只保证可见性,有序性,即被修改后的值能被其他线程看见,即其他线程读取该值时强制从内存里面读。
-
juc 里面提供类许多安全的委托
-
JDK中有atomic包提供给我们实现原子性操作
-
https://blog.csdn.net/eson_15/article/details/51553338. 原子类基本操作
-
-
死锁
- 原因 : 线程拥有其他需要的资源,线程需要其他拥有的资源,线程都不放弃自己的资源
- 解决死锁
- 固定顺序锁
- 开放调用锁
- 使用定时锁
- 死锁检查 JconsoleJDK,Jstack
同步工具类
用来更好的协助线程通信
-
CountDownLatch
初始化对象时 ,设置AQS的状态变量count初值,调用await()使线程进入AQS阻塞队列等待count为零,countDown()原子性使count减一。 -
CyclicBarrier
初始化对象时 ,设置count初值, await()设置线程等待其他一起线程到达,可从重复用,使用ReentrantLock实现。 -
Semaphore
初始化对象时 ,设置AQS的状态变量count初值count初值,控制一组线程执行,acquire() 原子性使减一,release添加一个。 -
CountDownLatch(闭锁)
-
- 某个线程等待其他线程执行完毕后,它才执行(其他线程等待某个线程执行完毕后,它才执行)
-
CyclicBarrier(栅栏)
-
- 一组线程互相等待至某个状态,这组线程再同时执行。
-
Semaphore(信号量)
-
- 控制一组线程同时执行。
Atomic
-
CAS
- 原子操作的一种,一种乐观锁。
- cpmpare and swap。保存了三个值 ,内存值,期望值,要修改的值,如果在写入的时候发现,期望值于内存值不一样就不再写入,失败过后可能进行自旋 或者 都不做。
-
原子类
-
我们可以对其进行分类:
-
基本类型:
-
- AtomicBoolean:布尔型
- AtomicInteger:整型
- AtomicLong:长整型
-
数组:
-
- AtomicIntegerArray:数组里的整型
- AtomicLongArray:数组里的长整型
- AtomicReferenceArray:数组里的引用类型
-
引用类型:
-
- AtomicReference:引用类型
- AtomicStampedReference:带有版本号的引用类型
- AtomicMarkableReference:带有标记位的引用类型
-
对象的属性:
-
- AtomicIntegerFieldUpdater:对象的属性是整型
- AtomicLongFieldUpdater:对象的属性是长整型
- AtomicReferenceFieldUpdater:对象的属性是引用类型
-
JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder
-
- 是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。
-
-
Atomic包下的类基本都是用Unsafe实现的包装类,Unsafe提供类类似指针操作内存的操作。
-
CAS 存在ABA问题
- 解决ABA问题 使用AtomicStampedReference和AtomicMarkableReference类。
-
LongAdder性能比AtomicLong要好
如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
-
使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS会成功,所以其他线程会不断尝试自旋尝试CAS操作,这会浪费不少的CPU资源。
-
而LongAdder可以概括成这样:内部核心数据value分离成一个数组(Cell),每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。
-
- 简单来说就是将一个值分散成多个值,在并发的时候就可以分散压力,性能有所提高。
-
-