Java-多线程
线程
要想学习多线程,就得先知道什么是线程,要想知道线程,就得先知道什么是进程。
- 进程:
是指正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和资源。
通过任务管理器看- 线程:
是进程的单个顺序控制流,或者就是说是一个单独执行的路径
如果一个进程只有一条执行路径,称之为单线程
如果一个进程有多条执行路径,称之为多线程
线程是包含在进程中的。
举例:扫雷,360杀毒软件,百度网盘
何为串行,并行,并发?
- 1、串行,是指一个程序中所有的任务都是按照先后顺序执行的,在前一个任务还没有处理完的情况下,是不会进行处理下一个任务的。
举例:理发店只有一个理发师,很多人去理发,就需要排队,就有先后顺序,先等前面的人理完发,再轮到后面的人。- 2、并行,是指将任务分给不同的处理器去畜栏里,每一个处理器中的任务再进行串行处理
举例:火车站上有很多卖票窗口,多个窗口同时卖票,但是呢,针对于某一个窗口来说,是一个接着一个去处理的。- 3、并发,是指一个现象,并发需要处理器的支持,比如在处理一个任务的时候,操作系统可以调用资源去处理其他的任务,这个任务并行还是串行都可以
无论是串行还是并行,都需要处理支持并发。
举例:假设喝水是一个任务,每个火车站售票员,他再售票的同时也能喝水,这就表示支持并发
思考?JVM启动的时候是单线程还是多线程呢?多线程
- JVM启动的时候,相当于启动了一个程序,就是启动了一个进程
其中包含了主线程,垃圾回收线程。
在启动JVM的时候,最低的要求是需要启动两个线程,所以JVM启动的时候是多线程程序。
创建线程的第一种方式:继承Thread类,重写run方法
- 1、创建一个自定义类继承Thread类
- 2、这个继承的类要重写run方法
1)为什么要重写run方法?
2)重写run方法后如何使用?- 3、根据这个类创建线程对象
- 4、启动线程
面试题:线程调用start()和run()方法的区别?
- 单纯地调用run方法仅仅表示的是第一个对象调用普通的方法,所以这里的执行还是按照自上而下顺序执行,所以这里依旧是单线程程序
要想看到多线程程序的执行效果,就必须换一种方式启动线程。start()
run方法中仅仅是封装了被线程执行的逻辑代码,但是直接对象调用run方法于普通的方法调用没有任何区别
start()方法调用,首先做的是启动一个线程,然后再由JVM去调用该线程对象中的run()方法
模拟多线程环境
- 要想模拟多线程环境,就得创建多个线程对象,然后同时启动
模拟多线程环境,至少创建2个及以上的线程对象
注意事项
- 1、启动线程调用的是start()方法
- 2、线程的调用start()方法先后顺序与今后真正执行的顺序没有影响
线程的逻辑代码
package com.shujia.wyh.day25;
public class MyThread1 extends Thread{
@Override
public void run() {
//写的是线程要执行的逻辑代码
// System.out.println("数加真好!");
//一般情况下,线程执行的逻辑代码都是比较耗时并且复杂的,为了模拟这里耗时复杂,我使用循环打印来代替
for(int i=1;i<=300;i++){
System.out.println(i);
}
}
}
Thread类的基本获取和设置方法
1.设置名字(两种方法)
- 1、public final void setName(String name)
- 2、通过构造方法给线程起名字Thread(String name)
获取名字
- 如何获取一个线程的名字呢?
public final String getName()
public class MyThreadDemo3 {
public static void main(String[] args) {
// MyThread2 t1 = new MyThread2(); //Thread-0
// MyThread2 t2 = new MyThread2(); //Thread-1
MyThread2 t1 = new MyThread2("李毅");
MyThread2 t2 = new MyThread2("小虎");
// t1.setName("李毅");
// t2.setName("小虎");
//启动线程
t1.start();
t2.start();
}
}
线程调度问题
问题引入
- 假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,
才可以执行指令。那么Java是如何对线程进行调用的呢?
线程的两种调度模型(Java属于抢占式调度模型)
- 1、分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 2、抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
Java使用的是抢占式调度模型。***
获取线程中的优先级方法:
public final int getPriority() 返回此线程的优先级。
设置线程优先级的方法:
- public final void setPriority(int newPriority) 更改此线程的优先级。
最大和最小优先级
public final static int MIN_PRIORITY = 1; 线程可以拥有的最小的优先级
public final static int MAX_PRIORITY = 10; 线程可以拥有的最大的优先级
注意事项:
- 1、线程的默认优先级是5
- 2、设置优先级的时候,范围是1-10
- 3、线程的优先级越高仅仅表示的是获取CPU时间片的机率会高一些,并不能保证一定会先执行。
代码如下:
public class ThreadPriorityDemo {
public static void main(String[] args) {
MyPriorityThread p1 = new MyPriorityThread();
MyPriorityThread p2 = new MyPriorityThread();
MyPriorityThread p3 = new MyPriorityThread();
//获取线程优先级
// int py1 = p1.getPriority();
// System.out.println(py1); 5
// int py2 = p2.getPriority();
// System.out.println(py2); 5
// int py3 = p3.getPriority();
// System.out.println(py3); 5
//可以设置线程的优先级
p1.setPriority(10);
p2.setPriority(5);
p3.setPriority(1);
//设置各个线程的名字
p1.setName("小花");
p2.setName("小黑");
p3.setName("小白");
p1.start();
p2.start();
p3.start();
}
}
线程加入 public final void join()
- public final void join()
线程对象调用该方法的时候,目的是让调用该方法的当前线程先执行完,执行完毕后,再让其他线程执行
其他没有调用join方法的线程,他们之间还是会抢CPU执行权的。
注意事项:
- join方法的调用,必须是紧跟着当前线程start()方法后调用,否则不起作用。
public class JoinDemo {
public static void main(String[] args) {
MyJoinThread jd1 = new MyJoinThread();
MyJoinThread jd2 = new MyJoinThread();
MyJoinThread jd3 = new MyJoinThread();
//给各个线程取名字
jd1.setName("无敌小可爱");
jd2.setName("无敌菠萝怪");
jd3.setName("无敌香蕉π");
//注意:join 方法的调用,必须是紧跟着当前线程start()方法后调用的,否则不起作用;
//启动各个线程
// try {
// jd1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
jd1.start();
try {
jd1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
jd2.start();
jd3.start();
}
}
线程礼让 public static void yield()
- 礼让线程的目的是暂停当前正在执行的线程,并让其他线程执行,
它的作用实际上是为了让线程之间看起来更加和谐,它并不能保证多个线程之间一人一次。
public class MyYieldDemo {
public static void main(String[] args) {
//创建多线程环境
MyYieldThread t1 = new MyYieldThread();
MyYieldThread t2 = new MyYieldThread();
MyYieldThread t3 = new MyYieldThread();
//给线程设置名字
t1.setName("小白");
t2.setName("小黑");
t3.setName("小红");
t1.start();
t2.start();
t3.start();
}
}
后台线程(守护线程) public final void setDaemon(boolean on)
线程分类
- 用户线程:我们在学习线程之前,运行起来的一个一个程序中的线程都是用户线程
- 守护线程:所谓的守护线程,指的是程序运行的时候,在后台提供了一个通用的服务线程,比如说垃圾回收线程,他就是一个守护线程。
这种线程不一定是要存在的,但是可能程序会出问题。只要程序存在用户线程,程序就不会停止
守护线程的设置:
- public final void setDaemon(boolean on)
注意事项:
- 1、守护线程必须在启动之前进行设置
public class ThreadDaemonDemo {
public static void main(String[] args) {
//模拟多线程环境
MyDaemonThread dd1 = new MyDaemonThread();
MyDaemonThread dd2 = new MyDaemonThread();
MyDaemonThread dd3 = new MyDaemonThread();
//给线程付名字
// dd1.setName("草莓");
// dd1.setDaemon(true);
dd2.setName("西瓜");
dd2.setDaemon(true);
dd3.setName("菠萝");
dd3.setDaemon(true);
//当所有线程都为守护进程时。守护进程会马上死亡,因为它没有需要守护的东西
//当草莓为线程,其他俩是守护线程时,如果草莓进程结束,那么,西瓜和菠萝也会很快调亡
//启动线程
// dd1.start();
dd2.start();
dd3.start();
}
}
休眠线程
- 模拟真实环境,让线程进入休眠状态
package com.bigdat.java.day26;
public class MySleepThread extends Thread{
@Override
public void run() {
for (int i = 0; i <= 300; i++) {
//需求:每打印一次,间隔 1000 毫秒在打印下一个
//public static void sleep(long millis) throws InterruptedException
//史当前正在执行的线程已指定的毫秒数暂停
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":"+i);
}
}
}
package com.bigdat.java.day26;
/*
public static void sleep(long millis)
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
//创建三个进程
MySleepThread st1 = new MySleepThread();
MySleepThread st2 = new MySleepThread();
MySleepThread st3 = new MySleepThread();
//给进程名字
st1.setName("窗口一");
st2.setName("窗口二");
st3.setName("窗口三");
//启动线程
st1.start();
st2.start();
st3.start();
}
}
中断线程
- 在自定义类中定义每个线程睡眠5秒
public class MyStopTread extends Thread{
@Override
public void run() {
System.out.println("我开始睡觉。。。。");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡醒了。。。");
}
}
- 然后再测试类中,将正在睡眠的线程唤醒,即打断线程的休眠时间
两种方法
- public final void stop()
强制打断睡眠,程序停止,这个方法不太好,这个方法已经被弃用
- public void interrupt()/打断睡眠,提示打断睡眠的错误,run方法中后面的代码继续执行,直到执行完毕
public class ThreadStopDemo {
public static void main(String[] args) {
MyStopTread myStopTread = new MyStopTread();
myStopTread.start();
try {
Thread.sleep(2000);
// myStopTread.stop(); //强制打断睡眠,程序停止,这个方法不太好,这个方法已经被弃用
myStopTread.interrupt(); //打断睡眠,提示打断睡眠的错误,run方法中后面的代码继续执行,直到执行完毕。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
多线程的第二种打开方式-runnable
多线程的实现方式2:实现Runnable接口,实现run方法
- 1、自定义一个类实现Runnable接口
- 2、实现run方法
- 3、创建实现Runnable接口类对应的对象
- 4、借助Thread类创建线程对象,将自定义类作为构造方法的参数传入
主方法代码:
public class MyRunnableDemo1 {
public static void main(String[] args) {
//创建实现Runnable接口类对应的对象
MyRunnable1 myRunnable1 = new MyRunnable1();
//创建多个线程对象
// Thread t1 = new Thread(myRunnable1);
// Thread t2 = new Thread(myRunnable1);
//Thread(Runnable target, String name)
//分配一个新的 Thread对象。
Thread t1 = new Thread(myRunnable1,"小白");
Thread t2 = new Thread(myRunnable1,"小黑");
//给线程起名字
// t1.setName("李毅");
// t2.setName("小虎");
//启动线程
t1.start();
t2.start();
}
}
自定义类代码
public class MyRunnable1 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 300; i++) {
//由于Runnable接口没有getName()方法,所以在这里无法直接调用获取线程的名字
//间接调用,
//Thread类中有一个静态的方法
// public static Thread currentThread()返回对当前正在执行的线程对象的引用。
// System.out.println(getName() + ":" + i);
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人