线程(一)线程基础

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;
        }
    }
相关推荐
Viktor_Ye12 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm14 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手19 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven
J老熊24 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java29 分钟前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding31 分钟前
时间请求参数、响应
java·后端·spring
好奇的菜鸟43 分钟前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
所待.3831 小时前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood1 小时前
Java线程池详解
java·线程池·多线程·性能
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang