多线程的概念及常用的方法

192 阅读4分钟

线程: 是操作系统能够进行运算调度的最小单位,一个线程指的是进程中一个单一顺序的控制流,每个线程执行不同的任务,一个进程至少包括一个线程

使用多线程的目的

充分利用 CPU 资源,并发做多件事

创建线程的方式

  1. 自定义类继承 Thread 类重写 run() 方法
  2. 实现 Runnable 接口,重写 run() 方法,new Thread 时把该类对象当做参数传入
  3. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值,通常和

  Future 或 FutureTask 联合使用。
  Future 是存储 Callable 执行后的结果,使用 Future 获得异步执行结果时,要么调用阻塞方法 get(),要么轮询看 isDone() 是否为 true ,这两种方法都不是很好,因为主线程也会被迫等待。
  从 Java 8 开始引入了 CompletableFuture,它针对 Future 做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
  FutureTask 实现了 Runnable 和 Future ,是一个可以取消的异步运算,它有启动和取消运算,查询运算是否完成和取回运算结果等方法,只有当运算完成的时候结果才能返回,如果运算尚未完成,get 方法会被阻塞,由于 FutureTask 也是调用了Runnable 接口所以它可以提交给 Executor 执行。

和 Future 联合使用

public static void main(String[] args) throws Exception {
        Callable<String> c = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "123";
            }
        };

        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> submit = executorService.submit(c);

        // 等 call 方法执行完才会执行下面的代码,和 Thread 不一样
        System.out.println("456");
        // get 方法会阻塞到 Callable 执行完有返回值之后
        System.out.println(submit.get());

        executorService.shutdown();
    }

和 FutureTask 联合使用

    public static void main(String[] args) throws Exception {
        Callable<String> c = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "123";
            }
        };
        FutureTask<String> futureTask = new FutureTask<>(c);

        new Thread(futureTask).start();

        // 等 call 方法执行完才会执行下面的代码,和 Thread 不一样
        System.out.println("456");
        // get 方法会阻塞到 Callable 执行完有返回值之后
        System.out.println(futureTask.get());
    }

CompletableFuture

CompletableFuture 使用

例子

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建异步执行任务:
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);
        // 如果执行成功:
        cf.thenAccept((result) -> {
            System.out.println("price: " + result);
        });
        // 如果执行异常:
        cf.exceptionally((e) -> {
            e.printStackTrace();
            return null;
        });
        System.out.println("会先执行我");
        // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(2000);
    }

    static Double fetchPrice() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        if (Math.random() < 0.3) {
            throw new RuntimeException("fetch price failed!");
        }
        return 5 + Math.random() * 20;
    }
}

CompletableFuture 的优点

  • 异步任务结束时,会自动回调某个对象的方法;
  • 异步任务出错时,会自动回调某个对象的方法;
  • 主线程设置好回调后,不再关心异步任务的执行;
  • 多个 CompletableFuture 可以串行或并行;
  • 可以实现任意一个 CompletableFuture 或全部 CompletableFuture 执行完毕的后续处理

线程的生命周期

一个线程实例化完成,到这个线程被销毁。

线程的状态

  1. 新生态:New
    一个线程对象被实例化完成,但是还没有做任何操作
  2. 就绪状态:Ready
    一个线程已经被开启,已经开始争抢CPU时间片
  3. 运行状态:Run
    抢到了CPU时间片,开始执行这个线程中的逻辑
  4. 阻塞状态:Interrupt
    一个线程在运行的过程中,受到某些操作的影响,放弃了已经获取到的CPU时间片,并且不再参与CPU时间片的争抢,此时线程处于挂起状态
  5. 死亡状态:Dead
    一个线程对象需要被销毁

  等待锁标记的意思就是有一个线程抢到了 cpu 时间片进入运行状态,获取到锁的拥有权,别的线程就进入到锁池状态,等到在运行状态的线程运行结束之后,锁池里面的线程争抢锁标记,谁抢到锁标记谁进入就绪状态。

方法

wait()、await()、notify()、notifyAll()、await()、signal()、signalAll() 必须在获取到锁才能调用,不然会抛出 IllegalMonitorStateException

sleep():使线程由运行状态进入阻塞状态,休眠,不释放锁资源

Thread.currentThread().getName():获取当前运行线程名称

Thread.currentThread().getPriority():获取当前线程的优先级

setPriority():设置优先级
    
yiled():线程礼让,指的是让当前的运行状态的线程释放自己的CPU资源,由运行状态回到就绪状态,不会释放锁
    
wait(): 等待唤醒,是Object类中的一个方法,当前线程释放自己的锁标记,并且让出CPU资源,使当前的线程进入等待队列中,只有拿到该资源的线程才能使用此方法,否则抛出	       															java.lang.IllegalMonitorStateException
notify(): 通知唤醒等待队列中的在该对象上调用wait方法进入等待队列的线程,进入锁池,是Object类中的一个方法
notifyAll(): 唤醒所有在该对象上调用wait方法进入等待队列的线程,并使他们进入锁池,只有获得该对象的锁才能使用在该对象上使用该方法,不会释放锁
setDaemon(true): 把线程设置为守护线程  线程分为守护线程和用户线程,当进程中没有用户进程的时候,JVM会退出
join(): 加入线程,让调用的线程先执行指定时间或执行完毕,再执行主线程,经常用在等待另外一个线程执行结束,在 t1 里调用 t2.join,等待 t2 执行完再执行 t1
interrupt(): 终端调用该方法的线程,如果当前线程由于调用wait(),join()或者sleep()方法被阻塞,则退出阻塞且终端状态,并且抛出InterrruptedException异常
    
Condition类中的方法
await(): 和wait()一样,用于lock锁  
signal(): 和notify一样   
这些方法需在有Condition对象的基础上使用
Condition condition = lock.newCondition();
//进入等待队列,也会释放锁,等待唤醒
condition.await();
//唤醒
condition.signal()