多线程入门(概念、api等等相关基础)
关于多线程的名词
进程: 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销,一个进程包含1–n个线程。
线程: 同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
并发: 并发是指一个线程或者是计算机的内核同时执行多个任务。
举例:一个人种一棵树需要5天, 五个人(线程)种一棵树需要一天
并行: 并行是指任务在同一时间运行,每个内核同一时间内只执行一个任务。
举例:一个人种一棵树需要5天, 两个人种两棵树需要10天
线程优先级: java程序属于抢占式调度,优先让优先级高的线程使用cpu,如果线程的优先级相同的,那么随机选择一个线程执行。
注意:
- 多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
对象.run()
可以执行方法,但是不是使用的多线程的方式,就是一个普通的方法,要想实现多线程的方式,一定需要通过对象.start()
方法。
继承Thread类和实现Runnable接口的区别:
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
- 避免点继承的局限,一个类可以实现多个接口
- Thread类也是Runnable接口的子类
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
- 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
线程的状态 生命周期
- 新建状态(New):新创建了一个线程对象。
- 就绪状态(Runnable):一旦调用了Start()方法 那么线程就处与可以运行的状态,等待获取CPU的使用权。
- 运行状态(Running):就绪状态的线程获取了CPU执行权力,开始正真的执行Run方法中的代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1. 等待阻塞:运行的线程调用wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。wait和sleep的区别 一个是放弃锁并进入等待,一个是不放弃锁,时间到了就开始执行。 - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
多线程的实现方式(三种)
-
继承Thread类(不常用)、
-
实现Runnable接口(常用)、
-
实现Callable接口通过FutureTask包装器来创建Thread线程、
-
使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
线程池内容很多,以后单独写一篇;
继承Thread
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
/**
* explain:继承Thread类的方式来实现多线程
*
* @author Hope
* @date 2022/4/5
* @see #start()
* @see #run()
*/
@AllArgsConstructor
@NoArgsConstructor
public class ThreadRunTest extends Thread {
private String threadName;
@lombok.SneakyThrows
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + "运行: " + i);
// Thread.sleep()方法调用目的是突出线程抢夺CPU资源,线程是交替执行。
sleep(100);
}
}
public static void main(String[] args) {
ThreadRunTest startA = new ThreadRunTest("线程A");
ThreadRunTest startB = new ThreadRunTest("线程B");
startA.start();
startB.start();
// 从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
}
}
实现Runnable
/**
* explain: 实现Runnable接口来实现多线程
*
* @author Hope
* @date 2022/4/5
* @see java.lang.Runnable
*/
@AllArgsConstructor
@NoArgsConstructor
public class RunnableRunTest implements Runnable {
private String threadName;
@SneakyThrows
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + "运行: " + i);
// Thread.sleep()方法调用目的是突出线程抢夺CPU资源,线程是交替执行。
sleep(100);
}
}
public static void main(String[] args) {
RunnableRunTest startA = new RunnableRunTest("线程A");
RunnableRunTest startB = new RunnableRunTest("线程B");
new Thread(startA).start();
new Thread(startB).start();
// run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
// 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,
// 然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。
}
}
实现Callable
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import static java.lang.Thread.sleep;
/**
* explain:采用实现Callable接口实现多线程
* 采用实现Callable接口实现多线程启动方式和以往两种的方式不太一样,
* 下面就看一下怎样启动采用实现Callable接口的线程,首先我 new 一个我的实现实例,然后将我生成的实例对象注册进入到
* FutureTask类中,然后将FutureTask类的实例注册进入Thread中运行。最后可以采用FutureTask<V>中的get方法获取自定义线程的返回值
* <p>
* <p>
* 为什么实现Callable接口实现的多线程的形式需要采用这样的方式去启动线程。
* Callable接口的源码,其中只有一个call方法的定义,
* FutureTask<V>类其中我们可以看到FutureTask<V>类是实现了RunnableFuture<V>接口的,
* 我们再看FutureTask<V>类其中的一个构造方法如下,其中需要传入一个Callable<V>类型的参数
*
* @author Hope
* @date 2022/4/5
* @see FutureTask
* @see Runnable
* @see Thread
*/
@AllArgsConstructor
@NoArgsConstructor
public class CallableRunTest implements Callable {
private String threadName;
@Override
public Object call() throws Exception {
int age = 0;
for (int i = 0; i < 5; i++) {
System.out.println(threadName + "运行: " + i);
age++;
// Thread.sleep()方法调用目的是突出线程抢夺CPU资源,线程是交替执行。
sleep(100);
}
return threadName + "打印次数:" + age;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableRunTest startA = new CallableRunTest("线程A");
CallableRunTest startB = new CallableRunTest("线程B");
FutureTask futureTaskA = new FutureTask(startA);
FutureTask futureTaskB = new FutureTask(startB);
new Thread(futureTaskA).start();
new Thread(futureTaskB).start();
System.out.println(futureTaskA.get());
System.out.println(futureTaskB.get());
}
}
Thread-Api
/**
* explain:Thread类中的方法
*
* @author Hope
* @date 2022/4/6
*/
public class ThreadApiTest {
@SneakyThrows
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("线程执行中!"));
//void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
// 多次启动一个线程是非法的:一个线程对象只能调用一次start方法。特别是当线程已经结束执行后,不能再重新启动。
thread.start();
//void setName(String name)->设置线程名字
thread.setName("线程001");
// static void sleep(long millis)->让当前线程睡眠 millis:设置的是线程休眠的时间(毫秒值)
Thread.sleep(100);
//String getName():获取线程名字
System.out.println(thread.getName());
//String toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
System.out.println(thread.toString());
//模拟线程在处理数据 等待3秒
Thread.sleep(3000);
//void join() 等待该线程终止, 在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
thread.join();
//static Thread currentThread()->获取当前正在运行的线程对象,在哪个线程中调用,获取的就是哪个线程对象
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
}
}