一、进程与线程
进程:是资源分配的最小单位,操作系统中一个程序开始执行就产生一个进程,是动态概念。
线程:是程序执行的最小单位,它是进程的一个执行流。一个程序执行多个任务,每个任务就是一个线程。
1、进程和线程的区别:
- 线程更加“轻量级”,创建和关闭的开销小。
- 没有进程就没有线程。
- 每个进程拥有独立的内存空间,而线程之间共享数据,因此线程间通信比进程之间更加方便。
2、线程的状态:
5种状态:创建、就绪、阻塞、运行、结束。
二、java多线程的实现
线程的创建方式,有四种方式:继承Thread类,实现Runnable接口,实现Callable接口,还有一种方式就是线程池。
1、继承 Thread 类
- 线程类只能单继承,不能再继承其它类(受限于Java类的继承)
- 业务实现在 run() 方法中,业务和线程实现耦合
- 业务是不能共享的
例子:继承 Thread 类实现多线程:
class MyThread extends Thread {
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.name + " " + i);
}
}
}
public class ThreadTest{
public static void main(String[] args) {
MyThread myThread1 = new MyThread("Thread_1");
MyThread myThread2 = new MyThread("Thread_2");
myThread1.start();
myThread2.start();
}
}
注意:启动线程不能直接去调用 run() 方法,否则只是简单的方法调用,和多线程无关,应该使用 start() 方法启动多线程。总之,不管何种情况,多线程的启动只有使用 Thread 类中的 start() 方法。
2、实现 Runnable 接口
- 解决单继承问题
- 业务实现和线程实现解耦
- 业务可以共享
- 使用 Lambda 表达式和匿名内部类的方式创建 Runnable 对象(开发常用)
- run() 方法的返回值是 void , 业务如果有返回值无法处理
例子:通过 Runnable 接口实现多线程
class MyThread implements Runnable {
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.name + " " + i);
}
}
}
public class ThreadTest{
public static void main(String[] args) {
MyThread myThread1 = new MyThread("Thread_1");
MyThread myThread2 = new MyThread("Thread_2");
//Runnable接口中只有一个run()方法,没有start()方法
//要启动多线程,只能通过Thread类的构造方法实现
new Thread(myThread1).start();
new Thread(myThread2).start();
}
}
3、实现 Callable 接口
- 可以处理返回结果
- 使用 Callable 的业务对象,通过 FutureTask 来获取执行结果
例子:通过 Callable 接口实现多线程
class MyThread implements Callable<String> {
private int ticket = 10;
@Override
public String call() throws Exception {
while (this.ticket > 0) {
this.ticket--;
System.out.println("还剩" + " " + this.ticket + " " + "张票");
}
return "票卖完了!";
}
}
public class ThreadTest {
public static void main(String[] args) {
FutureTask<String> task = new FutureTask<>(new MyThread());
new Thread(task).start();
new Thread(task).start();
try {
System.out.println(task.get());//打印最后的结果
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
4、线程池:(后面补充)
思考:Thread 和 Runnable 的区别:
- Runnable 可以多继承,没有继承局限性。
- Thread 类是 Runnable 接口的子类
- 多线程的处理使用的是代理设计模式
- 使用 Runnable 实现的多线程程序类可以更好地描述出程序共享的概念
三、多线程的常用操作方法
1、线程命名与获取
要设置和取得线程的名字,就必须取得当前线程的对象,可以使用Thread类中的方法:
public static native Thread currentThread();
构造方法:public Thread(Runnable target,String name)
设置线程的名字:public final synchronized void setName(String name)
取得线程名字:public final String getName()
主方法本身就是一个线程,所有的线程都是通过主方法创建并启动的。
2、线程休眠(sleep)
Thread.sleep()
-
线程暂停执行,进入阻塞状态
-
交出CPU
-
不释放占用的资源锁
-
休眠时间到了回到就绪状态
3、线程让步(yield)
Thread.yield()
-
线程暂停执行,进入就绪状态
-
交出CPU,但不确定具体时间
-
不释放占用的资源锁
-
相同优先级的线程拥有获取CPU时间片的机会
4、join()方法
当在线程A中调用线程B的join方法,线程A暂停执行,直到线程B执行完毕,线程A继续执行。
5、线程停止(三种方法)
线程停止方式:
(1)标志位 (boolean 类型的表示)
(2)Stop (弃用,不安全,会立即解除所有锁定,立即结束线程。)
(3)interrupt() 只是改变中断状态,而不会中断正在运行的线程,给受阻塞的线程发出一个中断信号,使受阻线程能够退出阻塞状态。
- 改变线程的中断标识为true
- 如果非阻塞 设置中断标志为true
- 如果阻塞,若由wait、 sleep、 join引起,抛出异常InterruptedExeception ,清理中断标识重新设置为 false
- interrupt() 本质:通过触发中断,让开发者决定如何处理线程业务
6、线程的优先级:
线程优先级越高,可能优先执行,仅仅是可能性!!!
(1)优先级从 1-10 最低为1,中等为 5,最高为 10
(2) main默认优先级是5
(3) 线程优先级具有继承性,如果线程A中创建线程B,那么线程B的默认优先级继承线程A的优先级
设置优先级:
public final void setPriority(int newPriority);
/*优先级常量:
MAX_PRIORITY = 10;
NORM_PRIORITY = 5;
MIN_PRIORITY = 1; */
取得优先级:
public final int getPriority();
7、守护线程
线程可分为:用户线程和守护线程
守护线程:主要用来“陪伴”用户线程,当一个JVM进程中最后一个用户线程退出,那么守护线程将伴随JVM一起结束。
守护线程举例:GC垃圾回收线程