多线程
多线程:栈空间独立,堆内存共享。
一、线程与进程
- 进程
正在运行的应用程序:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
每个进程都有着自己独立的堆、栈且是互不共享的
-
线程
- 进程中的一个执行路径(一个应用程序从执行到结束的整个过程),共享一个内存空间,一个进程中可以包含多条线程;
- 线程之间可以自由切换,并发执行;
- 一个进程最少有一个线程,线程控制着进程。
二、线程调度
-
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
-
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为 抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
三、同步与异步&并发与并行
同步:排队执行 , 效率低但是安全
异步:同时执行 , 效率高但是数据不安全
并发:指两个任务在同一时间段内都请求运行
处理器只能接收一个任务,把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行
- 如果用一台电脑先给友人A发个消息,然后立刻再给友人B发消息,然后再跟友人A聊,再跟友人B聊。这就叫并发。
并行:两个任务同一时刻运行,需要多核CPU
- 比如跟两个朋友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
四、多线程实现方式
1、继承Thread
步骤:
- 定义MyThread类并继承Thread类;
- 重写run方法,新的执行路径(通过thread对象的start()启动任务)
/**
* @author Elvira
* @date 2020/10/12 16:01
* @description 继承Thread
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("嘿……"+i);
}
}
}
-
创建子类对象
-
调用start(),让线程执行
/**
* @author Elvira
* @date 2020/10/12 16:08
* @description 继承Thread的程序入口
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
for (int i = 0; i < 10; i++) {
System.out.println("嘻嘻……"
+i);
}
}
}
- 运行结果:可以看到顺序并不统一,这是因为线程在抢占时间片,谁先抢到就是谁的
线程时序图:
注意:运行过程中子线程任务中调用的方法都在子线程中运行
2、实现Runnable
步骤:
- 实现Runnable接口;
- 实现抽象方法run(),编写线程的任务;
/**
* @author Elvira
* @date 2020/10/12 16:22
* @description 实现Runnable
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("哼哼!" + i);
}
}
}
-
创建一个任务对象
-
创建一个线程,并给它一个任务
-
调用start()方法执行线程
/**
* @author Elvira
* @date 2020/10/12 16:30
* @description 实现Runnable的程序入口
*/
public class RunnableTest {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("呵呵?" + i);
}
}
}
- 运行结果:
3、实现Runnable和继承Thread比较
继承Thread
- 优点:直接使用Thread类中的方法,代码简单
- 弊端:如果已有父类,不可用
实现Runnable接口(更常用)
-
其优点:
1. 通过创建任务,给线程分配任务实现多线程,更适合多个线程同时执行相同任务的情况
-
可以避免单继承带来的局限性
-
任务和线程分离,提高程序健壮性
-
(最重要)后续提供的线程池任务,接收Runnable类型任务,不接收Thread类型线程
-
-
弊端:不能直接使用Thread中的方法,需先获取线程对象,再得到Thread的方法,代码复杂
4、匿名内部类实现方式
- 继承Thread
/**
* @author Elvira
* @date 2020/10/12 16:52
* @description 继承thread的匿名内部类实现方式
*/
public class MyThread0 {
public static void main(String[] args) {
new Thread() {
public 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);
}
}
}
- 运行结果:
- 实现Runnable
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("aaa" + i);
}
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("bbb" + i);
}
- 运行结果:
五、Thread类
接下来列举API中Thread类最常用的方法:
构造器 | 描述 |
---|---|
Thread() |
分配新的 Thread 对象。 |
Thread(Runnable target) |
分配新的 Thread 对象。 |
Thread(Runnable target, String name) |
分配新的 Thread 对象。 |
Thread(String name) |
分配新的 Thread 对象。 |
常见方法:
变量和类型 | 方法 | 描述 |
---|---|---|
long |
getId() |
返回此Thread的标识符。 |
String |
getName() |
返回此线程的名称。 |
int |
getPriority() |
返回此线程的优先级。 |
void |
setPriority(int newPriority) |
更改此线程的优先级。 |
void |
start() |
导致此线程开始执行; Java虚拟机调用此线程的run 方法。 |
static void |
sleep(long millis) |
导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 |
static void |
sleep(long millis, int nanos) |
导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。 |
void |
setDaemon(boolean on) |
将此线程标记为 daemon线程或用户线程。 |
特殊字段:控制线程抢到时间片的几率
变量和类型 | 字段 | 描述 |
---|---|---|
static int |
MAX_PRIORITY |
线程可以拥有的最大优先级。 |
static int |
MIN_PRIORITY |
线程可以拥有的最低优先级。 |
static int |
NORM_PRIORITY |
分配给线程的默认优先级。 |
六、设置和获取线程名称
首先需要了解:currentThread() 可以获取当前正在执行的线程对象
- 获取线程名称
/**
* @author Elvira
* @date 2020/10/12 17:41
* @description 获取线程名称
*/
public class NameTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
//给线程指定一个名称
new Thread(new MyRunnable(), "Elvira").start();
}
}
实现类:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
- 运行结果:
如果不指定线程名称,直接给线程一个任务:再加两句
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();