二十章多线程

概念

有很多工作是可以同时完成的,这种思想放在Java中被称为并发,并发完成每一件事被称为线程。

程序员可以在程序中执行多个线程,每一个线程完成一个功能//与其他线程并发执行,这种机制被称为多线程,并不算所有编程语言都支持多线程。

创建线程

继承Thread类和实现Runnable接口两种方法

继承Thread类

是Java.long包下的一个类,在这个类中实例化对象代表线程,程序员启动一个新线程需要建立一个实例。Thread类常用的两种构造方法如下:

public Thread():创建一个新的线程对象

public Thread(String threadName):创建一个名为threadName的线程对象

继承Thread类创建一个新的线程的语法如下:

public class ThreadTest extends Thread{

}

完成线程真实代码的功能放在run()方法,当继承Thread类后,就可以在该线程中覆盖run()方法,将实现线程功能的代码写入run()方法中,调用run()方法。

Thread对象需要一个任务来执行,任务是指线程在启动时执行的工作,这个代码写在了run()方法中,语法格式如下:

public void run(){

如果start()方法调用一个已经启动的线程,系统将抛出IllegalThreadStateException异常

例题20.1

package lx;
 
public class Demo20_1 extends Thread {
	public void run(){
		for(int i=0;i<=10;i++) {
			System.out.println(i+"");
		}
	}
	public static void main(String[] args) {
		Demo20_1 th=new Demo20_1();
		th.start();
		
	}
 
}
 

结果

 

实现Runnable接口

线程都是通过扩展 Thread 类来创建的,如果需要继承其他类(非 Thread类),而且还要使当前类实现多线程,那么可以通过 Runnable 接口来实现。实现 Runnable 接口的语法如下:

public class Thread extends Object implements Runnable{
}

 实质上 Thread 类实现了 Runnable 接口,其中的run()方法正是对 Runnable 接口中的 run()方法的具体实现

实现 Runnable 接口的程序会创建一个 Thread 对象,并将 Runnable 对象与 Thread 对象相关联。

Thread 类中有两个构造方法:

public Thread(Runnable target)。

public Thread(Runnable target,String name)。

这两个构造方法的参数中都存在 Runnable 实例

使用 Runnable 接口启动新的线程的步骤如下:

建立 Runnable 对象。

使用参数为 Runnable 对象的构造方法创建 Thread 实例。

调用 start()方法启动线程。

 

 线程最引人注目的部分应该是与 Swing 相结合创建GUI程序

例题20.2

package lx;
 
import java.awt.Container;
 
import javax.swing.JFrame;
import javax.swing.*;
 
public class Demo20_2 extends JFrame {
	int c=0;//图标横坐标
	public Demo20_2() {
		setBounds(300,200,250,100);//绝对定位窗体大小和位置
		Container con=getContentPane();//主容器
		con.setLayout(null);//使窗体不使用任何布局管理器
		
		Icon img=new ImageIcon("src/1.gif");//图标对象
		JLabel jl=new JLabel(img);//显示图标的标签
		jl.setBounds(10, 10, 200, 50);//设置标签的位置和大小
		Thread t=new Thread() {//定义匿名线程对象
			public void run() {
				while(true) {
					jl.setBounds(c, 10, 200, 50);//将标签的横坐标用线程表示
					try {
						Thread.sleep(500);//使线程休眠500毫秒
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					c+=4;//使横坐标每次增加4
					if(c>=120) {
						c=10;//当图标到达标签最右边时,使其回到标签最左边
					}
				}
			}
		};
		t.start();//启动线程
		con.add(jl);//将标签添加到容器中
		setVisible(true);//使窗体可见
		setDefaultCloseOperation(EXIT_ON_CLOSE);//设置窗体的关闭方式
	}
	public static void main(String[] args) {
		new Demo20_2();
	}
 
}

结果

 

 

为了使图标具有滚动功能,需要在类的构造方法中创建 Thread 实例。

在创建该实例的同时需要 Runnable 对象作为 Thread 类构造方法的参数,然后使用内部类形式实现 run()方法。

在 run()方法中主要循环图标的横坐标位置,

当图标横坐标到达标签的最右方时,再次将图标的横坐标置于图标滚动的初始位置。
启动一个新的线程,不是直接调用 Thread 子类对象的 run()方法,而是调用 Thread 子类的 start()方法,Thread 类的 start()方法产生一个新的线程,该线程运行 Thread 子类的 run()方法。

 

 线程的生命周期

线程具有生命周期,其中包含 7 种状态,分别为出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。

出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前线程都处于出生状态.

当用户调用 start()方法后,线程处于就绪状态 

当线程得到系统资源后就进入运行状态。

旦线程进入可执行状态,它会在就绪与运行状态下转换,同时也有可能进入等待、休眠、阻塞或死亡状态。

虽然多线程看起来像同时执行,但事实上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生线程是同时进行的假象。

操作线程的方法

操作线程有很多方法,这些方法可以使线程从某一种状态过波到另一种状态。

 线程的休眠

一种能控制线程行为的方法是调用 seep()方法,sleep()方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位。slep()方法的语法如下:

try{
Thread.sleep(2000);

}catch(InterruptedException e){
e.printStackTrace();

}

上述代码会使线程在 2 秒之内不会进入就绪状态。

由于 sleep()方法的执行有可能抛InterrupledException 异常,所以将 sleep()方法的调用放在 try-catch 块中。

不能保证线程醒来后进入运行状态,只能保证它进入就绪状态。
 

 例题20.3

package lx;
 
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
 
import javax.swing.JFrame;
 
public class Demo20_3 extends JFrame {
	private static Color[]color= {//定义颜色数组
			Color.BLACK,Color.BLUE,Color.CYAN,Color.GREEN,Color.ORANGE,Color.YELLOW
			,Color.RED,Color.PINK,Color.LIGHT_GRAY
	};
	private static  final Random rand=new Random();
	//创建随机对象
	private static Color getC() {//获取随机颜色值的方法
		return color[rand.nextInt(color.length)];
	}
	public Demo20_3() {
		Thread t=new Thread(new Runnable() {
			//创建匿名线程对象
			int x=70;
			//定义初始坐标
			int y=50;
			public void run() {
				while(true) {//无限循环
					try {
						Thread.sleep(100);//线程休眠0.1秒
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				Graphics g= getGraphics();//获取组件绘图上下文对象
				g.setColor(getC());//设置绘图颜色
				g.drawLine(x, y, 200, y++);//绘制直线并递增垂直坐标
				if(y>=100) {
					y=50;
				}
			}
		}
			});
		t.start();//启动线程
	}
	
	public static void main(String[] args) {
		init(new Demo20_3(),300,100);
	}
	
	public static void init(JFrame f,int w,int h) {//初始化程序界面的方法
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.setSize(w, h);
		f.setVisible(true);
	}
}

结果

 

在本实例中定义了 getC()方法,该方法用于随机产生 Color 类型的对象并且在产生线程的匿名内部类中使用 getGraphics()方法获取 Graphics 对象,使用该对象调用 setColor()方法为图形设置颜色。调用 drawLine()方法绘制一条线段,同时线段会根据纵坐标的变化自动调整。
 

线程的加入

如果当前某程序为多线程程序,假如存在一个线程 A,现在需要插入线程 B,并要求线程 B 先执行完毕,然后再继续执行线程 A,此时可以使用 Thread 类中的 join()方法来完成。

例题20.4

package lx;
 
import java.awt.BorderLayout;
 
import javax.swing.*;
 
public class Demo20_4 extends JFrame{
	private Thread A;//定义两个线程
	private Thread B;
	private JProgressBar Bar=new JProgressBar();//定义两个进度条组件
	private JProgressBar Bar2=new JProgressBar();
	
	public static void main(String[] args) {
		Demo20_4 Text=new Demo20_4();
		Text.setVisible(true);
 
	}
	public Demo20_4() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(200,200,200,100);
		getContentPane().add(Bar,BorderLayout.NORTH);//将进度条设置在窗体最北面
		getContentPane().add(Bar2,BorderLayout.SOUTH);//将进度条设置在窗体最南面
		Bar.setStringPainted(true);//设置进度条显示数字字符
		Bar2.setStringPainted(true);
		A=new Thread(new Runnable() {//使用匿名内部类形式初始化Thread实例
			int c=0;
			public void run() {//重写润()方法
				while(true) {
					Bar.setValue(c++);//设置进度条当前值
					try {
						
						Thread.sleep(100);//让A线程休眠100毫秒
						
						B.join();//让/B调用join()方法
						if(c==30)//设置当A线程走到了30,B线程才启动
							B.start();//启动B线程
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		});
		A.start();//启动A线程
		B=new Thread(new Runnable() {
			int c=0;
			public void run() {
				while(true) {
					Bar2.setValue(++c);//设置进度条当前值
					try {
						Thread.sleep(100);//让B线程休眠100毫秒
						
					}catch(InterruptedException e) {
						e.printStackTrace();
						
				}
					if(c==100)//当c变量增长为100时
						break;			//跳出循环	
				}
			}
		});
		
		}
	}

结果

 

线程的中断

如果线程是因为使用了 sleep()或 wait()方法进入了就绪状态,可以使用 Thread 类中 interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出 InterruptedException 异常,用户可以在处理该异常时完成线程的中断业务处理,如终止 while 循环。
例题20.5

package lx;
 
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
 
public class Demo20_5 extends JFrame{
	
	public Demo20_5(){
		JProgressBar Bar=new JProgressBar();//创建进度条
		getContentPane().add(Bar,BorderLayout.NORTH);//将进度条设置在窗体最北面
		JButton b=new JButton("停止");
		getContentPane().add(b,BorderLayout.SOUTH);//将进度条设置在窗体最南面
		Bar.setStringPainted(true);//设置进度条显示数字字符
		Thread t=new Thread(new Runnable() {
			int c=0;
			public void run() {
				while(true) {
					Bar.setValue(++c);//设置进度条当前值
					try {
						Thread.sleep(100);//让A线程休眠100毫秒
					}catch(InterruptedException e) {//捕捉InterruptedException异常
						System.out.println("当前线程程序被中断");
						break;
					}
				}
			}
			
		});
		b.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				t.interrupt();//中断线程
			}
		});
		t.start();//启动线程
	}
	public static void init(JFrame frame,int w,int h) {
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(w, h);
		frame.setVisible(true);
	}
	public static void main(String[] args) {
		init(new Demo20_5(),100,100);
 
	}
 
}

结果

 

线程的礼让

Thread 类中提供了一种礼让方法,使用 yield()方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。yicld()方法使具有同样优先级的线程有进入可执行状态的机会,在当前线程放弃执行权时会再度回到就绪状态。

线程的优先级

每个线程都具有各自的优先级,线程的优先级可以表明在程序中该线程的重要性。

线程的优先级可以使用 setPriority()方法调整,如果使用该方法设置的优先级不在 1~10,将产生IllegalArgumentException 异常。

例题20.6
package lx;
 
public class Demo20_6 implements Runnable{
	String name;
	public 	Demo20_6(String name) {
		this.name=name;
	}
		public void run() {
			String tmp="";
			for(int i=0;i<50000;i++) {//完成5万次字符串拼接
				tmp+=i;
			}
			System.out.println(name+"线程完成任务");
		}
	
	
	public static void main(String[] args) {
	Thread a=new Thread(new Demo20_6("A"));//A线程优先级最小
	a.setPriority(1);
	Thread b=new Thread(new Demo20_6("B"));
	b.setPriority(3);
	Thread c=new Thread(new Demo20_6("C"));
	c.setPriority(7);
	Thread d=new Thread(new Demo20_6("D"));//D线程优先级最大
	d.setPriority(10);
	a.start();
	b.start();
	c.start();
	d.start();
	//线程的执行顺序由CPU决定,所有可能不一定按优先级排序
	}
 
}

结果

 

由于线程的执行顺序是由 CPU 决定的,即使线程设定了优先级也是作为 CPU 的参考数据,所以真实的运行结果可能并不一定按照优先级排序

线程同步
在单线程程序中,每次只能做一件事情,后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,如两个人同时说话、两个人同时过同-个独木桥等。所以,在多线程编程中需要防止这些资源访问的冲突。Java 提供了线程同步的机制来防止资源访问的冲突。

 线程安全
实际开发中,使用多线程程序的情况很多,以火车站售票系统为例,在代码中判断当前票数是否大于 0,如果大于 0 则执行将该票出售给乘客的功能,但当两个线程同时访问这段代码时(假如这时只剩下一张票),第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得出票数大于 0 的结论,于是它也执行售出操作,这样就会产生负数。

所以,在编写多线程程序时,应该考虑到线程安全问题。实质上线程安全问题来源于两个线程同时存取单一对象的数据。

实例
 

package lx;
 
public class Demo20_6_1 implements Runnable {
	int n=10;//设置当前总票数
	public void run() {
		while(true) {//设置无限循环
			if(n>0) {//判断当前票数是否大于 0
				try {
					Thread.sleep(100);		//使当前线程休眠 100毫秒	
					}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"————票数"+n--);//票数减1
			}	
		}
	}
	
	
	public static void main(String[] args) {
		Demo20_6_1 t=new Demo20_6_1();//实例化类对象
		Thread tA=new Thread(t,"线程一");//以该类对象分别实例化 4 个线程
		Thread tB=new Thread(t,"线程二");
		Thread tC=new Thread(t,"线程三");
		Thread tD=new Thread(t,"线程四");
		tA.start();//分别启动线程
		tB.start();
		tC.start();
		tD.start();
		
		
	}
 
}

结果 

 

线程同步机制
该如何解决资源共享的问题呢? 所有解决多线程资源冲突问题的方法基本上都是采用给定时间只允许一个线程访问共享资源的方法,这时就需要给共享资源上一道锁。

同步块
Java 中提供了同步机制,可以有效地防止资源冲突。同步机制使用 synchronized 关键字,使用该关键字包含的代码块称为同步块,也称为临界区,语法如下:

synchronized (Object){

}

 通常将共享资源的操作放置在 synchronized 定义的区域内,这样当其他线程获取到这个锁时,就必须等待锁被释放后才可以进入该区域。

例题20.7

package lx;
 
public class Demo20_6_1 implements Runnable {
	int n=10;//设置当前总票数
	public void run() {
		while(true) {//设置无限循环
			synchronized (this) {
				if(n>0) {//判断当前票数是否大于 0
					try {
						Thread.sleep(100);		//使当前线程休眠 100毫秒	
						}
				catch(InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"————票数"+n--);//票数减1
				}	
			}
			
		}
	}
	
	
	public static void main(String[] args) {
		Demo20_6_1 t=new Demo20_6_1();//实例化类对象
		Thread tA=new Thread(t,"线程一");//以该类对象分别实例化 4 个线程
		Thread tB=new Thread(t,"线程二");
		Thread tC=new Thread(t,"线程三");
		Thread tD=new Thread(t,"线程四");
		tA.start();//分别启动线程
		tB.start();
		tC.start();
		tD.start();
		
		
	}
 
}

结果 

 

从这个结果可以看出,打印到最后票数没有出现负数,这是因为将共享资源放置在了同步块中,不管程序如何运行都不会出现负数。

同步方法

同步方法就是在方法前面用 synchronized 关键字修饰的方法,其语法如下:

synchronized void f(){

}

 当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为 synchronized,否则就会出错。

修改20.7的代码如下:
package lx;
 
public class Demo20_6_1 implements Runnable {
	int n=10;//设置当前总票数
	
	public  synchronized void du() {
		if(n>0) {//判断当前票数是否大于 0
			try {
				Thread.sleep(100);		//使当前线程休眠 100毫秒	
				}
		catch(InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"————票数"+n--);//票数减1
		}	
	}
	public void run() {
		while(true) {//设置无限循环
			du();
			
		}
	}
	
	
	public static void main(String[] args) {
		Demo20_6_1 t=new Demo20_6_1();//实例化类对象
		Thread tA=new Thread(t,"线程一");//以该类对象分别实例化 4 个线程
		Thread tB=new Thread(t,"线程二");
		Thread tC=new Thread(t,"线程三");
		Thread tD=new Thread(t,"线程四");
		tA.start();//分别启动线程
		tB.start();
		tC.start();
		tD.start();
		
		
	}
 
}

结果

 

执行结果一样 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/195268.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

phpstudy安装redis

Redis 是一个开源的高性能键值存储数据库&#xff0c;广泛用于缓存、消息队列、会话管理和实时数据分析等应用场景。 使用 PHP Redis 扩展&#xff0c;你可以在 PHP 代码中使用一系列的函数来连接到 Redis 服务器&#xff0c;并执行各种操作&#xff0c;如设置和获取键值对、操…

CLIPTokenizer.from_pretrained本地加载

以"openai/clip-vit-large-patch14"为例&#xff0c;原代码为&#xff1a; self.tokenizer CLIPTokenizer.from_pretrained(“openai/clip-vit-large-patch14”) self.transformer CLIPTextModel.from_pretrained(“openai/clip-vit-large-patch14”) 但我连不到外…

java学习part15单例模式

107-面向对象(高级)-单例设计模式与main()的理解_哔哩哔哩_bilibili 1.单例 就是说在某些开发场景中&#xff0c;某个类只要有一个对象就足够使用了&#xff0c;不需要重复创建。 &#xff08;理解&#xff1a;比如说是数据库对象&#xff0c;使用时创建一个可以处理所有的数…

【网络安全】-常见的网站攻击方式详解

文章目录 介绍1. SQL 注入攻击攻击原理攻击目的防范措施 2. 跨站脚本攻击&#xff08;XSS&#xff09;攻击原理攻击目的防范措施 3. CSRF 攻击攻击原理攻击目的防范措施 4. 文件上传漏洞攻击原理攻击目的防范措施 5. 点击劫持攻击原理攻击目的防范措施 结论 介绍 在数字时代&a…

第13周 预习、实验与作业:Java网络编程

目录 1 课前问题列表 1.编写一个网络程序&#xff0c;为了与其他网络程序通信&#xff0c;至少要知道对方的什么信息&#xff1f; 2.TCP与UDP协议有什么不同的呢&#xff1f;什么时候该选择哪种协议&#xff1f;HTTP使用的是TCP还是UDP&#xff1f;不重要的短信息传送之类的功能…

GaussDB数据库SQL系列-触发器

目录 一、前言 二、触发器概念 三、GaussDB数据库中的触发器 1、语法格式 2、创建步骤 3、注意事项 4、附&#xff1a;表和视图上支持的触发器种类 四、GaussDB数据库中的示例 示例一、在GaussDB数据库中创建一个触发器&#xff0c;以便在插入新记录时自动将记录的创建…

选择aspera替代方案的理由,有哪些aspera替代方案

Aspera是一种快速数据传输协议和工具&#xff0c;它使用高效的UDP协议和复杂的流控制算法来实现可靠、高速的数据传输。该协议和工具广泛应用于媒体和娱乐行业、金融服务和其他需要大规模数据传输的领域。然而&#xff0c;Aspera的高昂价格和限制性许可证可能使得某些企业寻找替…

C#中openFileDialog控件的使用方法

目录 一、OpenFileDialog基本属性 二、使用 OpenFile 从筛选的选择中打开文件 1.示例源码 2.生成效果 3. 其它示例 三、使用 StreamReader 以流的形式读取文件 1.示例源码 2.生成效果 四、一种新颖的Windows窗体应用文件设计方法 在C#中&#xff0c;OpenFileDialog控件…

萤石云接口调用

获取appKey和secret 登录后在开发者服务-我的应用中获取 根据appKey和secret获取accessToken 参考官方文档&#xff1a;文档概述 萤石开放平台API文档 # 获取accessToken url_accessToken"https://open.ys7.com/api/lapp/token/get" data {"appKey": &…

云原生CI/CD流水线发布

文章目录 前言k8s组件与操作流程k8s组件创建pod k8s代码&&打包k8s yamldeploymentservicek8s volumesdemo CIgitlabCI runner CD配置git repository安装argo创建argo cd的配置yamlargocd和helm结合argocd hookargocd 发布 RBACoperatorhelmprometheus && grafn…

家政预约服务管理系统,轻松搭建专属家政小程序

家政预约服务管理系统&#xff0c;轻松搭建专属家政小程序app&#xff1b; 家政服务app开发架构包括&#xff1a; 1. 后台管理端&#xff1a;全面管理家政服务、门店、员工、阿姨信息、订单及优惠促销等数据&#xff0c;并进行统计分析。 2. 门店端&#xff1a;助力各门店及员工…

2023.11.27【读书笔记】|医疗科技创新流程(前言)

目录 注重价值关键要素如何解决价值问题&#xff1f;注重三个关键点价值探索价值预测价值定位 中国视角背景挑战战术 洞察过程发现需求发现需求筛选 发明概念产生概念选择 发挥战略发展商业计划 注重价值 在美国&#xff0c;医疗费用的增长率已经多年超过GDP增长率&#xff1b…

不用render_template函数,把html代码放在py文件里,不用单独写html文件

3.猜拳游戏&#xff1a;石头、剪刀、布的游戏 ##不用render_template函数&#xff0c;把html代码放在py文件里&#xff0c;不用单独写html文件 from flask import Flask, request import randomapp Flask(__name__)app.route(/) def index():#下面form标签虽然放在注释里&…

基于JavaWeb+SSM+Vue校园综合服务小程序系统的设计和实现

基于JavaWebSSMVue校园综合服务小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 摘 要 I Abstract II 第一章 绪 论 1 1.1选题背景 2 1.2研究现状 3 1.3研究内容 …

30.0/集合/ArrayList/LinkedList

目录 30.1什么是集合? 30.1.2为什么使用集合 30.1.3自己创建一个集合类 30.1.3 集合框架有哪些? 30.1.2使用ArrayList集合 30.2增加元素 30.3查询的方法 30.4删除 30.5 修改 30.6泛型 30.1什么是集合? 我们之前讲过数组&#xff0c;数组中它也可以存放多个元素。集合…

05_属性描述符

05_属性描述符 文章目录 05_属性描述符一、属性描述符是什么&#xff1f;二、属性描述符①&#xff1a;查看属性描述②&#xff1a;设置属性描述符③&#xff1a;案例01.代码实现02.代码实现&#xff08;优化&#xff09; 一、属性描述符是什么&#xff1f; 属性描述符的结构 在…

值得收藏的 6 个顶级 Mac 数据恢复软件榜单

对于 Mac 用户来说&#xff0c;丢失重要数据可能是一场真正的噩梦。无论是意外删除、系统崩溃还是狡猾的恶意软件&#xff0c;后果都可能是毁灭性的。幸运的是&#xff0c;Mac 数据恢复软件带来了一线希望。这些工具旨在帮助您轻松恢复珍贵的文件&#xff0c;无论是什么原因导致…

入侵redis之准备---Centos7上面部署redis

入侵redis之准备—Centos7上面部署redis 编写这个部署redis&#xff0c;只是为了另一个文章入侵redis做准备&#xff0c;网上还有好多类似的文章&#xff0c;这个单纯的就是部署安装&#xff0c;并简单的测试使用以下 关联其他文章 [1]VMware上面安装部署centos7镜像系统【详细…

knife4j集合化postman

knife4j集合化postman 01 knife4j的介绍 基于 JavaMVC的集成框架swagger的进一步强化&#xff0c;在原有通过注释就能生成文档的前身swagger-bootstrap-ui之上&#xff0c;增加了postman的测试功能&#xff0c;优化了文档的UI界面&#xff0c;在测试api接口的方面有了极大的进…

C 语言-循环嵌套-函数

C 语言 - 循环嵌套、函数 1. 循环嵌套 1.1 作用 循环 套 循环。 1.2 使用 需求1&#xff1a; 打印以下图形&#xff1a; * * * * * * * * * * * * * * * *代码&#xff1a; 1、使用循环打印 #include <stdio.h> int main(int argc, char const *argv[]) {for (int i…
最新文章