最近在准备面试,把知识点复习一遍,整理出的笔记记录下,里面会穿插代码和面试例题。
内容不是原创,是总结和收集,并在理解的基础上进行一些完善,如果侵权了请联系作者,若有错误也请各位指正。因为收集的时候忘记把来源记录下来了,所以就不po出处了,请见谅(这是个坏习惯,一定改)。
Java基础进阶——多线程与JUC(上)
本章内容主要讲Java中的多线程开发的JUC包,Java中有关多线程开发的操作基本上都在这几个包里。本章内容主要介绍JUC包内的一些概念和用法,与多线程相关的基本概念可以参考之前的内容: Java基础(十三):多线程.
本章为上篇,将分为上下两篇。上篇涉及Synchronized的实现原理,以及锁升级的介绍。
多线程与JUC
JUC是什么
JUC指的是java.util 下几个包的简称,涉及多线程开发的相关操作。
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
线程的状态
我们看一下Thread.State的源码中的枚举,线程的生命周期内有以下6种状态,并将其注释翻译出来:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW, //新生状态。 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。是线程还未调用start()方法之前的状态。
/**
* 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.
*/
RUNNABLE, //运行状态。 运行的线程状态。线程正在Java虚拟机中执行(Running),但它可能正在等待来自操作系统(如处理器)的其他资源(Ready)。包含了Ready和Running两种状态。
/**
* Thread state for a thread blocked waiting for a monitor lock.
* 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}.
*/
BLOCKED, //阻塞状态。 是正在等待锁监视器的状态。等待进入synchronized块/方法;再次进入synchronized块/方法后等待响应;调用了Object.wait()方法。
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING, //等待状态。 线程处于无限期等待状态中。由于调用以下方法之一,线程处于等待状态:Object.wait(),没有超时时间,无限期;Thread.join(),没有超时时间,无限期;LockSupport#park()。
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING, //超时等待。 线程在特定的时长内等待。同样是以下方法之一导致:Thread.sleep(long)/Object.wait(long)\Thread.join(long)\LockSupport.parkNanos()\LockSupport.parkUntil()。
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;//终止状态。 线程已经完成操作。
}
一个线程的生命周期如下图所示:
- 新生状态(New)
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。是线程还未调用start()方法之前的状态。
- 运行状态(Runnable)
调用start()方法之后线程处于可运行状态的线程了。Runnable表示运行的线程状态,它可能正在Java虚拟机中执行,它也可能正在等待来自操作系统(如处理器)的其他资源。即包含了就绪(Ready)和运行(Running)两种状态。
- 阻塞状态(Blocked)
是正在等待锁监视器的状态。等待进入synchronized块/方法;再次进入synchronized块/方法后等待响应;调用了Object.wait()方法。
- 等待状态(Waiting)
线程处于无限期等待状态中。由于调用以下方法之一,线程处于等待状态:Object.wait(),没有超时时间,无限期;Thread.join(),没有超时时间,无限期;LockSupport#park()。
wait()与wait(0)同义,意思是无限期等待;sleep(0)的意思是不等待,等待时间为0。
- 超时等待(Timed_Waiting)
线程在特定的时长内等待。同样是以下方法之一导致:Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()。
- wait方法之后线程进入等待池中等待,它没有CPU的执行权,需调用notify或者notifyAll唤醒,随机唤醒。等待期间可以调用里面的同步方法,其他线程可以访问。
- sleep方法拥有CPU的执行权,它会在等待时间结束后自动唤醒。等待期间不释放锁,其他线程不可以访问。
- 死亡/终止状态(Terminated)
线程已经完成操作。已经死亡的线程不能使用start()重新唤醒,死亡等于结束了。线程会以如下3种方式结束,结束后就处于死亡状态:
- run()或call()方法执行完成,线程正常结束。
- 线程抛出一个未捕获的Exception或Error。
- 直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。
wait和sleep的区别
- 来自不同的类:wait属于Object类,sleep属于Thread类。
- 是否释放锁:wait会释放锁,sleep不会释放锁。
wait之后线程进入等待池中等待,它没有CPU的执行权,需调用notify或者notifyAll唤醒,随机唤醒。等待期间可以调用里面的同步方法,其他线程可以访问。
sleep方法拥有CPU的执行权,它会在等待时间结束后自动唤醒。等待期间不释放锁,其他线程不可以访问。
若它们唤醒时没有cpu资源则需继续等待资源。 - 使用范围:wait需要在同步方法或者同步代码块中使用;sleep可以在任何地方使用,注意sleep是静态方法,也就是说它只对当前对象有效。通过对象名.sleep()想让该对象线程进入休眠是无效的,它只会让当前线程进入休眠。
- 捕获异常:wait可以不捕获异常,sleep必须捕获异常。
- wait()的使用
wait()需要先获取对象的monitor锁,才能调用,否则报出异常java.lang.IllegalMonitorStateException,这也是wait需要在同步代码块中使用的原因。常用写法:
synchronized(obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程安全
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
简单来说:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
线程安全在三个方面体现:
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
- 可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
线程安全的级别
- 1)不可变
像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
- 2)绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet
- 3)相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast(快速失败)机制。
- 4)线程非安全
ArrayList、LinkedList、HashMap等都是线程非安全的类,在多线程下操作往往因为并发导致输出容量少于实际容量,或者因为扩容的重复导致越界异常的现象。
Synchronized
线程Thread 类是一个单独的资源类,内含属性和方法,可直接使用。Thread 类是函数式接口,使用时将资源类放入线程即可。其构造方法中实现了Runnable接口,可以将Runnable当做资源类放入。
public class Test {
public static void main(String[] args)