1、什么是线程
线程(Thread)是一条程序内部的一条执行流程。
程序中如果只有一条执行流程,那这个程序就是单线程的程序。
2、什么是多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理“。
3、多线程的优缺点
3.1、优点
- 同时执行多个任务,提高程序的工作效率。
- 提高CPU的使用率。
- 线程之间可以共享资源。
- 相比进程而言,创建线程代价比较小。
3.2、缺点
- 等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
- 对线程进行管理要求额外的 CPU开销,线程的使用会给系统带来上下文切换的额外负担
- 可能会出现线程的死锁。即对共享资源加锁实现同步的过程中可能会死锁。
4、java中的多线程
在java语言中: 线程A和线程B,堆内存和方法区内存共享。 但是栈内存独立,一个线程一个栈。假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
5.、java多线程的声明周期
- 创建(New):线程对象通过 new 关键字创建,但还未调用 start() 方法时,线程处于新建状态。此时,线程对象已经分配了内存空间,但尚未启动执行。
- 就绪(Runnable):线程对象调用 start() 方法后,线程处于就绪状态。此时,线程已经准备好执行,但还没有获得 CPU 时间片。多个线程处于就绪状态时,由 Java 虚拟机的线程调度器来决定哪个线程获得 CPU 时间片开始执行。
- 运行(Running):当线程获得 CPU 时间片开始执行时,线程处于运行状态。此时,线程的 run() 方法正在被执行。
- 阻塞(Blocked):在特定情况下,线程可能会被暂时挂起,进入阻塞状态。例如,线程调用了 sleep() 方法、等待 I/O 操作、获得了某个对象的锁但没有获取到锁等。当阻塞状态的条件解除时,线程会重新进入就绪状态,等待获取 CPU 时间片继续执行。
- 销毁(Terminated):线程执行完 run() 方法后,或者调用了 stop() 方法,线程将进入销毁状态。一旦线程进入了销毁状态,就无法再恢复到其他状态。
6、Java中多线程的实现方式
6.1、继承Thread类
- 定义一个类继承Thread类,重写run方法
- 创建调用线程类的对象
- 调用线程对象的start方法启动线程(启动后会自动执行MyThread中重写的run()方法)
/**
* 1、类继承Thread类
**/
public class ThreadTest extends Thread{
//必须重写Thread类中的run方法
@Override
public void run() {
//super.run();
for (int i = 0; i <= 5; i++) {
System.out.println("子线程:"+i);
}
}
}
public class ThreadMain {
public static void main(String[] args) {
//2、创建子线程对象
Thread t = new ThreadTest();
//3、启动子线程
t.start();
for (int i = 0; i <= 5; i++) {
System.out.println("主线程:"+i);
}
}
}
PS:
- 该方法编码简单,但使用了继承,不利于扩展。
- 启动线程必须是用start方法,不能用run方法,否知会变成单线程。
- 不要把主线程任务放到启动子线程之前,否则会变成成主线程执行完了,才执行子线程。
6.2、实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run方法
- 创建MyRunnable对象
- 把MyRunnable对象交给Thread处理
/**
* 1、实现Runnable接口
**/
public class RunnableTest implements Runnable{
//重现run方法
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println("子线程:"+i);
}
}
}
public class RunnableMain {
public static void main(String[] args) {
/**
* 2.创建子线程对象
*/
Runnable target = new RunnableTest();
/**
* 3.把Runnable对象交给Thread处理
*/
Thread t = new Thread(target);
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出" + i);
}
}
}
PS:实现接口,可以继续继承或者实现接口,扩展性强
匿名内部类(推荐方法)
public class RunnableMain1 {
public static void main(String[] args) {
//简化方式一
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println("子线程1:"+i);
}
}
};
new Thread(r).start();
//简化方式二
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println("子线程2:"+i);
}
}
}).start();
//简化方式三(lambda表达式)(jdk8以上)
new Thread(() -> {
for (int i = 0; i <= 5; i++) {
System.out.println("子线程3:"+i);
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出" + i);
}
}
}
6.3、实现Callable接口,通过FutureTask接口接收返回值(JDK5新增)
- 得到任务对象 第一步:定义一个线程任务类MyCallable实现Callable接口,重写call方法,该方法可以返回结果 第二步:用Future吧Callable对象封装成线程任务对象
- 把线程任务对象交给Thread处理
- 调用Thread的start方法启动任务
- 线程执行完毕后,通过FutureTask的get方法获得结果
/**
* 1、定义一个实现类,实现Callable接口,记得声明结果的数据类型
**/
public class CallableTest implements Callable<String> {
//2.重写call方法
private int n;
public CallableTest(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "子线程执行的结果是" + sum;
}
}
public class CalableMain {
public static void main(String[] args) {
//3.创建任务对象
Callable<String> call1 = new CallableTest(100);
/**
* 4.把Callable任务对象交给FutureTask对象
* FutureTask的作用1:FutureTask实现了Runnable接口,此时就可以交给Thread了
* FutureTask的作用2:可以在线程执行完毕后调用get方法得到线程执行的结果
*/
//Thread t = new Thread(call); 报错,Thread不能接收call对象
FutureTask<String> f1 = new FutureTask<>(call1);
//5.交给线程处理
Thread t1 = new Thread(f1);
//6.启动线程
t1.start();
Callable<String> call2 = new CallableTest(200);
FutureTask<String> f2 = new FutureTask<>(call2);
Thread t2 = new Thread(f2);
t2.start();
try {
String rs1 = f1.get(); //直接调用call方法,可能还没有执行完,使用get时若发现线程未执行完会先等线程执行完毕
System.out.println("第一个结果为:" + rs1);
} catch (Exception e) {
e.printStackTrace();
}
try {
String rs2 = f2.get(); //直接调用call方法,可能还没有执行完,使用get时若发现线程未执行完会先等线程执行完毕
System.out.println("第二个结果为:" + rs2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
PS:
- 扩展性强,可以继续继承和实现。
- 可以在线程执行完毕后获取线程执行的结果。
6.4、Thread常用方法和构造器
Thread提供的常用方法 | 说明 |
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
jublic String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
public void setName(String name) | 为线程设置名称 |
public static Thread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后,再继续执行 |
public final void join()... | 让调用当前这个放的线程先执行完 |
Thread提供的常见构造器 | 说明 |
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target,String name) | 封装Runnable对象成为线程对象,并指定线程名称 |