线程(一)线程基础

1. 线程创建、启动、运行

1.1 继承线程重写 run() 方法

java 复制代码
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("1. 继承 Thread 重写 run 方法,Thread implements Runnable");
        }
    }

    public static void main(String[] args) {
        new MyThread().start();
    }

1.2 新建线程传入 Runnable

java 复制代码
public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("2. 新建 Thread 传入 Runnable");
        }
    }).start();
}

2.线程状态

  • id:存储线程的唯一标识符,自增值,不能指定

  • name:存储线程的名字,不同线程名字可以相同,可以默认生成,也可以指定

    • 默认值:Thread_ + 自增值 ,该自增值与 id 自增值不同
  • priority:线程对象的优先级,默认一般为 5,范围为 1~10,属性值与父属性值相同,优先级并不会保证按照其优先级高低的顺序运行

  • daemon:是不是守护线程,默认为 false,守护线程会在主线程退出前结束,而非守护线程即便主线程退出了也仍然会执行,默认情况下父线程是守护线程,子线程也是守护线程。

  • status:线程状态

    • 该属性保存了线程的状态。在Java中,线程有6种状态------Thread.State枚举中定义这些状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED。这些状态的具体意义如下。

      • NEW:线程已经创建完毕但未开始执行
      • RUNNABLE:线程正在执行
      • BLOCKED:线程处于阻塞状态,发起了一个阻塞 I/O 或者申请了一个由其他线程占有的资源(例如锁),处于该状态的线程并不会占用 CPU 资源
      • WAITING:线程在等待另一个线程到达某种状态,例如 Object.wait()、Thread.join()、LockSupport.park(Object)。对应的能够使线程从 WAITING 状态变为 RUNNABLE 的方法包括:Object.notify()/notifyAll()和 LockSupport.unpark(Object)
      • TIMED_WAITING:该状态与 WAITING 类似,但是差别在于 TIMED_WAITING 会等待一定的时间,如果一定时间内没有被唤醒自动变为 RUNNABLE,sleep() 也会使线程进入这个状态。
      • TERMINATED:线程执行完毕。
    • (引用自:Java多线程编程实战指南(核心篇))

3.线程方法

方法 功能
static Thread currentThread() 返回当前线程
void run() 线程的执行逻辑
void start() 启动线程,只有该方法才会真正创建操作系统级别的线程。只能被调用一次,多次调用抛异常。
void join() 等待相应线程结束,例如线程 A 调用线程 B 的 join 方法,那么线程 A 会被暂停直到 B 执行结束。
static void yield() 使当前线程放弃一次对处理器的占用
static void sleep(long millis) 使当前线程停止运行指定时间

4.线程中断

有时我们需要终止某一个线程所执行的任务,需要用到线程中断

java 复制代码
        Thread thread = new Thread(() -> {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                System.out.println(String.format("thread is interrupted count = %s, isInterrupted = %s", i, Thread.currentThread().isInterrupted()));
            }
        });

        thread.start();

        System.out.println("thread status : " + thread.getState());
        System.out.println("thread is interrupt : " + thread.isInterrupted());
        System.out.println("thread is live : " + thread.isAlive());

        Thread.sleep(20);
        System.out.println("thread interrupt ------------------------------------");
        thread.interrupt();

        System.out.println("thread status : " + thread.getState());
        System.out.println("thread is interrupt : " + thread.isInterrupted());
        System.out.println("thread is live : " + thread.isAlive());

thread status : RUNNABLE

thread is interrupt : false

thread is live : true

thread is interrupted count = 0, isInterrupted = false

thread is interrupted count = 1, isInterrupted = false

thread is interrupted count = 2, isInterrupted = false

...

thread is interrupted count = 157, isInterrupted = false

thread interrupt ------------------------------------

thread is interrupted count = 158, isInterrupted = false

thread status : RUNNABLE

thread is interrupt : true

thread is live : true

thread is interrupted count = 159, isInterrupted = true

thread is interrupted count = 160, isInterrupted = true

thread is interrupted count = 161, isInterrupted = true

thread is interrupted count = 162, isInterrupted = true

...

上述例子的结果看即便线程被中断,并且中断状态为 true ,但是线程仍然存活。并且正常执行,这并不是预期中的行为。

所以通过 interrupt() 方法并不能实现真正的中断。

  • 如果被中断线程在调用 wait()/join()/sleep() 方法被阻塞时调用中断将会抛出 InterruptedException 异常,中断状态被清除
  • 如果被中断线程在对 InterruptibleChannel 进行 I/O 操作被阻塞时,那么通道将会被关闭,同时设置中断状态抛出 ClosedByInterruptException 异常
  • 如果该线程被阻塞在 java.nio.channels.Selector 中,那么它将会被立即返回,同时设置中断状态

中断未存活的线程不产生任何影响。

如果想要真正的停止线程可以调用已经被废弃的方法 stop()

5.线程本地变量-ThreadLocal

在某些场景下,我们希望线程自己维护一套数据自己的环境变量

java 复制代码
        ThreadLocal<String> threadLocal = new ThreadLocal<>();

        for (int i = 0; i < 100; i++) {
            final int threadIndex = i;
            new Thread(() -> {
               threadLocal.set(Thread.currentThread().getName());

                try {
                    Thread.sleep(threadIndex * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(threadIndex + ":" + threadLocal.get());

            }, "thread-" + i).start();
        }

0:thread-0

1:thread-1

2:thread-2

3:thread-3

4:thread-4

...

96:thread-96

97:thread-97

98:thread-98

99:thread-99

可以看到每个线程维护的变量不冲突。

ThreadLocal 的原理是每个线程自己维护一个 Map ,Map 的 key 为 ThreadLocal ,val 为线程在 ThreadLocal 中存储的值。这样每个线程单独维护的 Map 就不会有并发冲突。

ThreadLocal 提供了 remove() 方法,用于删除 ThreadLocal 中当前线程的本地变量,如果本地变量无用时记得清除避免内存泄漏。

Java并发API中提供的InheritableThreadLocal类能够实现从线程的创建线程上继承本地变量的功能。如果线程A有一个线程本地变量,它又创建了线程B,那么线程B也拥有了与A相同的线程本地变量。

应用:

实际开发中,有很多需要用到用户信息的逻辑,例如某些场景需要校验用户是否登陆/用户权限等,但是传入 userId 对业务逻辑侵入性很大,此时就可用 ThreadLocal 在 web 容器入口处写入 user 信息,这样业务逻辑就不用一直传递 userId。在 web 容器执行完一次请求后清除 ThreadLocal 的数据避免造成影响。

6.线程工厂

工厂模式是一种用来创造实例的设计模式,使用工厂模式来创造线程具有以下优点。

  • 可以对同一个类型的线程创建做限制,例如最多可以创建多少个实例
  • 可以给同一个类型的线程创建做统计或者打标记,例如记录线程创建频次数量为线程名字添加固定前缀+自增值便于排查问题
java 复制代码
    class MyThreadFactory implements ThreadFactory {
        int count = 0;
        List<String> status = new ArrayList<String>();

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread("thread-" + count++);
            status.add(thread.getName());
            return thread;
        }
    }
相关推荐
Asthenia04127 分钟前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom8 分钟前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04122 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04123 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04123 小时前
Spring 启动流程:比喻表达
后端