5.多线程之JUC并发编程2

news2024/11/15 19:51:25

1.CompletableFuture异步回调 像ajax,未来再得到执行结果,想服务器不分先后顺序执行,可以用异步回调

     //调用的函数没有返回值的
          CompletableFuture<Void> future=CompletableFuture.runAsync(()->{
                           TimeUnit.SECONDS.sleep(2);
                            sout(Thread.currentThread.getName+"async=>Void")
           });
          sout("111");
            future.get();//获取结果,没有结果一直阻塞
    //调用的函数有返回值的 
  CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
                           TimeUnit.SECONDS.sleep(2);
                            sout(Thread.currentThread.getName+"async=>Void")
                         int i=10/0;//模拟错误
                             return 1024
           });
         //BiConsumer是需要插入2个参数的函数式接口
        future.whenComplete((t,u)->{
                 sout(t)
                 sout(u)   
        }).exceptionally((e)->{ sout(e.getMessage()
             return 23;
        )}).get().sout  //结果输出一下
public class CompeleteF {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future=CompletableFuture.runAsync(()->{
            System.out.println("aaa");

        });
        System.out.println(future.get());

        CompletableFuture<Integer> future1=CompletableFuture.supplyAsync(()->{

            return 10;
        });
        System.out.println("-------------返回结果的----------------");
        System.out.println(future1.whenComplete((s, a) -> {
//            System.out.println(s);//调用后返回的结果
//            System.out.println(a); //null
            int a1=10/0;
        }).exceptionally((e) -> {
            System.out.println("异常返回结果"+e.getMessage());

            return 11;
        }).get());
    }
}

2.JMM

  1. 面试:对 Volatile的理解
    答: Volatile是jvm通过轻量级的同步机制,比sychronized更轻
    1.保证可见性 2.不保证原子性 3.禁止指令重排
  2. 什么是JMM?
    答:java内存模型,是一种规定,不存在的东西
    一些同步的约定
    1.线程加锁前,必须读取主存中的最新值到工作工作内存中
    2.线程解锁前,必须把共享变量"立即"刷回主存
    3.加锁和解锁是同一把锁
    图 JMM解决的问题 (2个执行8组操作,会产生线程变量不同步的问题,由于共用一个变量)volatile可以解决(程序不知道主内存的值被修改,主线程通知B线程
    某个变量可见[被修改])
  3. 代码验证线程间的可见性 不保证原子性(要么同时成功,要么同时失败)
    //可见性
    //图JMM

请添加图片描述

  private volatile int num=0;  //不加volatile来测试
   new Thread(()->{
         while(num==0){ //没有volatile,不知道变量的值被修改,一直死循环

        }
 }).start();
 TimeUnit.SECONDS.sleep(2);
 num=1; 

//不保证原子性,怎么不加synchronized或lock怎么实现
//使用原子类的类型,底层使用CAS cpu并发原理
//打开 target文件夹我们写的volatile class字节码文件的文件夹(右键然后optimize imports)
//cmd窗口 反编译 javap -c Demo.class可以看到字节码 图volatile底层代码分析

请添加图片描述

//多线程操作,比锁高效很多倍
  private volatile static AutomicInteger num=new AutomicInteger();
   public static void add(){
                  num.getAndIncrement();
   } 

  public class AtmoticDemo {
     static volatile int num= 0;
     public  static void add(){
        num++;
    }
    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){ //main和gc线程,不停的判断执行完的线程数量,如果是>2main线程让步给其他线程执行
             //直到线程数量为2
           Thread.yield();

        }
        System.out.println(num);


    }
}
------改进后-----
public class AtmoticDemo1 {
     static volatile AtomicInteger num= new AtomicInteger();
    public  static void add(){
        num.getAndIncrement();
    }
    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){ //main和gc线程,不停的判断执行完的线程数量,如果是>2main线程让步给其他线程执行
             //直到线程数量为2
           Thread.yield();

        }
        System.out.println(num);


    }
}

//Unsafe类是很特殊的存在,里面都是native方法

  1. 可以禁止指令重排(程序不是按我们写的那样去执行,而是通过系统的优化后才执行)
    1. 保证特定的操作的执行顺序
    2. 报错模型变量内存可见性
    图 volatile防止指令重排底层原理
    //一个线程如果两个变量没有依赖关系可以指令重排 如:
    //这个是我们写的代码
        int a=10;
        int b =20;
       a=30;
     
     //操作系统进行指令重排进行优化可能结果是
   int a=10;
      a=30;
    int b =20;
        b=40;
     //也可能是....多种情况的,会根据实际情况优化
     int b =20;
      int a=10;
      a=30;
        b=40;
      //但是多线程不保证指令重排, 1000万次可能只出现1次 大厂的海量大数据才可能遇到这种情况

3.单例模式(volatile使用得最多)

1.饿汉式的懒汉式DCL(double check双重检测)多线程会指令重排
//普通懒汉会导致资源浪费,没有使用的资源会大量闲置

public class Hungry {
   //饿汉式模拟 这个类有大量数据
   private byte[] data1 = new byte[1024];
   private byte[] data2 = new byte[1024];
   private byte[] data3 = new byte[1024];
   private byte[] data4 = new byte[1024];
   private Hungry() {
   }
   private final static Hungry hungry = new Hungry();
   public static Hungry getInstance() {
         return hungry;
   }
}

//DCL方式

 public class SingleonDoubleCheck {

    private SingleonDoubleCheck(){
    
    }
    private  static volatile   SingleonDoubleCheck instance;
    public static SingleonDoubleCheck getInstance(){
        if(instance==null){  //可能指令重排
            synchronized (SingleonDoubleCheck.class){//可能指令重排
                if(instance==null){//可能指令重排
                     instance=new SingleonDoubleCheck();
                }
            }
        }
        return instance;
    }

    
}

class test2{
    public static void main(String[] args) {

        CopyOnWriteArrayList list=new CopyOnWriteArrayList();

        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
                SingleonDoubleCheck instance = SingleonDoubleCheck.getInstance();
                list.add(instance.hashCode());
            }).start();
        }

        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list);



    }
}

2.静态内部类懒汉,使用反射可以破坏单例(可以写外挂,破坏java写的游戏,可以以后看看cf的源代码) 但是对枚举行不通

//使用jad反编译类cmd的 jad -s java Demo.class
//枚举是class类继承Enum类,枚举不会被破坏,没有无参构造

 public class Holder { //既保证线程安全性,又保证懒加载,因为内部类不会被优先创建,调用时才创建
    private Holder() {

    }
    public static Holder getInstance() {
      return InnerClass.holder;
    }
    private static class InnerClass {
        private static final Holder holder = new Holder();
    }
}
class test02{
    public static void main(String[] args) {
        System.out.println(Holder.getInstance().hashCode());
        System.out.println(Holder.getInstance().hashCode());
    }

}    

//但是反射可以无视private方法 ,我们可以加个flag判断对象是否为空,但是恶意者直接使用newInstance搞破坏

4.CAS(compareAndSwap) 比较并交换 计算机并发原理(如果达到期望值,则更新值,否则不更新)
atom=new AtomicInteger(2020);
atom.compareAndSet(2020,2021);//可以修改
atom.compareAndSet(2020,2021);//不可以修改
//java无法操作内存,通过native方法调用c++
atom.getAndIncrement(); //unsafe底层原理,向得到内存地址的值,然后如果和原来值相等,设置期望值,然后设置值+1 ,如果不是期望值就一直循环(使用自旋锁)
//缺点 1.循环会耗时 2.一次性只能保证一个共享变量的原子性 3.产生ABA问题
5.CAS ABA问题(狸猫换太子,就是原来是A改为B,再改回A) 一个线程有期望值,然后修改为其他值,再修改回来,另外一个线程不知情,以为是原来的值,修改了期望值

   atom.compareAndSet(20,21);//一个线程修改了值
    atom.compareAndSet(21,23);
    atom.compareAndSet(23,20);//更改回来原来的值,其他线程并不知道他被改变了
   //另外一个线程
  atom.compareAndSet(20,21);  //修改了值,相当于线程不安全(后面加个版本号可以解决)

//CAS他的底层代码 自旋锁

 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1); //valueOffset是默认的,最后一个是增加的值
    }
     //unsafe类
       public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); //通过 对象和地址偏移量得到他对应的volatile其他内存可见性的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // 对值+1,如果没有期望值 就一直循环等待得到这个值

        return var5;
    }

6.原子引用(带版本的CAS) 解决ABA问题需要如果修改一次版本号就+1
//如果泛型是包装类,需要注意对象的引用问题,正常使用对象来原子操作
//多线程效率更快

   AtomicStampedReference<Integer> atomic =new     AtomicStampedReference<>(2020,1);//第二个参数搜索版本号
  int stamp=atomic.getStamp();
      sleep(2);
   
   int stamp= atomic.getStamp();//得到版本号
        atomic.compareAndSet(2020,2022,atomic.getStamp(),atomic.getStamp()+1);//版本号+1,代表现在2020这个值的版本号为 不是原来的,其他的线程不能修改
public class ABADemo {
    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(100,1);
    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("T1 stamp 01=>"+stamp);
// 暂停2秒钟,保证下面线程获得初始版本号
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101,
                    atomicStampedReference.getStamp()
                    ,
                    atomicStampedReference.getStamp()+1);
            System.out.println("T1 stamp 02=>"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp()+1);
            System.out.println("T1 stamp 03=>"+atomicStampedReference.getStamp());
        },"T1").start();
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("T2 stamp 01=>"+stamp);
// 暂停3秒钟,保证上面线程先执行
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100, 2019,
                    stamp, stamp + 1);
            System.out.println("T2 是否修改成功 =>"+ result);
            System.out.println("T2 最新stamp =>"+atomicStampedReference.getStamp());
            System.out.println("T2 当前的最新值 =>"+atomicStampedReference.getReference());
        },"T2").start();
    }
}
//执行结果
T1 stamp 01=>1
T2 stamp 01=>1
T1 stamp 02=>2
T1 stamp 03=>3
T2 是否修改成功 =>false
T2 最新stamp =>3
T2 当前的最新值 =>100

7.锁的分类

  1. 公平锁(不能插队),非公平锁(默认 synchronized或者lock都是.可以插队执行)
    ReentrantLock lock = new ReentrantLock(false); //非公平锁
  2. 可重入锁 拿到外面的锁 可以拿到里面的锁 而synchronized不可以,差别在效率上,结果一致 注意锁要配对,不可以两个lock 一个unlock,会导致死锁
  //普通synchronized
public class ReentrantLockDemo {
    public static void main(String[] args) throws Exception {
        Phone phone = new Phone();
// T1 线程在外层获取锁时,也会自动获取里面的锁
        new Thread(()->{
            phone.sendSMS();
        },"T1").start();
        new Thread(()->{
            phone.sendSMS();
        },"T2").start();
    }
}
 class Phone{
        public synchronized void sendSMS(){
            System.out.println(Thread.currentThread().getName()+" sendSMS");
            sendEmail();
        }
        public synchronized void sendEmail(){
            System.out.println(Thread.currentThread().getName()+" sendEmail");
        }
 }
 //使用可重入锁
public class ReentrantLockDemo1 {
   public static void main(String[] args) throws Exception {
       Phone1 phone = new Phone1();
// T1 线程在外层获取锁时,也会自动获取里面的锁
       new Thread(phone,"T1").start();
       new Thread(phone,"T2").start();
   }

}
class Phone1 implements Runnable{
   Lock lock = new ReentrantLock();
   @Override
   public void run() {
       get();
   }
   public void get(){
       lock.lock();
// lock.lock(); 锁必须匹配,如果两个锁,只有一个解锁就会失败
       try {
           System.out.println(Thread.currentThread().getName()+" get()");
           set();
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
// lock.lock();
       }
   }
   public void set(){
       lock.lock();
       try {
           System.out.println(Thread.currentThread().getName()+" set()");
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }
}

3.自旋锁 spinLock 不断得到锁,直到成功

          //自己写,自旋锁 加锁解锁
         AtomicReference<Thread> atomic =new  AtomicReference<>(); //默认为null,Integer默认为1
       public void myLock(){
            Thread thread=Thread.currentThread();
            while(!atomic.compareAndSet(null,thread)){  //如果为设置值成功则退出,如果失败不停执行设置值
   
             }
        }
          public void myUnLock(){//底层使用CAS效率高,发现也可以线程有序调用
	Thread thread=Thread.currentThread();
                             atomic.compareAndSet(thread,null);  //直接设置为null

           }
        ------完整代码-------
 class MyspinLock {

    AtomicReference<Thread> reference=new AtomicReference<>();

    public void lock(){
         Thread thread = Thread.currentThread();
         while (!reference.compareAndSet(null,thread)){ //是会根据整个线程对象来设置


         }
     }
     public void unlock(){
         Thread thread = Thread.currentThread();
             reference.compareAndSet(thread,null);
     }
    

}
public class usingLock {
    public static void main(String[] args) {
        Reasouce reasouce = new Reasouce();


        for (int i = 0; i <1000 ; i++) {
            new Thread(reasouce).start();
        }


    }
}
class Reasouce implements Runnable{
    int num=0;
    MyspinLock lock=new MyspinLock();

    @Override
    public  void run() {

            System.out.println(Thread.currentThread().getName());

            while (true){
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();
               try{
                   num++;

                   System.out.println(Thread.currentThread().getName()+",num:"+num );
               }finally {
                   lock.unlock();
               }

            }


    }
}

8.死锁 怎么排查死锁(jdk自带)
jps -l //查看java进程
jstack 11444//查看对应的进程号的信息 看最后一行的 waiting to lock 和 locked,两个线程有没有交叉

public class DeadLock {
    public static void main(String[] args) {
        MyDead myDead = new MyDead();

            new Thread(myDead).start();
        new Thread(myDead).start();


    }


}

class MyDead implements Runnable{
    int num=1;
    Object lock1=new Object();
    Object lock2=new Object();
    @Override
    public void run() {
        try {
            sell();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            produce();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void sell() throws InterruptedException {
        synchronized (lock1){
            TimeUnit.SECONDS.sleep(2);
            synchronized (lock2){
                System.out.println(num);
                num--;


            }
            System.out.println(Thread.currentThread().getName()+"解锁");
        }

    }
    public void produce() throws InterruptedException {
        synchronized (lock2){
            TimeUnit.SECONDS.sleep(10);
            synchronized (lock1){
                num++;
                System.out.println(num);

            }
        }
    }


}
       

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网安全:内网穿透详解

目录 内网穿透技术 内网穿透原理 实验环境 内网穿透项目 内网穿透&#xff1a;Ngrok 配置服务端 客户端配置 客户端生成后门&#xff0c;等待目标上线 内网穿透&#xff1a;Frp 客户端服务端建立连接 MSF生成后门&#xff0c;等待上线 内网穿透&#xff1a;Nps 服…

更新的NICE工具现在可以直接下载

大家好&#xff0c;才是真的好。 很多人还不知道什么是Nots中的NICE工具&#xff0c;它的全称是Notes Install Cleanup Executable&#xff0c;我主要使用该款工具在Windows上来卸载Notes标准客户机&#xff08;包括Domino Designer和Domino Admin&#xff09;。 补充一下&am…

优化3500倍,从70s到20ms的顶级调优,此方案人人可用

前言&#xff1a; 在40岁老架构师尼恩的读者社区&#xff08;50&#xff09;中&#xff0c;很多小伙伴拿不到offer&#xff0c;或者拿不到好的offer。 尼恩经常给大家 优化项目&#xff0c;优化简历&#xff0c;挖掘技术亮点。在指导简历的过程中&#xff0c; Java 调优是一项…

一文读懂数字孪生水利解决方案

如今&#xff0c;数字孪生作为一种创新的技术手段&#xff0c;正在被广泛应用于各个领域。在工业互联网、5G、边缘计算、AR等技术发展背景下&#xff0c;数字孪生技术与流域的融合正风生水起&#xff0c;数字流域建设备受瞩目&#xff0c;智慧水利依托物联网、大数据、人工智能…

怎么在eclipse中创建python项目

目录 方法一&#xff1a;借助Eclipse Marketplace安装PyDev插件 方法二&#xff1a;到官网下载手动安装插件 参考文件 方法一&#xff1a;借助Eclipse Marketplace安装PyDev插件 这可以通过Eclipse Marketplace完成。打开Eclipse&#xff0c;然后选择“Help” > “Eclips…

金鸣识别的表格分析技术揭秘

表格分析是指将图片中的表格区域分割出来&#xff0c;并识别出表格中的单元格和单元格中的内容。表格分析技术主要包括以下几个步骤&#xff1a; 1. 表格检测&#xff1a;通过图像处理技术&#xff0c;将图片中的表格区域分割出来。 2. 单元格分割&#xff1a;将表格中的每个单…

iphone尺寸大全

iPhone各机型的navbar和title的高度&#xff0c;宽高和在微信开发工具中的高度如下&#xff1a; 需要注意的是&#xff0c;pt是苹果设计稿中使用的单位&#xff0c;与px的换算关系为1pt等于2px。而在微信开发工具中&#xff0c;默认使用的是rpx&#xff0c;1rpx等于0.5px。因此…

Datax+DataX-Web分布式搭建

DataxDataX-Web分布式搭建 DataX简介 DataX 是一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 DataX本身作为数据同步框架&#xff0c;将不同数据源的同步…

餐企复苏破局!高效智能营销+服务如何打造?

随着数字化时代的飞速发展&#xff0c;越来越多的行业对企业运营服务模式的需求已逐渐趋向于“智能化”发展。特别是“后疫情时代”及O2O模式下的餐饮变革&#xff0c;餐饮行业的竞争已从餐食本身&#xff0c;发展为流量、服务、品牌效应的竞争&#xff0c;很多企业为了进一步促…

MySQL 8.0.29 instant DDL 数据腐化问题分析

前言Instant add or drop column的主线逻辑表定义的列顺序与row 存储列顺序阐述引入row版本的必要性数据腐化问题原因分析Bug重现与解析MySQL8.0.30修复方案 前言 DDL 相对于数据库的 DML 之类的其他操作&#xff0c;相对来说是比较耗时、相对重型的操作; 因此对业务的影比较严…

ASO优化之关于应用宝的关键词排名

应用宝是国内主要的安卓应用分发渠道之一&#xff0c;它的流量和影响力是非常大的。所以我们可以通过ASO优化&#xff0c;来提升应用宝关键词覆盖和排名&#xff0c;从而有利于应用可以获得稳定的自然下载量。 关键词覆盖要围绕元数据的优化&#xff08;包括应用名称&#xff…

ai智能写诗你了解吗?

在当今科技快速发展的时代&#xff0c;越来越多的人开始注重人工智能在日常生活中的应用。其中之一就是智能写诗软件&#xff0c;这种技术不仅可以为我们提供便捷的创作体验&#xff0c;还可以让我们轻松地享受到优美的诗歌韵律。那么&#xff0c;智能写诗软件怎么用呢&#xf…

服务端测试深度解析:如何保障后端稳定性

一、引言 在日益数字化的时代&#xff0c;软件稳定性和高可用性的保证对企业来说至关重要。在众多环节中&#xff0c;服务端作为整个系统的心脏&#xff0c;它的稳定性对用户体验和企业声誉影响尤为深远。因此&#xff0c;进行全面彻底的服务端测试具有重要意义。本文将深入讨…

真和思科有关?官方严令广东电信彻查网络故障!

下午好&#xff0c;我的网工朋友。 广东电信那事儿&#xff0c;大家都听说了吧&#xff0c;这闹的&#xff0c;行业内无人不晓了吧。 昨天晚上有聚餐&#xff0c;没来得及跟你们唠上这事儿&#xff0c;这会儿下班前&#xff0c;唠唠&#xff1f; 虽然昨晚已经恢复正常&#x…

工作4/5年,中高级测试工程师的我需要掌握什么?该往哪个方向发展?

毕业到公司4/5年&#xff0c;一般做软件测试至少是中级测试工程师了&#xff0c;好一些到高级测试工程师了。 我觉得对于中高级测试工程师的要求有几点 首先&#xff1a;你得有比较强的产品思维能力&#xff0c;你要知道产品的逻辑是什么&#xff0c;你要能站在用户的角度去思…

ArkTS语言HarmonyOS/OpenHarmony应用开发-message事件刷新卡片内容

开发过程 在卡片页面中可以通过postCardAction接口触发message事件拉起FormExtensionAbility&#xff0c;然后由FormExtensionAbility刷新卡片内容。 common&#xff1a;公共文件 通过点击button按钮&#xff0c;刷新卡片内容。代码示例&#xff1a; WidgetCard.ets let stor…

网页端兼容ie浏览器,强制用户以最高版本的ie进行页面渲染

1.针对浏览器&#xff0c;兼容不同ie版本写法 //IE9以及低于IE9版本 : <!--[if IE]><link rel"stylesheet" type"text/css" href"index.css" /> <![endif]-->//IE10或IE11: media all and (-ms-high-contrast: none), (-ms-h…

【JavaScript】实战训练小项目-WebAPI

JavaScript实战训练小项目 文章目录 JavaScript实战训练小项目 & WebAPI1. JS操作DOM树1.1 获得HTML控件/元素标签1.2 操纵控件1.2.1 获取属性值1.2.1 修改属性值 1.3 实现一个猜数字的功能 2. JQuery3. 简单计算器4. 聚合搜索5. 表白墙 JavaScript实战训练小项目 & We…

cuda编程入门07

程序优化技巧 程序解读 降低256倍&#xff0c;但是后面数组长度还是不知道的 对1万的元素在此降低一定倍数 初始加速比为9.58左右 这里没有volatie if (tid < 32) sdata[tid] sdata[tid 32];__syncthreads();在一个wrap内进行合并 sdata[tid] sdata[tid 32];sdata[ti…

【kali】使用VMware安装kali

目录 2、解压 3、安装 3.1 然后打开vm虚拟机&#xff0c;在文件菜单中选择打开 3.2 找到已解压的文件&#xff0c;选择一下文件 4、开启虚拟机&#xff0c;进入系统 1、下载 Get Kali | Kali Linux 我使用的是VMware12&#xff0c;所有下载下图 2、解压 3、安装 3.1 然后…