关于多线程

#进程与线程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。

线程是比进程更小的执行单位,一个进程在其执行的过程中可以产生多个线程。线程共享进程的堆和方法区的资源,同时线程还有私有的程序计数器、虚拟机栈和本地方法栈资源。

#并行与并发

并行:单位时间内,多个任务同时执行。

并发:同一时间段,多个任务都在执行(单位时间内不一定同时执行)。

#线程的生命周期(状态)

lifecycle-of-thread.png

状态名称 说明
NEW 初始状态,线程被构建
RUNNABLE 运行状态,包括运行中和就绪两种状态
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,如果其他线程不通知则不会唤醒
TIMED_WAITING 超时等待状态,经过指定等待时间后会自动唤醒
TERMINATED 终止状态,表示线程已经执行完毕

#线程的创建方式

#继承Thread类

通过继承Thread类并重写run() 方法可以创建线程,调用start()方法来启动线程。

public class ThreadDemo01 {
    public static void main(String[] args) {
        MyThread01 t = new MyThread01();
        t.start(); // 线程名称:Thread-0
    }
}

/**
 * 继承Thread类
 */
class MyThread01 extends Thread {
    @Override
    public void run() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

由于Java中类的单继承特性,当一个类继承Thread类后就不能继承其它的类了。

#实现Runnable接口

通过实现Runnable接口并重写run() 方法可以创建一个线程,同时可以继承其它的类。

public class ThreadDemo02 {
    public static void main(String[] args) {
        new Thread(new MyThread02()).start(); // 线程名称:Thread-0
    }
}

/**
 * 实现Runnable接口
 */
class MyThread02 extends Object implements Runnable {
    @Override
    public void run() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

采用这种方式创建线程时可以利用JDK1.8的新特性lambda表达式,无需实现Runnable接口的实现类,简化代码。

public class ThreadDemo02 {
    public static void main(String[] args) {
        new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
    }
}
#实现Callable接口

有返回值的任务必须实现Callable接口并重新call() 方法,返回值封装在future中,通过get() 方法获取返回的Object,再结合线程池接口ExecutorService实现有返回值得线程运行。

import java.util.concurrent.*;

public class ThreadDemo03 {
    public static void main(String[] args) {
        // 创建单个线程的线程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        // 提交任务到线程池并获取执行结果
        Future<Integer> future = es.submit(new MyThread03());
        try {
            if (future.get() != null) {
                System.out.println("Callable子线程计算结果:" + future.get());
            } else {
                System.out.println("Callable子线程未获取到结果");
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            es.shutdown();
        }
    }
}

/**
 * 实现Callable接口
 */
class MyThread03 implements Callable<Integer> {
    private int sum;

    @Override
    public Integer call() throws Exception {
        System.out.println("Callable子线程开始计算...");
        Thread.sleep(1000);
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        System.out.println("Callable子线程计算结束...");
        return sum;
    }
}

运行结果:

callable.png

#线程终止的方式

  • 正常运行结束:程序运行结束,线程自动结束;

  • 使用退出标志退出线程:设置一个boolean类型的标志,通过设置标志的值终止线程;

    public class ThreadTerminatedDemo01 extends Thread{
        public volatile boolean exit = false;
    
        @Override
        public void run() {
            while (!exit) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
    
  • interrupt() 方法中断线程

    • 线程处于阻塞状态:调用interrupt()方法时会抛出InterruptedException异常。阻塞中哪个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,使得有机会结束这个线程的执行。
    • 线程处于为阻塞状态:使用isInterrupt() 判断线程的中断标志位退出循环。当使用interrupt() 方法时,中断标志就会设置为true,和使用自定义的标志控制循环是相同的原理。
    public class ThreadTerminatedDemo02 extends Thread{
        @Override
        public void run() {
            // 非阻塞状态下通过判断中断标志来退出
            while (!isInterrupted()) {
                try {
                    // 阻塞状态下捕获中断异常来退出
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 捕获到异常后执行break跳出循环
                    break;
                }
            }
        }
    }
    
  • stop() 方法终止线程(线程不安全)

    程序中直接使用Thread.stop()方法可以强行终止线程,但会有线程不安全问题。当调用Thread.stop() 方法后,线程抛出ThreadDeathError异常,并且会释放其持有的所有锁,从而可能导致数据出现不一致的情况。

#死锁与死锁避免

#死锁

线程之间由于相互竞争资源或调度不当而同时被阻塞,它们中的一个或全部都在等待某个资源被释放。

public class ThreadDeadLockDemo {
    private static Object r1 = new Object();
    private static Object r2 = new Object();

    public static void main(String[] args) {
        // Thread01
        new Thread(() -> {
            synchronized (r1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " has got r1.");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r2.");
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " has got r2.");
                }
            }
        }, "Thread01").start();

        // Thread02
        new Thread(() -> {
            synchronized (r2) {
                System.out.println(Thread.currentThread().getName() + " has got r2.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r1.");
                synchronized (r1) {
                    System.out.println(Thread.currentThread().getName() + " has got r1.");
                }
            }
        }, "Thread02").start();
    }
}

执行结果:

dead-lock.png

#死锁产生的四个必要条件

互斥:该资源任意一个时刻只能由一个线程占用;

请求保持:一个进程因请求资源而阻塞时不会释放已经获得的资源;

不可剥夺:一个线程已经获得的资源不能被其它线程强行剥夺,只有等使用结束才会被释放;

循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。

#死锁避免

破坏产生死锁的四个必要条件中的一个。

破坏互斥条件:无法破坏。

破坏请求保持条件:线程一次性申请所有的资源。

破坏不可剥夺条件:占用部分资源的线程进一步申请资源时,如果申请不到可以主动释放已占有的资源。

破坏循环等待条件:靠按序申请预防。按某一顺序申请资源,释放资源则反序释放,破坏循环等待条件。

public class BreakDeadLockDemo {
    private static Object r1 = new Object();
    private static Object r2 = new Object();

    public static void main(String[] args) {
        // Thread01
        new Thread(() -> {
            synchronized (r1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " has got r1.");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r2.");
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " has got r2.");
                }
            }
        }, "Thread01").start();

        // Thread02
        new Thread(() -> {
            synchronized (r1) {
                System.out.println(Thread.currentThread().getName() + " has got r1.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is waiting for r1.");
                synchronized (r2) {
                    System.out.println(Thread.currentThread().getName() + " has got r2.");
                }
            }
        }, "Thread02").start();
    }
}

对Thread02线程进行修改后,破坏循环等待条件,从而避免死锁。执行结果如下:

dead-lock-solution.png

#比较sleep()和wait()方法

  • 最大区别:sleep()不会释放锁,wait()方法会释放锁;
  • 两者都可以暂停线程的执行;
  • sleep()通常被用于暂停执行,wait()通常用于线程间通信/交互;
  • wait()方法调用后,线程不会自动唤醒,需要其他线程调用notify()/notifyAll()方法。sleep(long timeout)方法或者wait(long timeout)可以指定线程休眠的时间,超时后线程会自动唤醒。

#比较run()和start()方法

创建一个线程时需要重写run()方法,调用start()方法后会启动该线程。调用start()方法会执行线程的相应准备工作然后自动执行run() 方法的内容,这是真正的多线程工作。执行run()方法会把run()方法当成main线程下的普通方法去执行,并不会再某个线程中执行,不是多线程的工作。

调用start()方法可以启动线程并使该线程进入就绪状态,而执行run()方法只是线程中的一个普通方法调用,仍在main线程里执行。