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;
}
}