1, 线程与进程
进程:
进程是程序运行以及资源分配的基本单位, 一个程序至少有一个进程.
如下图所示:
线程:
线程是 CPU 调度和分配的基本单位, 一个进程至少有一个线程.
同一个进程中的线程共享进程资源(减少切换, 可提高效率), 且可以并发执行.
2, 并发和并行
并发:
指的是同一时间间隔执行多个事件, 强调的是一段时间内可以做多个不同的事.
比如在一分钟内, 先吃一口饭, 再喝一口水, 接着说一句话等等.
并行:
指的是同一时刻执行多个事件, 强调的是同一时刻可以做多个不同的事.
比如在吃饭的同时, 一边听歌, 看电视.
3, 线程的上下文切换
基本原理:
一个 CPU 在任意时刻只能执行一个线程, 如果有多个线程 (超过 CPU 个数) 存在, CPU 将会采用时间片轮转的方式进行线程切换, 即给每一个线程分配一个时间片, 当一个线程的时间片用完的时候便会处于就绪状态, 并让出 CPU 给其他线程使用, 这就是一次上下文切换.
上下文切换时是通过运行时数据区中的程序计数器来存储各个线程的运行状态的, 以便于下次运行线程时可以接着上次指令继续运行(比如线程 A 做了一次计算准备返回数据时, 切换到了线程 B, 然后又切回线程 A, 是直接返回数据, 而不是再去计算一遍).
程序计数器指的是 JVM 中的一块内存区域, 它可以看作是当前线程所执行字节码的行号指示器, 通过它, Java 可以知道每个线程执行到了哪一步指令(由此可以看出程序计数器是线程私有的).
一般而言, 上下文切换是比较耗费 CPU 时间的一种操作.
4, 线程的几种状态
基本原理:
创建: 指的是生成线程对象, 此时并没有调用 start 方法.
就绪: 调用线程的 start 方法后, 线程便进入了就绪状态, 此时等待系统调度.
运行: 通过系统调度, 开始运行线程中的 run 函数.
等待: 调用了 Object.wait()会进入等待队列. 一般需要等待其他线程做出一些特定的通知或者中断.
超时等待: 该状态与等待状态有一点区别就是它会自动返回, 比如调用 Threa.sleep(long), 在超时之后, 会自动返回进入就绪状态.
阻塞: 指的是去获取锁时(等待进入 synchronized 方法或块), 发现同步锁被占用了, 这时线程会被放入锁池, 等待获取锁.
死亡: 运行完 run 方法, main 方法 (main 方法指的是主线程) 之后正常退出, 也有可能出现异常导致死亡; 死亡的线程不能重新启用, 否则报错.
5, 创建线程的几种方式
继承 Thread 类:
创建一个类继承 Thread, 重写其中的 run 方法, 即线程执行体.
创建继承了 Thread 类的子类实例, 即线程对象
调用线程对象的 start()方法启动线程.
示例代码如下;
- public class ThreadTest {
- public static void main(String[] args){
- new TestThread().start();
- }
- }
- class TestThread extends Thread{
- @Override
- public void run(){
- System.out.println("Test Thread");
- }
- }
实现 Runnable 接口:
创建一个类实现 Runnable 接口, 重写其中的 run 方法, 即线程执行体.
创建实现 Runnable 接口的类实例, 接着将该实例对象作为 target 传入 Thread 类的构造器, 此时创建的 Thread 类才是线程对象.
调用线程对象的 start()方法启动线程.
示例代码如下;
- public class ThreadTest {
- public static void main(String[] args){
- new Thread(new TestRunnable()).start();
- }
- }
- class TestRunnable implements Runnable{
- @Override
- public void run() {
- System.out.println("Test Runnable");
- }
- }
实现 Callable 类:
创建一个类实现 Callable 接口, 重写其中的 call 方法, 即线程执行体, call 方法存在返回值.
创建实现 Callable 接口的类实例, 将该实例对象作为 callable 传入 FutureTask 类的构造器, 此 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值, 可使用 get 方法获取.
将 FutureTask 实例对象作为 target 传入 Thread 的构造器, 此时创建的 Thread 类才是线程对象.
调用线程对象的 start()方法启动线程.
示例代码如下;
- public class ThreadTest {
- public static void main(String[] args){
- FutureTask<Integer> task = new FutureTask(new TestCallable());
- new Thread(task).start();
- try {
- System.out.println(task.get());//get 方法会得到 call 执行完之后的值
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- }
- }
- class TestCallable implements Callable<Integer> {
- @Override
- public Integer call() throws Exception {
- int num = 0;
- while(num <5){
- num++;
- }
- return num;
- }
- }
6,sleep() 和 wait()
sleep():
sleep()方法是线程类 Thread 类的一个静态方法, 如若是在 synchronized 中调用, sleep()方法不会释放锁.
wait():
wait()方法是线程类 Object 类的一个方法, 如若是在 synchronized 中调用, wait()方法会释放锁.
示例代码如下(从控制台输出的时间戳结果可以看出 sleep 没有释放锁, 是阻塞进行的, 而 wait 释放了锁):
- public class ThreadTest {
- protected static volatile Object lock = "lock";
- public static void main(String[] args){
- for(int i=0;i<5;i++){
- new Thread(() -> {
- synchronized(lock){
- System.out.println(Thread.currentThread().getName() + ": 等待开始当前时间戳:" + System.currentTimeMillis());
- try {
- // lock.wait(1000);// 让当前线程进入等待池
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- }
- }
7, 关于 wait() 与 notify(),notifyAll()
notify(),notifyAll():
notify()指的是随机唤醒一个 wait 线程进入对象锁池中竞争锁, 而 notifyAll()是唤醒所有的 wait 线程进入对象锁池中竞争锁.
关于 wait()与 notify()方法示例代码如下:
- public class ThreadTest {
- protected static volatile Object lock = "lock";
- public static void main(String[] args){
- new Thread(new TestRunnable(lock)).start();
- try {
- Thread.sleep(1000);
- System.out.println("sleep 1000ms");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- new TestThread(lock).start();
- }
- }
- class TestRunnable implements Runnable{
- private Object lock;
- public TestRunnable(Object lock){
- this.lock = lock;
- }
- @Override
- public void run() {
- try {
- synchronized (lock){
- System.out.println("begin wait:" + System.currentTimeMillis());
- System.out.println("Release lock........");
- lock.wait();// 让当前线程进入等待池
- System.out.println("end wait:" + System.currentTimeMillis());
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- class TestThread extends Thread{
- private Object lock;
- public TestThread(Object lock){
- this.lock = lock;
- }
- @Override
- public void run() {
- synchronized (lock){
- System.out.println("begin notify:" + System.currentTimeMillis());
- System.out.println("do something........");
- lock.notify();// 从等待池中随机唤醒一个 wait 线程进入锁池竞争锁
- // lock.notifyAll();// 从等待池中随机唤醒所有 wait 线程进入锁池竞争锁
- System.out.println("end notify:" + System.currentTimeMillis());
- }
- }
- }
8,start() 和 run()
start():
此方法是用来启动一个线程的, 执行了 start()方法之后, 线程将进入就绪状态, 之后会自动执行 run 函数中的内容, 无需等待, 是真正意义上的实现了多线程.
run():
单独运行此方法, 只是将其当做一个普通函数在使用, 每次执行必须等待 run 函数里面的内容完成, 才能接着往下执行, 无法实现多线程.
9, 线程安全性
主要体现在下面三方面:
原子性: 同一时刻只允许一个线程对数据进行操作(atomic 开头的原子类, synchronized,Lock).
可见性: 一个线程对共享变量的修改, 可以及时地被其他线程观察到,(synchronized,volatile,Lock).
有序性: 指的是可以观察到其他线程的指令执行顺序, 由于指令重排, 一般情况下是无序的(happens-before 原则).
10, 线程死锁
死锁:
指的是多个线程在执行过程中, 因争夺资源而陷入环路阻塞的一种现象.
示例代码如下(运行之后, 两个线程陷入死锁中, 如果没有外力中断, 将会一直锁定):
- public class ThreadTest {
- // 共享资源 A
- private static Object A = new Object();
- // 共享资源 B
- private static Object B = new Object();
- public static void main(String[] args){
- new Thread(() -> {
- synchronized (A){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---A");
- try {
- Thread.sleep(1000);// 此处休眠是为了让其他线程获得执行机会
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + ": 去获取资源 ---B");
- synchronized (B){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---B");
- }
- }
- },"Thread-01").start();
- new Thread(() -> {
- synchronized (B){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---B");
- try {
- Thread.sleep(1000);// 此处休眠是为了让其他线程获得执行机会
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + ": 去获取资源 ---A");
- synchronized (A){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---A");
- }
- }
- },"Thread-02").start();
- }
- }
死锁四个必要条件即如何避免死锁:
互斥条件: 一个资源在任意时刻只能被一个线程占用, 如果有其他线程请求该资源, 需要等待原线程释放资源(此条件不能被破坏, 因为锁本身就是为了互斥访问).
请求和保持条件: 一个线程已经获取了一部分资源, 又去请求其他资源, 如果其他资源正被占用, 原线程陷入阻塞, 且不会释放自己占用的资源(一次性申请所有资源; 或者阻塞时, 释放自己占用的资源).
不可剥夺条件: 一个线程已经获得的资源, 在自己未使用完之前不能被其他线程剥夺, 只能自己使用结束后释放(如果去获取正在被其他线程使用的资源而阻塞时, 可以释放自己占用的资源).
环路等待条件: 多个进程之间形成一种头尾相接的循环等待资源关系(按一定的顺序来获取资源).
针对上述例子而言, 可以让现线程 1 先获取资源 AB, 执行完之后, 再让线程 2 获取资源 AB, 改动如下(执行顺序为: 线程 1 先获取资源 A, 接着释放 CPU, 线程 2 执行准备去获取资源 A, 发现资源 A 已被占用, 此时线程 2 阻塞, 然后一秒之后释放 CPU, 线程 1 接着执行, 去获取资源 B, 正常获取, 执行完毕, 释放 CPU; 线程 2 开始获取资源 A, 由于线程 1 已经执行完毕释放了锁, 所以线程 2 正常获取资源 A, 接着休眠一秒释放 CPU, 然后又去获取资源 B, 同样正常获取, 执行完毕):
- public class ThreadTest {
- // 共享资源 A
- private static Object A = new Object();
- // 共享资源 B
- private static Object B = new Object();
- public static void main(String[] args){
- new Thread(() -> {
- synchronized (A){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---A");
- try {
- Thread.sleep(1000);// 此处休眠是为了让其他线程获得执行机会
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + ": 去获取资源 ---B");
- synchronized (B){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---B");
- }
- }
- },"Thread-01").start();
- new Thread(() -> {
- // 线程 2 去获取 A 时被阻塞
- synchronized (A){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---A");
- try {
- Thread.sleep(1000);// 此处休眠是为了让其他线程获得执行机会
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + ": 去获取资源 ---B");
- synchronized (B){
- System.out.println(Thread.currentThread().getName() + ": 得到资源 ---B");
- }
- }
- },"Thread-02").start();
- }
- }
- (以上所有内容皆为个人笔记, 如有错误之处还望指正.)
来源: http://www.bubuko.com/infodetail-3229486.html