进程与线程
**进程:**每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)
**线程:**同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
**线程和进程一样分为五个阶段:**创建、就绪、运行、阻塞、终止
**Java线程有六个状态:**NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、TIMED_WAITING(指定时间等待)、TERMINATED(终止)
Java线程是不区分 Ready(就绪)和 Running(运行)的,它们都是 Runnable 状态。
多进程是指操作系统能同时运行多个任务(程序),同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口。(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用)
一、创建与调用线程
1.创建类继承 Thread 类,重写 run 方法
public class MyThread extends Thread {/*** run 方法就是线性要执行的任务方法*/@Overridepublic void run() {// 这个的代码,就是一条新的执行路径// 这个执行路径的触发方式,不是调用 run 方法,而是通过 thread 对象的 start() 来启动任务for (int i = 0; i < 10; i++) {System.out.println("分支任务执行"+i);}}
}public class Test {/*** 多线程技术*/public static void main(String[] args) {MyThread m = new MyThread();m.start();for (int i = 0; i < 10; i++) {System.out.println("主任务执行"+i);}}
}
运行结果每次都会不一样,这是两个线程相互抢占,使用抢占式分配的结果,故后续相同功能的代码运行结果省略:
主任务执行0
主任务执行1
分支任务执行0
主任务执行2
分支任务执行1
主任务执行3
分支任务执行2
分支任务执行3
主任务执行4
主任务执行5
主任务执行6
分支任务执行4
主任务执行7
分支任务执行5
主任务执行8
分支任务执行6
主任务执行9
分支任务执行7
分支任务执行8
分支任务执行9
2.使用匿名内部类创建线程
public static void main(String[] args) {new Thread(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("分支任务执行"+i);}}}.start();for (int i = 0; i < 10; i++) {System.out.println("主任务执行"+i);}
}
3.创建类继承 Runnable 接口,重写 run 方法
public class MyRunnable implements Runnable {@Overridepublic void run() {// 线程的任务for (int i = 0; i < 10; i++) {System.out.println("分支任务执行"+i);}}
}public class Test {/*** 多线程技术*/public static void main(String[] args) {// 实现 Runnable// 1. 创建一个任务对象MyRunnable r = new MyRunnable();// 2. 创建一个线程,并为其分配一个任务Thread t = new Thread(r);// 3. 执行这个线程t.start();for (int i = 0; i < 10; i++) {System.out.println("主任务执行"+i);}}
}
4.使用匿名内部类实现 Runnable
public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("分支任务执行"+i);}}});t.start();for (int i = 0; i < 10; i++) {System.out.println("主任务执行"+i);}
}
5.使用 Lambda 表达式,这是一个面向函数的编写方式,其中:
注:括号代表重写的方法,方法名可省略,括号内部可传参数类型,大括号中是重写的方法内容,大括号外可传参数的值
public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println("分支任务执行"+i);}});t.start();for (int i = 0; i < 10; i++) {System.out.println("主任务执行"+i);}
}
另外:实现 Runnable 与继承 Thread 相比有如下优势
- 通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况
- 可以避免单继承所带来的局限性,实现 Runnable 可以继承其他的类增强实用性
- 任务与线程本身是分离的分离的,提高了程序的健壮性
- 线程池技术只接受 Runnable 类型的任务,而不接受 Thread 类型的线程
二、操作线程的常用方法
1.设置和获取线程名称
public static void main(String[] args) {System.out.println(Thread.currentThread().getName());new Thread(new MainRunnable(),"分支任务").start();
}static class MainRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
运行结果如下,若不传进程名称只传目标类,则会生成默认名称:Thread-0,后续创建的进程名称依次加一: Thread-1、Thread-2……
main
分支任务
2.线程休眠 sleep,在任何线程中均能使用,能使当前正在执行的线程休眠(暂时停止执行)指定的时间,常用传入参数为毫秒
public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {System.out.println(i);Thread.sleep(1000);}
}
3.线程的中断 interrupt,需要注意的是,这个方法只是给进程添加了一个标记,这个标记能被 InterruptedException 捕捉进入 catch 部分的代码,可在此处进行结束进程、释放资源等操作
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {// 1. e.printStackTrace();// 2. System.out.println("发现了中断标记");// 3.System.out.println("发现了中断标记,令进程死亡");return;}}});t.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+":"+i);Thread.sleep(1000);}t.interrupt();
}