目录
线程定义
要谈及线程,必须要理解进程这个概念。百度百科是这样描述的:
确实是有点抽象,但是当我们打开任务管理器时,每一个在操作系统中运行的exe程序都是一个进程,进程是受操作系统管理的基本运行单元。
那么什么是线程呢?线程可以理解为在进程中独立运行的子任务。比如上面的360.exe运行时就有很多子任务在同时运行。清理垃圾、病毒查杀、电脑瘦身等等,这些不同的任务都可以“同时”运行,其中每一项任务都可以理解为多个“线程”在工作。
为什么要使用多线程进行工作呢?主要有两个原因:
1. 可能提高执行速度。
在做A的同时,还可以执行B。 显然在某些情况下可以提高执行速度。但是线程的创建是有成本的,如果创建线程耗费的时间大于节省的时间,则不会提高速度。
2. 某些 场景下,必须多线程才能处理,例如有阻塞IO时(一个调度单位失效了,剩下的调度单位还可以抢夺CPU)
线程的创建
线程创建就是1. 创建一个线程对象 2. 调用该对象的start()方法,把对象放入就绪队列。主要有两种方式:
1. 直接创建线程对象
public class MyThread extends Thread {
@Override
public void run() {
//线程执行的代码
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();// 把 thread 线程放入就绪队列
}
}
2. 先创建Runnable对象,再创建线程对象。Runnable代表的是要求一个线程执行的任务清单对象。
public static void main1(String[] args) {
// 创建一个目标对象,具体要做的实现,见 run 方法
Runnable target = new MyRunnable();
// 拿着目标对象,去创建线程对象,这个线程要干的活,就是目标对象中指定的
Thread b = new Thread(target);
thread.start();
}
3. 三种变形
private static void useNotHaveNameClass() {
// 等同于直接创建线程对象
Thread a = new Thread() {
@Override
public void run() {
// 这里指定线程要干的活儿
}
};
// 等同于先创建目标对象,再创建线程对象
Thread b = new Thread(new Runnable() {
@Override
public void run() {
// 这里指定线程要干的活儿
}
});
// b 的变形,使用 lambda 表达式
// 完全等于 b 的写法
Thread c = new Thread(() -> {
// 这里指定线程要干的活儿
});
在这里,我们要注意的是,我们执行thread.start(); 之后,只是把创建出来的线程放进了就绪队列中,并不意味着该线程会马上抢到CPU,更不意味着要执行该线程的代码。当:1. CPU现在的线程被调度下来了 2. t被从就绪队列中选中了 3. t被调度上CPU上了。 只有这样,该线程的代码才被执行。
因为Thread这个类也实现了Runnable接口。所有我们可以吧一个Thread对象看成一个Runnable对象。
Thread t = new MyThread();
Runnable target = t;
Thread thread = new Thread(target);
线程的操作
1. 构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target)
|
使用 Runnable
对象创建线程对象
|
Thread(String name)
| 创建线程对象,并命名 |
Thread(Runnable target, String name)
| 使用 Runnable 对象创建线程对象,并命名 |
2. 常见属性
3. 启动一个线程
启动一个线程 :start() 方法。把线程对象放入就绪队列中,使得拥有被调度的资格。并不意味着会立刻执行该线程所要执行的任务。
那么 start() 方法和 run() 有什么区别呢?
覆写 run() 方法的目的,只是指导线程应该进行的工作,没有其他作用。
start() 方法不需要覆写,而调用 start() 方法,则会把线程放到就绪队列中,是线程用于被调度的机会,即就是抢CPU的机会。
4. A线程阻塞式等待B线程停止
在 A 线程执行的代码中,调用 b.join(); 会造成两个后果:
1. A 线程主动放弃 CPU
2. 直到 B 结束之前,再也不会抢 CPU。A 线程会阻塞在那里,直到 B 线程执行结束,才接着往下执行
5. A线程通知B线程停止(中断线程)
A 线程通知 B 线程停止。主要有两种方法。方法1可以设置共享的标记进行沟通,比如设置一个boolean类型的进行标记,状态改变,线程停止。不过这种方式存在一定的缺陷,当子线程中有 sleep() 等操作时,无法实时进行响应。主要看第二种方法,我们可以使用线程原生提供的方法,Thread中的一些方法。
通过 thread 对象调用 interrupt() 方法通知该线程停止运行 ,thread 收到通知的方式有两种:
1. 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
6. 获取当前线程引用
public static Thread currentThread(); //返回当前线程对象的引用
用法如下:
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
7. 休眠当前线程
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
线程的状态
线程的状态用来描述当前线程处于一个什么地步,进行JVM可以根据当前该线程的转态决定下一步该做什么。
我们可以先查看线程都有什么状态:
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
输出的结果:NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED
1. NEW:是线程刚刚被创建的状态。
2. RUNNABLE:
Ready:该线程拥有抢CPU资格
Running:该线程已经在CPU上了
从Running到Ready可能会有三种情况造成:
1. 被高优先级抢走了
2. 主动放弃 -yield()
3. 时间片耗尽
3. TERMINATED:线程的有效代码已经执行结束,但线程对象还在。
4. BLOCKED WAITING TIMED_WAITING:不再拥有抢CPU的资格。
要注意:除了NEW和TERMINATED,线程都是存活的,即isAlive()都返回true。
用一副线程的状态转移图可以形象描述线程的状态转移:
okay。