- 前言
- 一、概念引入
- 1、程序
- 2、 进程
- 3、 线程
- 4、 并行与并发
- 5、 单核CPU和多核CPU的理解
- 二、多线程意义
- 三、线程的创建与使用
- 1、 通过继承Thread
- 2、 通过实现Runnable接口
- 3、 继承大战实现
- 四、线程中的常用方法
- 五、线程的优先级
一、概念引入好久不见!狗子我在啃完了MySQL的基础之后,便转回头继续学习Java了,毕竟Java只掌握到异常时远远不够的,有着大厂梦的陈宝子总得加油冲!奥里给!若阅读过程中存在什么问题欢迎评论留言。
1、程序
程序(program)是为完成特定任务、用某种语言编写的一组指令集合,即指一段静态的代码,静待对象。
2、 进程进程(process)是程序的一次执行过程,或是正在运行的一个程序,这类似于我们电脑里面的任务管理器。是一个动态的过程,有它自身的产生、存在和消亡的过程,即生命周期。
- 程序是静态的,进程是动态的;
- 进程作为资源分配的单元,系统在运行时会为每个进程分配不同的内存区域。
线程(thread)是由进程中细化得来的,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的;
- 线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效,但多个线程操作共享的系统资源可能就会带来安全的隐患。
- 并行:多个CPU同时执行多个任务。比如,多个人同时做不同的事情、机场同时起飞飞往不同地方的飞机;
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:假期多人同时做同一件事、假期间某数字软件抢票。
- 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来;
- 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的);
- 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
我们以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?这就需要我们从多线程的优点入手:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验;
- 提高计算机系统CPU的利用率;
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
那么现在问题又来了,我们到底该什么时候会用到多线程呢?主要有以下使用场景:
- 程序需要同时执行两个或多个任务;
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等;
- 需要一些后台运行的程序时。
1、 通过继承Thread
在创建一个分线程的时候,我们可直接继承Thread类,通过该类实例化的对象进行开启线程。具体步骤如下:
- 创建一个继承于 Thread类 的子类
- 重写 Thread类 中的 run(),将此线程执行的操作声明在 run() 中
- 创建 Thread类 的子类的对象
- 通过此对象调用 start()
//1. 创建一个继承于Thread类的子类
public class CreateThread extends Thread{
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
CreateThread t1 = new CreateThread();
CreateThread t2 = new CreateThread();
//4. 通过对象调用start()方法
t1.start();
t2.start();
//在主线程中添加输出语句进行验证两个线程是否同时执行
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i); //输出线程名,检测各线程的执行
}
}
//2. 重写Thread类中的run()方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(i%2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i); //输出线程名,检测各线程的执行
}
}
}
}
但在创建和使用线程的过程中需要注意下面两点:
- 不能通过直接调用 run()方法 启动线程,这不是多线程,只是调用了方法而已
- 若想再启动一个线程,不可用已经调用了 start() 的线程去执行,需重新创建一个线程的对象才行,否则会报IllegalThreadStateException
由于有些线程只使用一次,因此我们可以通过匿名子类的方式对线程的核心内容进行编写,下面通过练习进行演示(创建两个线程,一个线程输出奇数,一个线程输出偶数)
public class ThreadDemo01 {
//可按部就班根据步骤书写两个线程类继承Thread类,也可利用匿名子类
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
2、 通过实现Runnable接口
Java 中的 lang包 下内置了 Runnable 接口,我们可通过实现 Runnable 接口中的 run() 方法进行创建线程。具体步骤如下:
- 创建一个实现了 Runnable 接口的类
- 实现类去实现 Runnable 中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到 Thread 类中的构造器中,创建 Thread 类中的对象
- 通过 Thread 类中的对象调用 start(),其中调用了 Runnable 类型的 target 对象
//1. 创建一个实现了 Runnable 接口的类
public class ThreadRunnable implements Runnable{
public static void main(String[] args) {
//3. 创建实现类的对象
ThreadRunnable threadRunnable = new ThreadRunnable();
//4. 将此对象作为参数传递到 Thread 类中的构造器中,创建 Thread 类中的对象
Thread t1 = new Thread(threadRunnable);
Thread t2 = new Thread(threadRunnable); //方式二中可通过 new 一个新的 Thread 对象开启新线程,共用一个实现类
t1.setName("线程1");
t2.setName("线程2");
//5. 通过 Thread 类中的对象调用 start()
t1.start();
t2.start();
for (int i = 0; i < 50; i++) {
if(i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
//2. 实现类去实现 Runnable 中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
3、 继承大战实现
细心的伙伴可能已经发现,Thread类 其实也实现了 Runnable 接口,Thread类 也是 Runnable 接口的一个特殊的实现类。无论我们选择继承 Thread类 还是实现 Runnable接口 的方式进行创建线程,我们都需要重写 run(),将线程要执行的逻辑声明在 run() 中,都需要通过 start() 方法进行开启线程。那么这两种我们到底选择那种呢?
开发中,优先选择实现 Runnable接口 的方式,原因如下并通过多个窗口卖票的案例进行展示:
- 实现的方式没有类的单继承性的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况
public class ThreadTicket01 {
public static void main(String[] args) {
//创建三个窗口卖票线程
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
//设置三个线程名
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
//开启三个线程
w1.start();
w2.start();
w3.start();
}
}
//创建继承了Thread类的子类
class Window extends Thread {
private static int ticket = 100; //设置为静态变量使得定义的多个变量公用共用同一个ticket变量
@Override
public void run() {
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + ": 当前票号为: " + ticket--);
}
}
}
public class ThreadTicket02{
public static void main(String[] args) {
//实例化实现类对象
NewWindow newWindow = new NewWindow();
//实例化三个窗口的线程
Thread w1 = new Thread(newWindow);
Thread w2 = new Thread(newWindow);
Thread w3 = new Thread(newWindow);
//重命名三个线程
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
//启动三个线程
w1.start();
w2.start();
w3.start();
}
}
//实现 Runnable 接口
class NewWindow implements Runnable {
private int ticket = 100; //由于三个线程共用一个实现类,因此不需要使用static
@Override
public void run() {
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + ": 当前票号为: " + ticket--);
}
}
}
四、线程中的常用方法
-
run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中;
@Override public void run() { for (int i = 0; i < 50; i++) { if(i%2 == 0) { System.out.println(i); } } } -
start(): 启动当前线程;调用当前线程的run();
MyThread t1 = new MyThread(); t1.start();
-
currentThread(): 静态方法,返回执行当前代码的线程;
-
getName(): 获得当前线程的名字;
-
setName(): 设置当前线程的名字;
System.out.println(Thread.currentThread().getName()); Thread.currentThread.setName("线程1"); -
yield(): 释放当前CPU的执行权,即线程遇到这一方法时会暂时将执行权转给其余线程;
if(i % 20 == 0) { yield(); }
-
join(): 会抛异常。在 线程a 中调用 线程b 的 join(),此时 线程a 就会进入阻塞状态,直到 线程b 完全执行完之后,线程a 才结束阻塞状态;
public class text { public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); System.out.println("Hello"); try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("World"); } } class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { if(i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } } -
stop(): 已过时。当执行此方法时,强制结束当前线程;
-
sleep(long millis): 会抛异常。让当前下线程**“睡眠”指定的 millis 毫秒**。在指定的 millis 毫秒。在指定的 millis 毫秒时间内,当前线程时阻塞状态;
try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } -
inAlive(): 判断当前线程是否存活,返回值类型为 boolean。
System.out.println(t1.inAlive);
线程中的优先级范围为1~10,其中较为特殊的三个优先级在 Java 的 Thread类 中已经内置好了,分别为 MAX_PRIORITY、MIN_PRIORITY 和 NORM_PRIORITY,分别代表优先级数值为10、1 和 5。在优先级中常用的两个方法如下:
- 获取线程优先级:getPriority()
- 设置线程优先级:setPriority()
说明:高优先级的线程要抢占低优先级线程CPU的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
public class ThreadPriority {
public static void main(String[] args) {
MyPriority p1 = new MyPriority();
p1.setPriority(Thread.MAX_PRIORITY); ///设置优先级为最高
p1.start();
for (int i = 0; i < 50; i++) {
if(i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getPriority() + ": " + i);
}
}
}
}
class MyPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getPriority() + ": " + i);
}
}
}
}



