什么线程:一个程序内部的一条执行路径。简单的说就是在做事情
什么是单线程:程序内部只有一条单独的执行路径。简单的说就是只做一件事情
什么是多线程:从软硬件上实现多条执行流程的路径。简单的说就是同时做多件事情
线程可以减少队列阻塞带来的影响,提高CPU的利用率
进程与线程
进程:正在运行的程序(软件)就是一个独立的进程
线程:线程是属于进程的,一个进程中可以同时运行很多个线程
如何创建多线程
一、继承 Thread
类
- 定义一个子类继承线程类
java.lang.Thread
,重写run()
方法 - 创建子类的对象
- 调用子类对象的
start()
方法启动线程(底层会自动去执行run方法)
public class Demo1 {
public static void main(String[] args) {
//需求:创建两个线程,分别用于打印10个A和10个B,最后观察下输出顺序
A a = new A();
B b = new B();
a.start();
a.setName("线程A");
b.start();
b.setName("线程B");
//main线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class A extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//Thread.currentThread() ---->获取当前线程对象
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}
class B extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//Thread.currentThread() ---->获取当前线程对象
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}
二、实现 Runnable
接口
- 定义一个线程任务类实现
Runnable
接口,重写run()
方法 - 创建任务类对象
- 把任务类对象交给
Thread
处理
-
- public Thread(Runnable target)
- 调用线程对象的
start()
方法启动线程
public class Demo1 {
public static void main(String[] args) {
//需求:创建两个线程,分别用于打印10个A和10个B,最后观察下输出顺序
A a = new A();
B b = new B();
Thread At = new Thread(a,"A**");
Thread Bt = new Thread(b,"B**");
At.start();
Bt.start();
//Thread Ct = new Thread(new Runnable(){});
//可使用Lambda实现,他是一个函数式接口
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":C"+i);
}
},"C**").start();
}
}
class A implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":A"+i);
}
}
}
class B implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":B"+i);
}
}
}
三、实现 Callable<T>
接口
- 创建任务对象
-
- 定义一个类实现
Callable
接口,重写call()
方法,封装要做的事情,和要返回的数据 - 将
Callable
类型的对象封装成FutureTask
(线程任务对象)
- 定义一个类实现
-
-
public FutureTask<>(Callable call)
把Callable
对象封装成FutureTask
对象public V get() throws Exception
获取线程执行call()
方法返回的结果
-
- 把线程任务对象交给
Thread
对象。 - 调用
Thread
对象的start()
方法启动线程。 - 线程执行完毕后,可以通过
FutureTask
对象的的get()
方法去获取线程任务执行的结果
public class Demo1 {
public static void main(String[] args) throws Exception {
//需求:启动两个子线程,分别计算100之内的奇数的和和偶数的和,然后在主线程中再做个汇总,得到总和
A a = new A();
B b = new B();
FutureTask<Integer> f1 = new FutureTask<>(a);
FutureTask<Integer> f2 = new FutureTask<>(b);
Thread t1 = new Thread(f1, "A");
Thread t2 = new Thread(f2, "B");
t1.start();
t2.start();
// FutureTask获取结果时 在无结果返回时,会一直等待结果
// 如果有多个线程的返回值在一起使用,只有当最后一个返回值返回之后才会在去执行
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println("总和:"+i1+i2);
}
}
class A implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i+=2) {
sum+=i;
}
return sum;
}
}
class B implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <= 100; i+=2) {
sum+=i;
}
Thread.sleep(3000);
System.out.println("睡醒了,给你");
return sum;
}
}
上面三种实现方式的优缺点
Thread、Runnable、Callable<T> 的优缺点
Thread:编码简单,但它是继承的,无法继承其他类,不利于功能的扩展。
Runnable:虽然它是使用接口的,但在使用时会多出一个对象
Callable<T>:是一个接口,扩展性强;执行完毕后可以获得线程的返回结果,但编码复杂一些
方式 | 优点 | 缺点 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 |
四、使用线程池创建线程
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/mohuizhu/hz562v/isdfzg05vu693i8q
常用方法
Thread提供的常用方法 | 说明 |
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public 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对象成为线程对象,并指定线程名称 |
使用线程需要注意的事项
Thread
和Runnable
重写run()
方法后是使用Thread
中的start()
方法启动start()
方法底层是一个本地方法,它底层会调用run()
方法。- 如果直接调用
run()
,那么他就是一个普通方法了。 - 主线任务应该放在
start()
之后,如过不是,它将会先执行完主线任务后才会开启其他线程 join()
让调用当前这个方法的线程先执行完,当同时又A、B两个线程,在B线程使用A线程中的join()
方法,那么在A线程抢到CPU后才会先执行A线程
public class Demo1 {
//需求: 通过下面任务,我们来学习sleep和join方法
//1. 创建A、B两个线程类,分别打印1~5
//2. 在主线程序中创建A、B两个子线程
//3. 在主线程序中开启A、B两个子线程
//4. 让A线程每打印一次,都要暂停3秒钟
//5. B线程要等A线程打印完毕,再开始打印
public static void main(String[] args) throws InterruptedException {
A a = new A();
B b = new B();
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
t1.join();//抢到CPU后,先执行完成
}
}
class A extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("A"+i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("B"+i);
}
}
}
并发与并行
并发 : CPU分时轮询的执行线程
- CPU同时处理线程的数量有限
- CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发.
并行 : 同一个时刻同时在执行
- 在同一个时刻上,同时有多个线程在被CPU处理并执行。(多核CPU才会出现并行现象)