【JavaEE -- 多线程3 - 多线程案例】

多线程案例

  • 1.单例模式
    • 1.1 饿汉模式的实现方法
    • 1.2 懒汉模式的实现方法
  • 2. 阻塞队列
    • 2.1 引入生产消费者模型的意义:
    • 2.2 阻塞队列put方法和take方法
    • 2.3 实现阻塞队列--重点
  • 3.定时器
    • 3.1 定时器的使用
    • 3.2 实现定时器
  • 4 线程池
    • 4.1 线程池的使用
    • 4.2 实现一个简单的线程池 -- 重点

1.单例模式

单例模式:是一种设计模式,某个类,在一个进程中只创建出一个实例(对象),对代码进行一个更严格的校验和检查。

实现单例模式最基础的实现方式

  • 饿汉模式:
  • 懒汉模式

1.1 饿汉模式的实现方法

单例模式中一种简单的写法,饿汉:形容创建实例非常迫切,实例是在类加载的时候就创建了,创建时机非常早,相当于程序一启动,实例就创建了

class Singleton {
    // 在这个Singleton 被加载的时候,就会初始化这个静态成员
    private static Singleton instance = new Singleton();// instance 指向的这个对象,就是唯一的一个对象

    public static Singleton getInstance() {
    	// 对于饿汉模式,getInstance直接返回Instance实例,这个操作本质上是读操作,在多线程情况下读取同一个变量是线程安全的
        return instance;
    }

    private Singleton() {

    }
}

public class ThreadDemo26 {
    public static void main(String[] args) {
        // Singleton singleton = new Singleton(); //
        Singleton s = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s == s2);

    }
}

1.2 懒汉模式的实现方法

懒汉模式:创建实例的时机比较晚,只到第一次使用的时候才会创建实例
注意

  • 在这个引用 指向唯一实例,这个引用先初始化为null,而不是立即创建实例
  • 由于在 instance = new SingletonLazy(); 实例化的时候有读有写,在多线程下是不安全的,会出现指令重排序的线程安全问题,通过添加volatile解决
  • instance = new SingletonLazy(); 拆成三大步骤:
    1. 申请一段内存空间
    2. 在这内存上调用构造方法,创建出这个实例
    3. 把这个内存地址赋值给Instance引用变量
      正常是123,但是在多线程下可能132,就会出现问题
  • 如果InStance为null,就说明首次调用,首次调用就需要考虑到线程安全问题,如果非null,就说明是后续的调用,就不必加锁,即双重校验锁

** volatile**:

  1. 保证内存可见性,每次访问变量都必须重新读取内存,而不会优化到寄存器/缓存中
  2. 禁止指令重排序,针对这个volatile修饰的变量的读写操作相关指令,是不能被重排序的
class SingletonLazy {
    // 这个引用指向唯一实例,这个引用先初始化为null,而不是立即创建实例

    // 在这里添加volatile 避免重排序引起的线程安全问题
    private volatile static SingletonLazy instance = null;
    private static Object locker = new Object();

    // 在懒汉模式,有读也有写 instance = new SingletonLazy();,在多线程下是不安全的,且不是单例模式了,
    // 1.通过加锁 synchronized  然后再 把if 和 new两个操作打包成一个原子的
    public static SingletonLazy getInstance() {
        // 2.如果Istance 为null,就说明首次调用,首次调用就需要考虑到线程安全问题
        // 如果非null,就说明是后续的调用,就不必加锁
        // 双重校验锁
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();// 3.还会出现指令重排序引起线程安全,通过添加volatile解决
                    /**instance = new SingletonLazy(); 拆成三大步骤
                     * 1.申请一段内存空间
                     * 2.在这个内存上调用构造方法,创建出这个实例
                     * 3.把这个内存地址赋值给Instance引用变量
                     * 正常是123,但是在多线程下可能132 就会出现问题
                     */
                }
            }
        }

        return instance;
    }

    private SingletonLazy() {

    }
}
public class ThreadDemo27 {

    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

2. 阻塞队列

阻塞队列是一种特殊的队列,遵守先进先出的原则
阻塞队列是一种线程安全的数据结构,包含两个特性

