Java中多线程实现的三种方式及线程的常用操作
程序员文章站
2022-03-29 23:01:43
文章目录前言一、进程、线程、并行、并发二、多线程的实现方式1.多线程实现的方式一2.多线程实现的方式二3.多线程实现的方式三三、多线程中常用操作1.获取和设置线程对象名称2.线程调度及获取和设置线程优先级3.线程休眠4.守护线程5.中断线程6.线程礼让前言首先我们来看下面一段代码,了解单线程的概念。public class 多线程的引入 { public static void main(String[] args) { System.out.println("程序开始执行了...
文章目录
前言
首先我们来看下面一段代码,了解单线程的概念。
public class 多线程的引入 {
public static void main(String[] args) {
System.out.println("程序开始执行了");//1
test();//2
show();//6
}
private static void show(){
System.out.println("show方法执行了");//7
}
private static void test(){
System.out.println("test方法执行");//3
test2();//4
}
private static void test2() {
System.out.println("test2方法执行");//5
}
}
- 单线程:代码的执行路径只有一条。代码在一条主线中执行。
- 单线程的弊端:当某个节点中,存在耗时操作。那么下面的代码,都需要等待该节点执行结束。
- 我们可以将其想象成医院只开了一个窗口,只有当前患者完成咨询,后面的患者才可以咨询。
- 所以我们引出多线程,也就是开启多个"窗口“,耗时”窗口“,并不影响其他”窗口“的运行。
- 多线程:代码的执行路径有多条。
一、进程、线程、并行、并发
- 进程:就是正在运行的程序,是系统进行资源分配和调用的独立单位。
- 线程:正在执行的进程,可能运行很多任务,其中一个任务就是一个线程。
- 举例:
比如我们打开了word软件,一个任务正在进行文字录入,一个任务是进行自动保存。这两个任务就可以看作是两条线程。
线程依赖进程,一个进程至少有一个线程。
- 我们现在的计算机,是可以执行多个进程,也就是说可以同时打开多个应用程序。但是我们要知道,在某个时刻,单核CPU并不是同时执行多个进程,单核CPU在某个时刻只能执行一个进程,我们之所以感觉多个进程是同时执行的,是因为CPU会在多个进程间进行高速切换。
那么线程是同时执行的吗?首先我们需要了解两个概念——并发和并行。
- 并行:真正意义上的多个任务同时执行。(齐头并进)
- 并发:是多个任务高速交替执行,某个时刻只能执行一个任务。(交替执行)
进程是拥有资源的基本单位,线程是CPU调度的基本单位。CPU直接调度的是线程,不是调度进程。
二、多线程的实现方式
首先我们先要了解Java程序运行原理
- java.exe命令启动JVM,等于启动了一个应用程序,也就是进程。
- 该进程会自动启动一个”主线程“,然后主线程会调用某个类的main方法。所以main方法运行在主线程中。
- JVM是多线程的,因为至少启动了垃圾回收线程和主线程。
- JAVA封装好了Thread类,我们只需调用该类中的方法,由它调用底层资源去开启线程。
1.多线程实现的方式一
- 自己定义一个类,继承Thread类
- 重写Thread类中的run()方法——因为run()方法的代码是由线程来执行的。
- 创建自定义类的对象
- 调用start()方法开启线程
public class 多线程的实现方式1 {
public static void main(String[] args) {
MyThread th = new MyThread();
//th.run();并不是开启线程,只是普通的new对象,调了个方法而已。
th.start();
//th.start();多次开启线程是非法的。
MyThread th2 = new MyThread();
th2.start();
//th和th2两个线程并发执行,并不会阻塞主线程的执行。
}
}
class MyThread extends Thread{
@Override
public void run() {
//为什么要重写run()方法?是因为子线程开启后,run()方法中的代码是由子线程来执行的。
//一般耗时的操作代码,就可以放到run()方法内,让线程来执行。
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
2.多线程实现的方式二
- 定义一个类,实现Runable接口,重写Runable接口中的run()方法
- 创建实现这个接口的子类对象
- 创建Thread类的对象,把Runable子类对象传进去
- 调用start()方法,开启线程
public class 多线程是实现方式2 {
public static void main(String[] args) {
MyRunable myRunable = new MyRunable();
Thread th = new Thread(myRunable);
th.start();
}
}
class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() +" "+i);
}
}
}
3.多线程实现的方式三
- 创建一个类实现Callable接口,重写call方法
- 建一个FutureTask类将Callable接口的子类对象作为参数传进去
- 创建thread类,将FutureTask对象作为参数传进去
- 调用start()方法开启线程
public class 多线程的实现方式3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Mycallable mycallable = new Mycallable();
FutureTask<Object> task = new FutureTask<>(mycallable);
Thread th = new Thread(task);
th.setName("线程1");
th.start();
//获取子线程执行完后的返回结果
Object o=task.get();
System.out.println(o);
}
}
class Mycallable implements Callable<Object>{
@Override
public Object call() throws Exception {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return 100;
}
}
/*
前两种方法,主线程中无法获取子线程执行的结果,因为线程中的run()方法,并没有返回值。
但是会有需求,需要得到子线程的结果值。
所以该方法实现多线程的一个优势:
线程执行完有返回值。
*/
举例使用线程实现方式3,获取子线程的执行结果。
public class 多线程的实现方式3举例 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//任务1:获取1-100的和
Mycallable1 mycallable1 = new Mycallable1(100);
FutureTask task = new FutureTask<>(mycallable1);
Thread th = new Thread(task);
th.start();
//获取子线程的执行结果
System.out.println(task.get());
//任务2:获取1-1000的和
Mycallable1 mycallable11 = new Mycallable1(1000);
FutureTask<Integer> task1 = new FutureTask<>(mycallable11);
Thread th1 = new Thread(task1);
th1.start();
System.out.println(task1.get());
}
}
class Mycallable1 implements Callable<Integer> {
private int num;
public Mycallable1(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
//需求:计算累加和
int sum=0;
for (int i = 0; i <= num; i++) {
sum+=i;
}
return sum;
}
}
/*
实现callable接口,相较于实现runable接口,方法具有返回值,并且可以抛出异常
执行callable方式,需要FutureTask实现类的支持,用于接收运算结果。
*/
三、多线程中常用操作
1.获取和设置线程对象名称
- public final String getName()获取线程名称,存在默认值
- public final void setName(String name)给线程设置名字
- public static Thread currentThread()获取当前正在执行的线程对象
public class 获取和设置线程对象名称 {
public static void main(String[] args) {
//3.获取主线程的名称
Thread thread = Thread.currentThread();//Thread.currentThread()获取当前正在执行的线程对象
System.out.println(thread.getName());//主线程默认名字为main
MyTread th = new MyTread();
//2.setName()给线程设置名字
th.setName("线程1");
th.start();
}
}
class MyTread extends Thread{
@Override
public void run() {
//该this代表线程对象
System.out.println(this);
for (int i = 0; i < 100; i++) {
//1.getName()获取线程名称,存在默认值
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
2.线程调度及获取和设置线程优先级
- 线程的两种调度模型
- 分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间片(平均分配)
- 抢占式调度模型:优先级高的线程相比较优先级低的线程,获取cpu的时间片相对多一些。(java是抢占式调度模型)
- public final int getPriority()获取线程的优先级,优先级范围是1-10,默认值为5,最低为1,最高为10。
- public final void setPriority(int newPriority),设置线程优先级,可使用数字直接设置
public class 线程调度模型及优先级设置 {
public static void main(String[] args) {
Mythread1 th1 = new Mythread1();
th1.setName("线程1");
//1.getPriority()获取线程的优先级,默认值为5
System.out.println(th1.getPriority());
Mythread1 th2 = new Mythread1();
th2.setName("线程2");
//setPriority(),设置线程优先级
//可以使用数字,或者以下方式
th2.setPriority(Thread.MAX_PRIORITY);
th1.start();
th2.start();
}
}
class Mythread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
3.线程休眠
线程休眠的实际应用: 比如,视频开始之前,播放60秒广告。
- public static void sleep(long millis) 线程休眠
public class 线程休眠 {
public static void main(String[] args) {
Mythread2 th1 = new Mythread2();
th1.setName("线程1");
th1.start();
}
}
class Mythread2 extends Thread{
@Override
public void run() {
//this.sleep(),出现编译器异常,不能抛异常只能抓
// (因为父类方法如果没有抛出异常,子类重写该方法不能抛出异常)
try {
this.sleep(1000*3);//单位是毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
4.守护线程
- 用户线程和守护线程
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。 - 用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。 - public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
public class 守护线程 {
public static void main(String[] args) {
//三个线程并发执行,主线程循环次数少,率先执行完。
//需求:主线程(用户线程)执行完,子线程(守护线程)就要死亡掉
Thread.currentThread().setName("刘备");
MyThread4 th1 = new MyThread4();
th1.setName("关羽");
MyThread4 th2 = new MyThread4();
th2.setName("张飞");
//将子线程设置为守护线程
//在线程开启之前setDaemon(true)
th1.setDaemon(true);
th2.setDaemon(true);
th1.start();
th2.start();
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class MyThread4 extends MyThread{
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
5.中断线程
- public final void stop(): 停止线程的运行
代码示例:
/*
需求:子线程执行3秒后,停止执行
*/
public class 停止线程 {
public static void main(String[] args) throws InterruptedException {
Mythread5 th1 = new Mythread5();
Mythread5 th2 = new Mythread5();
th1.setName("线程1");
th2.setName("线程2");
th1.start();
//先让主线程休息3秒,那么子线程会执行3秒
Thread.sleep(1000*3);
//跳入该行,子线程停止运行
th1.stop();
th2.start();
}
}
class Mythread5 extends Thread{
@Override
public void run() {
//停止线程的方法this.stop();
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
- public void interrupt():中断线程,当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
代码示例:
public class 中断线程 {
public static void main(String[] args) throws InterruptedException {
Mythread6 th1 = new Mythread6();
th1.setName("线程1");
th1.start();
//主线程休息两秒,即子线程执行两秒,(先休息两秒)
Thread.sleep(1000*2);
//接着唤醒线程1,即线程1只休息了2秒
th1.interrupt();
}
}
class Mythread6 extends Thread {
@Override
public void run() {
try {
//线程休眠10秒,让线程处于一种阻塞的状态
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
6.线程礼让
- public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
代码示例:
public class 线程礼让 {
public static void main(String[] args) {
Mythread8 th1 = new Mythread8();
Mythread8 th2 = new Mythread8();
th1.setName("大哥");
th2.setName("小弟");
th1.start();
th2.start();
}
}
class Mythread8 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
//线程礼让:理想状态是两个线程交替执行
Thread.yield();
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
效果图:
7.线程加入
- public final void join() 等待该线程执行完毕以后,其他线程才能再次执行。
/*
需求:将三个并发执行的线程,改为串行
*/
public class 线程加入 {
public static void main(String[] args) throws InterruptedException {
//三个子线程并发执行
Mythread7 th1 = new Mythread7();
Mythread7 th2 = new Mythread7();
Mythread7 th3 = new Mythread7();
th1.setName("大哥");
th2.setName("二弟");
th3.setName("三弟");
//join()设置线程加入,可以让多个线程从并发执行,变为串行
//注意代码书写顺序,是在开启执行之后加入
th1.start();
th1.join();
th2.start();
th2.join();
th3.start();
th3.join();
}
}
class Mythread7 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
本文地址:https://blog.csdn.net/m0_46988935/article/details/112847702