线程Thread

Java线程创建的过程

线程Thread

由图可知,只有当调用一个线程类实例或其子类实例的start()方法才会真正创建一个线程,单独调用执行线程类实例或其子类实例的run()方法,只是当做一种单线程的方法调用

线程基础接口Runnable

创建线程有两种方式,一种是直接使用Thread类或其子类,一种是Runnable接口的实现类或实现类的子类,二者本质上是一种方式,因为Thread类也是Runnable的实现类
线程Thread

Thread类重要属性or方法

线程Thread

daeom:布尔变量,为true时表示当前线程是守护线程,守护线程一定是在主线程结束后立即结束
join():t.join()所在线程需要等待t线程执行完毕后,才能继续往下执行

wait & notify方法

线程Thread

wait()、notify()都是Object类的方法,意味着所有类都能作为锁对象
Thread.sleep()与Object.wait()的区别:前者仅仅表示取消对CPU资源的的占用和抢夺,后者除了包含前者的功能,同时还意味着放弃对象锁的持有

Thread 的状态改变操作

  1. Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入 TIMED_WAITING 状态,但不释放对象锁,millis 毫秒后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
  2. Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的 CPU 时间片,但不释放锁资源,由运行状态变为就绪状态,让 OS 再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞。该方法与sleep() 类似,只是不能由用户指定暂停多长时间。
  3. t.join()/t.join(long millis),当前线程里调用其它线程 t 的 join 方法,当前线程进入WAITING/TIMED_WAITING 状态,内部调用了 t.wait,所以会释放t这个对象上的同步锁(不影响t线程本身持有的锁)。线程 t 执行完毕或者 millis 时间到,当前线程进入就绪状态。其中,wait 操作对应的 notify 是由 jvm 底层的线程执行结束前触发的。
  4. obj.wait(),当前线程调用对象的 wait() 方法,当前线程释放 obj 对象锁,进入等待队列。依靠notify()/notifyAll() 唤醒或者 wait(long timeout) timeout 时间到自动唤醒。唤醒会,线程恢复到 wait 时的状态。
  5. obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll() 唤醒在此对象监视器上等待的所有线程。

Thread 的中断与异常处理及应用

  1. 线程内部自己处理异常,不溢出到外层(Future 可以封装)。
  2. 如果线程被 Object.wait, Thread.join和Thread.sleep 三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt() 将不起作用,直到执行到wait/sleep/join 时,才马上会抛出InterruptedException。
  3. 如果是计算密集型的操作怎么办?
    分段处理,每个片段检查一下状态,是不是要终止

线程状态

线程Thread

线程Thread

线程安全

线程Thread

  1. 多个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。比如线程A要求访问变量c的原值,线程B会不定时更新变量c的值,此时就存在访问顺序的问题
  2. 导致竞态条件发生的代码区称作临界区。
  3. 对于上述两项不进行恰当的控制,会导致线程安全问题。解决线程问题常见的两种方式:
    1、使用同步synchronized/volatile/final
    2、显式的加锁

并发相关的性质

  1. 原子性:即原子操作,注意跟事务 ACID 里原子性的区别与联系。对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行
    线程Thread
  2. 可见性:对于可见性,Java 提供了 volatile 关键字来保证可见性。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过 synchronized 和 Lock 也能够保证可见性,synchronized 和 Lock 能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。区别在于volatile 并不能保证原子性,synchronized 和 Lock可以保证原子性
  3. 有序性:Java 允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。可以通过 volatile 关键字来保证一定的“有序性(synchronized 和 Lock也可以)
    happens-before 原则(先行发生原则):
    1. 程序次序规则:一个线程内,按照代码先后顺序
    2. 锁定规则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作
    3. Volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
    4. 传递规则:如果操作 A 先行发生于操作 B,而操作 B 又先行发生于操作 C,则可以得出 A 先于 C
    5. 线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每个一个动作
    6. 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过 Thread.join() 方法结束、
      Thread.isAlive() 的返回值手段检测到线程已经终止执行
    8. 对象终结规则:一个对象的初始化完成先行发生于他的 finalize() 方法的开始

volatile通过栅栏效应实现有序性,如下图所示,对于x、y先后两次的赋值因为中间volatile
flag的赋值导致它们多线程的执行顺序一定是代码编写的顺序
线程Thread

解决线程问题常见的两种方式

synchronized 的实现

线程Thread

  • 同步方法和同步对象与同步代码块不同,同步方法和同步对象持有的锁对象都是方法所在类对象或同步对象本身,同步代码块根据传入的锁对象不同而持有不同的锁对象
  • 偏向锁即为该锁对象会更倾向于给前一次持有该锁对象的线程重新持有
  • 使用同步关键字持有的锁对象加的锁可能是偏向锁、轻量级锁、重量级锁中的任意一种,其中偏向锁、轻量级锁可以认为是乐观锁(CAS),会尝试去做同步操作,如果做成了就不用加很重的锁,如果做不成就会加重量级

volatile

  1. 每次读取都强制从主内存刷数据
  2. 适用场景: 单个线程写;多个线程读
  3. 原则: 能不用就不用,不确定的时候也不用
  4. 可选替代方案: Atomic 原子操作类

final

线程Thread

  • final 声明的引用数据类型与原生数据类型在处理时区别:
    1、final的原生类型变量,在编译时会被常量替代,且等同于final static修饰
    2、final修饰的引用类型变量指向的对象不能改,但是指向的对象的内部属性可以改
上一篇:c++ condition_variable的wait 语法糖


下一篇:详解threading模块:Condition类的使用