  • 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素
  • 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素

基于阻塞队列,就可以实现 生产消费者模型(是一种多线程编程的方法)

2.1 引入生产消费者模型的意义:

  1. 解耦合,即把代码的耦合程度,从高降低,在实际开发中,经常涉及到分布式系统服务器整个功能不是由一个服务器全部完成的,而是每个服务器负责一部分功能,通过服务器之间的网络通信,最终完成整个功能
    公网内 的电商网站客户端,获取到主页信息,机房内部网络中 有入口服务器A,用户服务器B,商品服务器C。在这个模型中,A代码就需要涉及到一些和B相关的操作,同样B也涉及A,A和C中的代码也相互涉及。如果B或者C挂了,对A的影响非常大,即为高耦合
    在这里插入图片描述

引入生产消费者模型之后就可降低耦合,即添加阻塞队列
在这里插入图片描述上述模型中,A和B、C都不是直接交互了,而是通过阻塞队列传话,如果B或者C挂了,对A的影响几乎没有

  1. 削峰填谷:如下图模型,当请求多了,A的请求数量会增加很多,B用户服务器(找到对应用户信息)和C商品服务器(从数据库中匹配商品)都会有很大的影响。
    在这里插入图片描述
    添加阻塞队列之后:即使外界的请求出现峰值,队列没有业务逻辑,只是存储数据抗压能力很强。有效的防止了B和C被冲击挂掉
    在这里插入图片描述

2.2 阻塞队列put方法和take方法

  • put(): put和offer都是入队列,而put带有阻塞功能,没带阻塞功能,队列满了会返回结果。
  • take():取出元素的时候,带有阻塞功能,判定如果队列为空,就进行wait阻塞等待。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ThreadDemo28 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
        // put和offer都是入队列 ,而 put带有阻塞功能,没带阻塞,队列满了会返回结果
        queue.put("aaa");
        // take 也带有阻塞功能
        String elem = queue.take();
        System.out.println("elem = " + elem);
        elem = queue.take();
        System.out.println("elem = " + elem);
    }
}

2.3 实现阻塞队列–重点

1)先实现普通队列,基于数组来实现(环形队列),区分队列空和队列满

  1. 浪费一个格子,定义一个head队头和tail队尾,tail最多走到head的前一个位置
  2. 引入size变量

2)再加上线程安全
3)再加上阻塞功能

  • 队列满了,添加wait进行阻塞,队列不满,即出队列成功后进行notify唤醒
  • 队列空了,再出队列,同样也需要阻塞,同样是在另一个入队列成功后的线程中唤醒
class MyBlockingQueue {
    private String[] elems = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    public MyBlockingQueue(int capacity) {
        elems = new String[capacity];
    }

    private Object locker = new Object();
    public void put(String elem) throws InterruptedException {
        synchronized (locker) {
            // 使用while:wait可能会被提前唤醒(当条件还没满足,就被唤醒了)
            while (size >= elems.length) {
                // 队列满了 实现阻塞
                locker.wait(); //在Java标准库推荐使用wait搭配while循环,多一/N次确认操作
            }
            // 如果加锁不包含 while判断队列是否为满,在多线程下就会导致当入队列就会多入一个
            // 新的元素要放到 tail指向的位置上
            elems[tail] = elem;
            tail++;
            // 如果队尾大于数组大小,让tail重新指向0下标,形成闭环
            if (tail >= elems.length) { // tail = tail % elems.length
                tail = 0;
            }
            size++;

            // 入队列成功后进行唤醒
            locker.notify();
        }
    }

    public String take() throws InterruptedException {
        String elem = null;
        synchronized (locker) {
            while (size == 0) {
                //队列空了
                // 实现阻塞
                locker.wait();
            }
            // 取出 head 位置的元素并返回
            elem = elems[head];
            head++;
            if (head >= elems.length) {
                head = 0;
            }
            size--;

            // 队列不满,即出队列成功之后,加上唤醒
            locker.notify();
        }
        return elem;
    }

}
public class ThreadDemo29 {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue(1000);
        // 生产者消费者模型(核心是阻塞队列,使用synchronized和wait/notify达到线程安全&阻塞) 不仅仅是一个线程
        // 也可能是一个独立的服务器程序,甚至是一组服务器程序
        // 生产者
        Thread t1 = new Thread(() -> {
            int n = 1;
            while (true) {
                try {
                    queue.put(n+" ");
                    System.out.println("生产元素:" + n);
                    n++;
                    //Thread.sleep(500); //生产一个消费一个
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        // 消费者
        Thread t2 = new Thread(() -> {
           while (true) {
               try {
                   String n = queue.take();
                   System.out.println("消费元素:" + n);
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t1.start();
        t2.start();
    }
}

3.定时器

在Java标准库中提供了定时器的使用,Timer类,核心方法是schedule(),它有两个参数,第一个参数是即将执行的任务代码,第二个是指定多长时间之后执行(单位ms)

3.1 定时器的使用

import java.util.Timer;
import java.util.TimerTask;
// 运行完之后,进程没有结束,因为timer 里内置了线程(前台线程) timer不知道是否还要添加任务进来,
// 可以使用timer.cancel()来主动结束

public class ThreadDemo30 {
    public static void main(String[] args) throws InterruptedException {
        // 定义一个timer添加多任务,每个任务同时会带有一个时间
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //时间到了之后,要执行的代码
                System.out.println("hello timer 3000");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer 2000");
            }
        },2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer 1000");
            }
        },1000);

        System.out.println("hello main");
        Thread.sleep(3000);

        timer.cancel();
    }
}

3.2 实现定时器

  • 一个带优先级的阻塞队列(按从小到大的顺序)
  • 队列中的每个元素是一个Task对象
  • Task中带有一个时间属性,队首元素就是即将执行的
  • 同时有一个worker线程一直扫描队首元素,看队首元素是否需要执行
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Timer;

// 通过这个类,来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {
    //在什么时间点来执行这个任务
    // 此处约定这个time是一个ms 级别的时间戳
    private long time;
    public long getTime() {
        return time;
    }
    // 实际任务要执行的代码
    private Runnable runnable;

    // delay 期望是一个相对时间
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        // 计算真正要执行任务的绝对时间()
        this.time = System.currentTimeMillis() + delay;
    }
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

// 通过这个类,来表示一个定时器
class MyTimer {
    // 负责扫描任务队列,执行任务的线程
    private Thread t = null;
    // 任务队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();
    // 把任务放进队列
    public void schedule(Runnable runnable,long delay) {
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable,delay);
            queue.offer(task);
            // 添加新的元素之后,就可以唤醒扫描线程的wait了
            locker.notify();
        }
    }

    public void cancel() {
        //
    }

    // 构造方法,创建扫描线程,让扫描线程来完成判定和执行
    public MyTimer() {
        t = new Thread(() -> {
            // 扫描线程就需要循环反复的扫描队首元素,然后判定队首元素是不是时间到了
            // 如果没到时间,啥也没有
            // 如果时间到了,就执行这个任务从队列中删除
            while (true) {
                try {
                    // 1. 解决线程安全问题
                    synchronized (locker) { //这里的代码执行速度很快,解锁之后立即又重新尝试加锁,导致
                        // 其他线程通过schedule想加锁,但是加不上 (即线程饿死) -》 引入wait/notify
                        if (queue.isEmpty()) {
                            // 暂时先不处理
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        // 获取当前时间
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            //当前时间已经达到了任务时间,就可以执行任务了
                            queue.poll();
                            task.run();
                        }else {
                            // 当前时间还没到任务时间,暂时不执行
                            // 不能使用sleep。会错过新的任务,也无法释放锁
                            //Thread.sleep(task.getTime() - curTime);
                            locker.wait(task.getTime() - curTime);
                        }
                    }  // 释放锁的
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 启动线程
        t.start();
    }
}
public class ThreadDemo31 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        },2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        },1000);
        System.out.println("hello main");
    }
}

