10

Java 多线程学习——基础概念

 3 years ago
source link: https://suiyia.github.io/2019/10/14/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E2%80%94%E2%80%94%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Java 多线程学习——基础概念

2019-10-14 Java多线程 42 评论 字数统计: 2.5k(字) 阅读时长: 9(分)

并行与并发

  • 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生(注意的是时间间隔,间隔是有大小的)。

  • 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。

  • 解释三:在一台处理器上同时处理多个任务,在多台处理器上同时处理多个任务。如 hadoop 分布式集群

这里另外再用一张经常用的图来解释并发与并行之间的关系

  • 并发就像只有一台咖啡机能制造咖啡(单核 CPU)的店铺,然而有序列 A 和序列 B 两队人在排队,在一个时间段内(咖啡机可能造出多杯咖啡),序列 A 和序列 B
    的人可能都会领取到咖啡,在这个时间段内多个人领取到咖啡这个过程称为 并发,这个时间段类似于多个 CPU 时间片,一个 CPU 时间片执行一个任务。

  • 并行就像有两台咖啡机的店铺,然后在同一个时刻,都会有两个人领取到咖啡,这样的过程称为 并行。

upload successful

Java 多线程实现方式

平时所说的多线程,其实就是并发知识的底层实现,Java 现在有多种多线程的实现方式,最基础的两种方式是:

  • 方式一:继承 Thread 类(一般不用,继承会把类的特性限制太死)
public class MyThread extends Thread {  
  public void run() {
   System.out.println("MyThread.run()");
  }
}

MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
  • 方式二:实现 Runnable 接口
public class ThreadB implements Runnable {

@Override
public void run() {
System.out.println("ThreadB");
}
}

Thread thread = new Thread(new ThreadB());
thread.start();
  • 还有其它实现方式,如实现 Callable 接口、Future 等,参考 JAVA多线程实现的四种方式

  • 这里 run() 方法内的逻辑表示线程执行的业务逻辑,而让线程启动的方法是 start()

  • start() 方法,即启动线程的方法,通过了解 start() 源码,执行 start 时,会有两个线程并发执行,当前线程去调用 start() 方法,另外一个线程会调 run() 方法开始具体的业务逻辑。

  • 需要注意的是,一个线程不能被启动多次,只有当它业务逻辑执行完成之时,才会启动下一次,否则会抛出 IllegalThreadStateException 异常。

线程的六种状态

根据 java.lang.Thread.State 枚举类源码可知,有 6 种不同的线程状态。These states are virtual machine states which do not reflect any operating system thread states.

upload successful
  • NEW(新建):仅定义了一个线程对象,还未调用它的 start() 方法。

    Thread state for a thread which has not yet started.

  • RUNNABLE(可运行):调用了线程的 start 方法,已经被 JVM 执行,但还在等待系统其它资源,如 CPU 时间片、I/O 等,根据操作系统知识可进一步将可运行状态细分为就绪、运行中两种状态(Java 并没有进一步细分)。

    Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

    • 就绪:调用了 start 方法,但没有得到 CPU 时间片的状态
    • 运行中:得到了 CPU 时间片的状态,正在执行
  • BLOCKED(阻塞):线程等待 monitor lock 的状态,只有得到了这个对象锁之后,状态才会由阻塞变为就绪

    等待 monitor lock 进入被锁住的代码块,或者在调用 wait() 方法之后又进入被锁住的代码块(A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling {@link Object#wait() Object.wait}.)

// TODO 下面内容还待考证

- 等待阻塞:运行的线程执行 o.wait() 方法,JVM 会把该线程放入等待队列(waitting queue)中

- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中

- 其他阻塞:运行的线程执行 Thread.sleep(long ms) 或t.join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态,当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态
  • WAITING(等待)

    A thread in the waiting state is waiting for another thread to perform a particular action.

    • 等待另一个线程的特定操作的状态,等待被其他线程唤醒

    • 等待的过程是主动等待的,而阻塞是得不到对象锁而被动阻塞住

    • 当线程拿到锁之后,调用相应锁对象的 wait()、join()、继承 LockSupport 类的 park() ,调用其中的方法线程就会处于这个状态。

  • TIMED_WAITING(有限期等待或超时等待)

    Thread state for a waiting thread with a specified waiting time.

    • 等待另一个线程执行指定等待时间的操作的线程处于此状态
    • 等待另一个线程特定时间,时间过后会自动唤醒
    • 线程执行 sleep()、wait(long)、join(long)、LockSupport.parkNanos 、LockSupport.parkUntil 方法时会处于这种状态
  • TERMINATED(终止):线程业务逻辑执行完成退出的状态
    Thread state for a terminated thread.The thread has completed execution.

线程具有的方法

主要讲解它的方法以及状态的切换、锁持有状态

  • Thread.sleep(long millis),一定是当前线程调用此方法,线程进入 TIME_WAITING 状态,但不释放对象锁,millis 后线程自动苏醒进入就绪状态。

    作用:给其它线程执行机会的最佳方式。

  • Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的 cpu 时间片,由运行状态变会就绪状态,让 OS 再次选择线程。

    作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞。

  • t.join()/t.join(long millis),当前线程里调用其它线程 t 的 join 方法,当前线程进入 TIME_WAITING 状态,当前线程不释放已经持有的对象锁。线程 t 执行完毕或者 millis 时间到,当前线程进入就绪状态。

  • obj.wait(),当前线程调用对象的 wait() 方法,当前线程释放对象锁,进入等待队列。依靠 notify()/notifyAll() 唤醒或者 wait(long timeout) timeout 时间到自动唤醒。

  • obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll() 唤醒在此对象监视器上等待的所有线程。

终止线程的 4 种方式

  • 退出标识:有些线程需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程

  • Interrupt() 方法

    • 线程处于阻塞状态:调用 interrupt() 方法会抛出异常,此时要退出线程必须 catch 异常,并使用 break 方法退出。

    • 线程不处于阻塞状态:使用 isInterrupted() 判断线程的中断标志来退出循环,当使用 interrupt() 方法时,中断标志就会置 true

  • stop 方法:thread.stop() 调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop() 后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性

wait 和 sleep 区别

  1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  2. wait() 和 sleep() 都可以通过 interrupt() 方法 打断线程的暂停状态 ,从而使线程立刻抛出 InterruptedException。
    如果线程 A 希望立即结束线程 B,则可以对线程 B 对应的 Thread 实例调用 interrupt 方法。如果此刻线程 B 正在 wait/sleep /join,则线程 B 会立刻抛出 InterruptedException,在 catch() {} 中直接 return 即可安全地结束线程。
    需要注意的是,InterruptedException 是线程自己从内部抛出的,并不是 interrupt() 方法抛出的。对某一线程调用 interrupt() 时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出 InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出 InterruptedException 。
  1. Thread类的方法:sleep(),yield()等 。Object的方法:wait()和notify()等
  2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3. wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用
  4. sleep 必须捕获异常,而 wait,notify和notifyAll 不需要捕获异常

所以sleep() 和 wait() 方法的最大区别是:sleep() 睡眠时,保持对象锁,仍然占有该锁;而 wait() 睡眠时,释放对象锁。但是 wait() 和 sleep() 都可以通过 interrupt() 方法打断线程的暂停状态,从而使线程立刻抛出 InterruptedException(但不建议使用该方法)。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK