JUC学习笔记 - 01多线程基础

225 阅读2分钟

写在前面: 本系列为多线程与高并发(即JUC)的学习笔记。中间涉及到的其他问题,例如:JVM、操作系统等问题,都只会简单描述后便不再详解。希望仅此系列文章能提高以后复习的效率。本文的主要内容为线程的基础概念,包括线程的理解、创建方式、几种状态等。

什么是线程

直观的理解就是一个程序中不同的执行路径在同时运行。当然如果运行在同一个单核CPU上,我们所看到的同时运行其实是交替运行。

创建方式

  1. 继承Thread类,实现run方法,调用start方法开启线程
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
  1. 继承Runnable接口,实现run方法
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }

    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }
  1. 直接用Threadlamda表达式
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("Hello Lambda!");
        }).start();
    }
  1. 使用线程池
    static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello Callable";
        }
    }

    public static void main(String[] args) {
        Executors.newCachedThreadPool().submit(new MyRunnable());
        Executors.newCachedThreadPool().submit(() -> {
            System.out.println("Hello Lambda!");
        });
        Executors.newCachedThreadPool().submit(new MyCallable());
        Executors.newCachedThreadPool().submit(() -> "Hello Callable");
    }
  1. FutureTask
    FutureTask<Integer> task = new FutureTask<>(()->{
            return 1000;
        });
    new Thread(task).start();

FutureTask实现了Runable接口,所以此方法可归并为上述方法2

线程常见的方法

  1. sleep

sleep表示让当前线程暂停一段时间,将CPU让给别的线程。睡眠时间内CPU运行其他线程。

    static void testSleep() {
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
  1. yield

yield将线程退出一下,重新回到等待队列。

    static void testYield() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                if (i % 10 == 0) {
                    Thread.yield();
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                if (i % 10 == 0) {
                    Thread.yield();
                }
            }
        }).start();
    }

从上述程序输出结果中发现,两个线程并没有交替输出内容,所以yield只是将线程让出CPU一下下,至于后续谁抢到CPU资源无法控制。

  1. join

join表示等待另一个线程结束后再运行当前线程。

    static void testJoin() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();
    }

上述t2线程会等待t1运行结束后再继续运行。

线程的状态

  • NEW:线程刚刚创建,还没有启动
  • RUNNABLE:可运行状态,由线程调度器可以安排执行。包括READY和RUNNING两种细分状态
  • WAITING:等待被唤醒
  • TIMED WAITING:隔一段时间后自动唤醒
  • BLOCKED:被阻塞,正在等待锁
  • TERMINATED:线程结束

多线程状态.png

interrupt

interrupt译为线程的“打断”,但是实际上并不是真正意义上的打断线程,只是对于线程标志位的操作。

interrupt相关的三个方法:

  1. interrupt():实例方法t.interrupt(),设置线程中断标志为true,即打扰一下线程处理一下中断
  2. isInterrupted():实例方法t.isInterrupted(),查询标志位是否被设置
  3. interrupted():静态方法Thread.interrupted(),查看当前线程标志位是否为true,为ture则恢复标志位为false

举两个例子验证上面的描述:

public class TestInterrupt {
    static void interruptAndIsInterrupted() {
        Thread t = new Thread(() -> {
            for (; ; ) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Thread is interrupted!");
                    System.out.println(Thread.currentThread().isInterrupted());
                    break;
                }
            }
        });
        t.start();
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }

    static void interruptAndInterrupted() {
        Thread t = new Thread(() -> {
            for (; ; ) {
                if (Thread.interrupted()) {
                    System.out.println("Thread is interrupted!");
                    System.out.println(Thread.interrupted());
                    break;
                }
            }
        });
        t.start();
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

interruptsleep()wait()

sleep()不到时间线程不会继续运行,wait()不被唤醒线程也不会继续运行,这时候使用interrupt也并不会让线程苏醒。但是可以catch InterruptedException异常来处理后续的逻辑,且此时会中断标志会自动复位。举个例子:

public class TestInterruptWithSleepOrWait {
    
    static void TestInterruptAndSleep() {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println("Thread is interrupted!");
                System.out.println(Thread.currentThread().isInterrupted());
            }
        });
        t.start();
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
    
    static void TestInterruptAndWait() {
        final Object o = new Object();
        Thread t = new Thread(() -> {
            synchronized (o) {
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    System.out.println("Thread is interrupted!");
                    System.out.println(Thread.currentThread().isInterrupted());
                }
            }
        });
        t.start();
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

interrupt无法中断正在竞争锁的线程

    public static void main(String[] args) {
        final Object o = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (o) {
                SleepHelper.sleepSeconds(10);
            }
        });
        t1.start();
        
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t2 = new Thread(() -> {
            synchronized (o) {
            }
            System.out.println("t2 end!");
        });
        t2.start();
        
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        t2.interrupt();
    }

可以发现t2.interrupt()后并没有阻止t2获取到锁。所以interrupt()不能打断正在竞争锁的线程。

如果想打断正在竞争锁的线程,可以使用ReentrantLocklockInterruptibly()。可以参考后面的文章:juejin.cn/post/706744…