4 线程池

线程池:提前把需要用的线程,在线程池里准备好,需要用的时候就从池子里取,用完之后还给池子
由于频繁的创建销毁进程,成本太高,引入了轻量级 进程,即线程,如果创建销毁线程的频率也进一步提高,此时线程的创建销毁开销也越来越大
所以有两种优化此处的线程的创建和销毁:

  1. 引入轻量级 线程,即纤程/协程:本质是程序员在用户太代码进行调度,而不是靠内核的调度器调度,节省了调度上的开销
  2. 线程池:把要使用的线程提前创建好,用完了之后也不要直接释放,而是放进线程池里以备下次使用,从而节省了创建销毁线程的开销。

引入问题:为什么从线程池里取线程就比系统申请更高效

  • 从线程池里取线程是纯用户态代码(可控的)
  • 通过系统申请创建线程,就是需要内核来完成(不太可控)

4.1 线程池的使用

在Java标准库中,把ThreadPoolExecutor类表线程池,给封装 成 Executors 工厂类,工厂类:创建出不同的线程池对象(在内部把ThreadPoolExecutor创建好了并且设置不同的参数)
ThreadPoolExecutor 线程池的参数:
在这里插入图片描述

  • int corePoolSize :核心线程数,int maximunmPoolSize:最大线程数
  • long keepAliveTime:保持存活时间,TimeUnit unit:时间单位(s,min,ms,hour)
  • ThreadFactory threadFactor:线程工厂,通过这个工厂类来创建线程对象(Thread)
  • RejectExecutionHandler handler:拒绝策略,在线程池中,有一个阻塞队列,能够容纳的元素有上限,当任务队列已经满了,如果继续往队列添加任务,线程池会进行下面4种操作:

在这里插入图片描述

Executor创建线程池的方式:

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadDemo32 {
    public static void main(String[] args) {
        // 创建线程池的时候,设定线程池的线程数量
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

** 创建线程池的时候,很多时候需要设定线程池的线程数量?**

  • 不同的程序,能够设定的线程的数目是不同的,要具体问题具体分析, 一个线程是CPU密集型的任务(在线程run里面进行计算),还是IO密集型任务(在线程run里使用scanner读取用户的输入)
  • 如果一个进程中所有的线程都是CPU密集型的,每个线程所有工作都是CPU上执行,此时线程数目就不应该超过N(CPU的逻辑核心数)。
  • 如果一个进程中,所有线程都是IO密集型,每个线程的大部分工作都是等待IO,此时线程数目与那元超过N
  • 由于程序的复杂性,所以需要通过实验/测试,即设定不同的线程数目,分别进行性能测试,衡量每种线程数目下,总的时间开销,和系统资源占用的开销,找到这之间的合适值

4.2 实现一个简单的线程池 – 重点

  1. 提供构造方法,指定创建多少个线程
  2. 在构造方法中,把这些线程都创建好
  3. 有一个阻塞队列,能够持有要执行的任务
  4. 提供submit方法,可以添加新的任务
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPoolExecutor {
    private List<Thread> threadList = new ArrayList<>();

    // 保存任务的队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
    // 通过n来指定创建多少个线程
    public MyThreadPoolExecutor(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
               // 线程把任务队列中的任务不停的取出来,并且进行执行
                while (true) {
                    try {
                        // 此处的take 带有阻塞功能
                        // 如果队列为空,此时 take就会阻塞
                        Runnable runnable = queue.take();
                        // 取出一个执行一个
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            threadList.add(t);
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

public class ThreadDemo33 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
        for (int i = 0; i < 1000; i++) {
            // n是一个实事final变量 ,每次循环都是一个新的n,就可以被捕获
            int n = i;
            executor.submit(new Runnable() {
                @Override
                public void run() { // 回调函数访问当前外部作用域的变量就是变量捕获
                    // i 一值在变,把i改成成员变量或者 int n = i
                    System.out.println("执行任务" + n + ",当前线程为:"+ Thread.currentThread().getName());
                }
            });
        }
    }
}

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

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

相关文章

力扣大厂热门面试算法题 33-35

33. 搜索旋转排序数组&#xff0c;34. 在排序数组中查找元素的第一个和最后一个位置 &#xff0c;35. 搜索插入位置&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.15 可通过leetcode所有测试用例。 目录 33. 搜索旋转排序数组…

Java项目:52 springboot基于SpringBoot的旅游网站的设计与实现013

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 旅游网站主要功能如下&#xff1a; 1.用户管理&#xff1a;注册、登录、退出、修改密码&#xff1b; 2.分类显示&#xff1a;显示旅游路线的分类&am…

基础GamePlay知识-凸多边形碰撞检测(SAT)

分离轴算法 也称为SAT(Separating Axis Theorem)算法&#xff0c;主要用于凸多边形之间的相交检测&#xff0c;主要思路为寻找分离轴。 分离轴&#xff1a;分离轴是一个向量&#xff0c;可以理解为一条平行于多边形边的线。如果两个凸多边形在分离轴上的投影没有重叠&#xf…

基于单片机的酒精浓度测试仪

摘 要 现如今&#xff0c;人们对生活的态度和生活方式变得不同,&#xff0c;不仅私家车成为了人们最普遍的交通工具&#xff0c;大多数人都有自己的私家车,而且人们对酒精的消耗量也越来越大&#xff0c;这些就导致酒后驾车行为越来越普遍&#xff0c;酒后驾车意外越来越频繁&…

家电工厂5G智能制造数字孪生可视化平台,推进家电工业数字化转型

家电5G智能制造工厂数字孪生可视化平台&#xff0c;推进家电工业数字化转型。随着科技的飞速发展&#xff0c;家电行业正迎来一场前所未有的数字化转型。在这场制造业数字化转型中&#xff0c;家电5G智能制造工厂数字孪生可视化平台扮演着至关重要的角色。本文将从数字孪生技术…

NCP1271D65R2G中文资料规格书PDF数据手册引脚图参数图片价格功能特性描述

产品描述&#xff1a; NCP1271 是成功的 7 引脚电流模式 NCP12XX 系列的新一代引脚-引脚兼容新产品。该控制器通过使用可调节 Soft Skip 模式和集成的高电压启动 FET&#xff0c;实现了卓越的待机功耗。此专属 Soft Skip 还大大降低了噪音的风险。 因此可以在箝位网络中使用不…

我的尝试:Codigger + Vim

若您愿意耐心投入&#xff0c;学习 Vim 的过程其实远比想象中轻松。我对 Vim 产生兴趣&#xff0c;主要是源于它对提升生产力的巨大潜力。我尝试了 Neovim、NvChad 以及 Codigger Vim 插件&#xff0c;如今我的工作效率已远超从前。 那么&#xff0c;Vim 究竟是什么呢&#xff…

uni app 钓鱼小游戏

最近姑娘喜欢玩那个餐厅游戏里的钓鱼 &#xff0c;经常让看广告&#xff0c;然后就点点点... 自己写个吧。小鱼的图片自己搞。 有问题自己改&#xff0c;不要私信我 <template><view class"page_main"><view class"top_linear"><v…

【四 (3)数据可视化之 Seaborn 常用图表及代码实现 】

目录 文章导航一、介绍二、安装Seaborn三、导入Seaborn四、设置可以中文显示五、占比类图表1、饼图2、环形图 六、比较排序类1、条形图2、箱线图3、小提琴图 七、趋势类图表1、折线图 八、频率分布类1、直方图 九、关系类图表1、散点图2、成对关系图3、热力图 文章导航 【一 简…

C语言-strstr(字符串里查找字符串)

strstr&#xff08;字符串里查找字符串&#xff09; 语法格式 库函数实现的逻辑 1&#xff0c;返回一个指向str2在str1中第一次出现的位置&#xff0c;如果str2不是p&#xff0c;则返回一个空指针&#xff0c;函数返回字符串str2在字符串str1中第一次出现的位置) 2&#xf…

bootstrap3 -入门简学

1.前期准备工作 1.1 https://www.bootcss.com/ 1.2 点击下载 1.3解压下载好得东西 2. 版本介绍 Bootstrap 版本 目前市面上使用的最多的是 3.x.x 版本。各个版本的介绍&#xff1a; 2.3.2版本&#xff1a; 2013年之后&#xff0c;停止维护&#xff1b; 支持更广泛的浏览…

CIDR网络地址、广播地址、网段区间计算说明与计算工具

文章目录 开始问题参考答案 答案解析计算工具测试 开始 好久没有看计算网络&#xff0c;感觉已经完全返给老师了。 最近&#xff0c;有同事遇到个问题&#xff0c;网络一直不对&#xff0c;又开始重新看一下。 相信很多朋友长时间不看也忘了&#xff0c;所以&#xff0c;这里…

UG NX二次开发(C#)-单选对话框UF_UI_select_with_single_dialog的使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、UF_UI_select_with_single_dialog函数3、实现代码3.1 利用委托创建一个方法3.2 直接调用1、前言 对于单选对话框,采用C++/C写的时候比较容易,也在帮助文档中有示例,但是对于C#开发采…

STL库中的string

文章目录 一、STL的六大组件二、string类2.1string中的size()方法2.2隐式类型的转换2.3string的多种构造2.4string中size与length是否有差异&#xff1f;2.4string中的capacity2.5string中的push_back和append2.6string中运算符重载operator2.7string中的reserve扩容2.8string中…

DJI RONIN 4D变0字节恢复案例

RONIN 4D这个产品听起来比较陌生&#xff0c;还是DJI大疆出品。没错&#xff0c;这是大疆进军影视级的重点明星机型。前阵子刚处理过大疆RONIN 4D的修复案例&#xff0c;下边这个案例是和exfat有关的老问题:文件长度变成0字节。 故障存储:希捷18T /MS Exfat文件系统。 故障现…

Mac上使用M1或M2芯片的设备安装Node.js时遇到一些问题,比如卡顿或性能问题

对于Mac上使用M1或M2芯片的设备可能会遇到在安装Node.js时遇到一些问题&#xff0c;比如卡顿或性能问题。这可能是因为某些软件包或工具在M1或M2芯片上的兼容性不佳。为了解决这个问题&#xff0c;您可以尝试以下方法&#xff1a; 1. 使用Rosetta模式 对于一些尚未适配M1或M2…

vscode 运行 java 项目之解决“Build failed, do you want to continue”的问题

Visual Studio Code运行 java 起来似乎比 IDEA 更轻量、比 eclipse 更友好&#xff0c;是不可多得的现代编译法宝。 安装好官方推荐的 java 扩展包后&#xff0c;就可以运行 java 代码了。功能 比 code runner 强&#xff0c;支持 gradle、maven、普通java项目&#xff0c;运行…

第五十九回 公孙胜芒砀山降魔 晁天王曾头市中箭-飞桨自然语言处理套件PaddleNLP初探

公孙胜献出八卦阵&#xff0c;宋江用八员大将守阵。项充李衮进入阵里&#xff0c;被抓住了。宋江说久闻大名&#xff0c;来梁山吧。两人说誓当效力到死&#xff0c;希望能先放我们两个回去把樊瑞带来一起。见到樊瑞后把宋江讲义气一说&#xff0c;樊瑞说不可逆天&#xff0c;于…

python flask报错OSError: [WinError 10038] 在一个非套接字上尝试了一个操作。

根本原因&#xff1a; 在执行到某个代码的时候&#xff0c;出错了&#xff0c;这个服务器的连接崩了&#xff0c;导致连接提前关闭。 针对的情况&#xff1a; 检查一下这个中文的报错的下面有没有这行 “ * Restarting with watchdog (windowsapi)” 上面某个地方应该还有这行…

HTML_CSS练习:HTML注释

一、代码示例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>HTML注释</title> </head> <body><marquee loop"1">马龙强<!--下面的输入框是可以滚动的&#x…
最新文章