进程:一个可执行程序;进程是操作系统资源分配的基本单位。 线程:一个线程就是一个执行单元;线程是操作系统调度执行的基本单位。
1.并发编程成为必需,线程也是为了实现并发编程效果,能够解决多进程涉及到的问题。 2.线程也叫做轻量级进程,创建销毁线程的开销比创建销毁进程快很多。轻量化体现在创建线程不需要申请那么多资源(进程中第一个线程创建时,所有资源都需要进行申请)。
联系:进程包含线程,每个进程至少包含一个线程,即主线程。 区别:
编写一个类继承Thread方法,重写run方法。 创建线程对象,调用线程对象的start方法。
//创建一个类继承标准库的Thread类
class MyThread extends Thread{
//重写父类的run方法
@Override
public void run() { //run就是线程的入口
}
}
public class Demo1 {
public static void main(String[] args) {
//1. 创建Thread 的实例
MyThread myThread = new MyThread();
// 2.启动线程 不是run();
myThread.start();
}
}其中:
编写一个类实现Runnable接口,实现run方法
class MyRunnable implements Runnable{
@Override
public void run() {
}
}
public class Demo2 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//创建线程对象,将Runnable对象作为参数传入
Thread thread = new Thread(myRunnable);
//运行线程
thread.start();
}
}eg:
public class Demo3 {
public static void main(String[] args) {
Thread t = new MyThread(){
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
//调用start 方法才是真正创建线程
t.start();
}public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
}
});
}
}public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
Thread t = new Thread(runnable);
}eg:
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}方法 | 说明 |
|---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
属性 | 获取方法 |
|---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
线程没有创建名字时其默认名称为:thread-0,thread-1…
后台线程:后台线程不会阻止整个进程结束。 前台线程:前台线程会阻止整个进程结束。
JVM会在一个进程的所有非后台线程结束之后,才会结束运行。 main中包括手动创建的线程都默认为前台线程。
通过覆写run方法创建一个线程对象,但是创建出线程对象不意味着线程开始运行了,只有start方法被调用时,才会真的在操作系统底层创建出一个线程出来。
对于Java来说,一个线程终止,就是这个线程的入口方法执行完毕。Java并不提供“强制终止”,因此所有让线程终止的做法都需要围绕着“让入口方法结束”的做法。
eg:
public class Demo8 {
private static boolean running = true;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (running){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程退出");
});
t.start();
//主线程中,让用户进行输入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入整数 0 表示让t线程终止");
int n = scanner.nextInt();
if(n == 0){
running = false;
}
}
}通过修改running的状态来使 t 线程结束 但是当sleep中的时间很长时,即使修改了标志位,也需要等待度过了sleep中的时间才会终止,这显然是不合适的。
public class Demo9 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(100_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Scanner scanner = new Scanner(System.in);
System.out.println("输入整数 0 让线程 t 结束");
int n = scanner.nextInt();
if(n == 0){
//不仅可以设置标志位,还可以处理sleep等导致线程阻塞的方法。
t.interrupt();
}
}
}线程之间是随机调度的,无法确定哪个线程先执行,而有时需要等待某个线程结束再进行下一个线程,此时需要干预两个线程的结束顺序。 eg: 首先让 t 线程计算,主线程等待 t 线程结束后打印其结果。
public class Demo10 {
private static int result = 0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程计算1+ 2+ 3+ ...+ 1000;
//主线程在这个计算线程完毕后,打印此处结果
Thread t = new Thread(() -> {
for(int i = 0; i <= 1000; i++){
result += i;
}
System.out.println("t 线程结束");
});
t.start();
//Thread.sleep(1000);
System.out.println(result);
}
}发现打印结果为0,是因为不确定先执行打印还是先执行循环,也可能循环执行一半再执行打印。不代表执行"概率均等",先执行打印的概率更大,start方法需要调用系统api,系统创建线程,新线程参与调度,都需要花费时间。但是当加上sleep给t一点执行时间时,t线程一定是先执行完的,因为这段计算逻辑执行速度极快,即使sleep(1)也是t线程先执行完毕,但当逻辑很复杂,如何评估计算时间呢?
public class Demo10 {
private static int result = 0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程计算1+ 2+ 3+ ...+ 1000;
//主线程在这个计算线程完毕后,打印此处结果
Thread t = new Thread(() -> {
for(int i = 0; i <= 1000; i++){
result += i;
}
System.out.println("t 线程结束" + result);
});
t.start();
//join等待 t 线程结束
t.join();
System.out.println(result);
}
}此时在main线程中使用t.join()就是让main线程等待 t 线程执行完毕在执行。 join 的阻塞等待时间是不确定的,取决于 t 线程何时退出。 任何两个线程都是可以进行等待的,t 线程也是可以等待main线程的。
方法 | 说明 |
|---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度,精确到纳秒 |
方法 | 说明 |
|---|---|
public static Thread currentThread() | 返回当前线程对象的引用 |
eg:
public class Demo12 {
public static void main(String[] args) {
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}方法 | 说明 |
|---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
特殊情况:sleep(0) - 线程主动放弃 cpu 注:某个线程调度到cpu上,可能会一口气执行很多逻辑,正常情况下,每个线程都会有一定的执行时间(时间片)。可能线程1执行一段时间,线程2再执行一段时间… 而sleep(0) 使线程从就绪状态调度到阻塞状态再立刻调度到就绪状态,虽然时间可能极短,但起到的效果可以降低CPU的使用率。