Java线程相关

线程优先级

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

public class Priority {
    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    public static void main(String[] args) throws Exception {
        List<Job> jobs = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            // MIN: 1 MAX: 10
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread thread = new Thread(job, "Thread:" + i);
            // 设置线程优先级(操作系统可能不会理会Java线程对优先级的设置) 不一定会被系统优先调度
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        for (Job job : jobs) {
            System.out.println("Job Priority : " + job.priority + ", Count:" + job.jobCount);
        }

    }

    static class Job implements Runnable {
        private final int priority;
        private long jobCount;

        public Job(int priority) {
            this.priority = priority;
        }

        public void run() {
            while (notStart) {
                Thread.yield();
            }
            while (notEnd) {
                Thread.yield();
                jobCount++;
            }
        }
    }
}

线程状态

Thread#State 六种状态

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

image-20230717134957954

线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换,Java线程状态变迁:

image-20230717135248075

等待有超时时间:TIMED_WAITING

等待无超时时间:WAITING

同步代码(synchronized)未获得锁:BLOCKED

注意:Java将操作系统中的运行和就绪两个状态合并称为运行状态。

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法

安全地终止线程

/**
 * 安全地终止线程
 * volatile / interrupt volatile变量或者中断标识
 */
public class Shutdown {
    public static void main(String[] args) throws Exception {
        Runner one = new Runner();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();
        Runner two = new Runner();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
    }

    private static class Runner implements Runnable {
        private long i;

        /**
         * 可见性
         */
        private volatile boolean on = true;

        @Override
        public void run() {
            // !!!
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " + i);
        }

        public void cancel() {
            on = false;
        }
    }
}

通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。

Thread.join()的使用

含义是:当前线程A等待thread线程终止之后才从thread.join()返回。

public class Join {

    /**
     * 线程顺序执行
     */
    public static void main(String[] args) throws Exception {
        Thread previous = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            // 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
            Thread thread = new Thread(new Domino(previous), String.valueOf(i));
            thread.start();
            previous = thread;
        }
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }

    static class Domino implements Runnable {
        private Thread thread;

        public Domino(Thread thread) {
            this.thread = thread;
        }

        public void run() {
            try {
                // 线程结束才从这返回
                thread.join();
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }
}

输出:

main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.

每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回,这里涉及了等待/通知机制(等待前驱线程结束,接收前驱线程结束通知)。

ThreadLocal的使用

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。【实现线程隔离】

/**
 * ThreadLocal 以ThreadLocal对象key , 泛型类型为value (线程隔离)
 */
public class ProfilerThreadLocal {
    // 第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次
    private static final ThreadLocal<Long> TIME_THREADLOCAL = ThreadLocal.withInitial(System::currentTimeMillis);

    public static final void begin() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    public static final long end() {
        return System.currentTimeMillis() - TIME_THREADLOCAL.get();
    }

    public static void main(String[] args) throws Exception {
        ProfilerThreadLocal.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println(Thread.currentThread().getName() + " Cost: " + ProfilerThreadLocal.end() + " mills");


        new Thread(() -> {
            ProfilerThreadLocal.begin();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + " Cost: " + ProfilerThreadLocal.end() + " mills");
        }, "test-thread").start();
    }
}

输出:

main Cost: 1007 mills
test-thread Cost: 3011 mills

等待/通知机制

一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者就是消费者,这种模式隔离了“做什么”(what)和“怎么做”(How),在功能层面上实现了解耦,体系结构上具备了良好的伸缩性。

等待/通知的相关方法

image-20230717213023282

/**
 * 等待/通知机制
 */
public class WaitNotify {
    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) throws Exception {
        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread = new Thread(new Notify(), "NotifyThread");
        notifyThread.start();

        // wait -> notify -> running -> sleep
    }

    static class Wait implements Runnable {
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 当条件不满足时,继续wait,同时释放了lock的锁 【当前线程放到对象的等待队列】
                while (flag) {
                    try {
                        System.out.println(Thread.currentThread() + " flag is true. wait "
                                + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        lock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                // 条件满足时,完成工作
                System.out.println(Thread.currentThread() + " flag is false. running "
                        + new SimpleDateFormat(" HH:mm:ss").format(new Date()));
            }
        }
    }

    static class Notify implements Runnable {
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
                // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
                System.out.println(Thread.currentThread() + " hold lock. notify " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                // 【将等待队列中所有线程全部移到同步队列】
                lock.notifyAll();
                flag = false;
                SleepUtils.second(5);
            }
            // 再次加锁
            synchronized (lock) {
                System.out.println(Thread.currentThread() + " hold lock again. sleep "
                        + new SimpleDateFormat(" HH:mm:ss").format(new Date()));
                SleepUtils.second(5);
            }
        }
    }
}

调用wait()、notify()以及notifyAll()时需要注意的细节:

  1. 使用wait()、notify()和notifyAll()时需要先对调用对象加锁
  2. 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
  3. 调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  4. notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。

上述全流程:

image-20230717214638851

等待/通知的经典范式

等待方

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件满足则执行对应的逻辑。

伪代码如下:

synchronized(对象) {
       while(条件不满足) {
              对象.wait();
       }
       对应的处理逻辑
}

通知方

  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。

伪代码如下:

synchronized(对象) {
       改变条件
       对象.notifyAll();
}

等待超时模式

这样一个场景:调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。

等待超时模式的伪代码:

// 对当前对象加锁
public synchronized Object get(long mills) throws InterruptedException {
       long future = System.currentTimeMillis() + mills;
       long remaining = mills;
       // 当超时大于0并且result返回值不满足要求
       while (result == null && remaining > 0) {
              wait(remaining);
              remaining = future - System.currentTimeMillis();
       }
       return result;
}

等待超时模式就是在等待/通知范式基础上增加了超时控制,这使得该模式相比原有范式更具有灵活性,因为即使方法执行时间过长,也不会“永久”阻塞调用者,而是会按照调用者的要求“按时”返回。

举个例子:

/**
 * 等待超时模式应用
 */
public class ConnectionPool {
    private final LinkedList<Connection> pool = new LinkedList<>();

    public ConnectionPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(ConnectionDriver.createConnection());
            }
        }
    }

    public void releaseConnection(Connection connection) {
        if (connection != null) {
            synchronized (pool) {
                // 连接释放后需要进行通知,这样其他消费者能够感知到连接池中已经归还了一个连接
                pool.addLast(connection);
                pool.notifyAll();
            }
        }
    }

    // 在mills内无法获取到连接,将会返回null
    public Connection fetchConnection(long mills) throws InterruptedException {
        synchronized (pool) {
            // 完全超时
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    pool.wait();
                }
                return pool.removeFirst();
            } else {
                long future = System.currentTimeMillis() + mills;
                long remaining = mills;
                while (pool.isEmpty() && remaining > 0) {
                    pool.wait(remaining);
                    remaining = future - System.currentTimeMillis();
                }
                Connection result = null;
                if (!pool.isEmpty()) {
                    result = pool.removeFirst();
                }
                return result;
            }
        }
    }
}

测试:

public class ConnectionPoolTest {
    static ConnectionPool pool = new ConnectionPool(10);
    // 保证所有ConnectionRunner能够同时开始
    static CountDownLatch start = new CountDownLatch(1);
    // main线程将会等待所有ConnectionRunner结束后才能继续执行
    static CountDownLatch end;

    public static void main(String[] args) throws Exception {
        // 线程数量,可以修改线程数量进行观察
        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        int count = 20;
        AtomicInteger got = new AtomicInteger();
        AtomicInteger notGot = new AtomicInteger();
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new ConnetionRunner(count, got, notGot),
                    "ConnectionRunnerThread");
            thread.start();
        }
        // 保证线程同时开始执行
        start.countDown();
        // 保证线程线程全部执行完毕,统一打印结果 countDown 数量 + 1,数量达到之后 await 之后代码才会执行
        end.await();
        System.out.println("total invoke: " + (threadCount * count));
        System.out.println("got connection:  " + got);
        System.out.println("not got connection " + notGot);
    }

    static class ConnetionRunner implements Runnable {
        int count;
        AtomicInteger got;
        AtomicInteger notGot;

        public ConnetionRunner(int count, AtomicInteger got, AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }

        public void run() {
            try {
                start.await();
            } catch (Exception ex) {
            }
            while (count > 0) {
                try {
                    // 从线程池中获取连接,如果1000ms内无法获取到,将会返回null
                    // 分别统计连接获取的数量got和未获取到的数量notGot
                    Connection connection = pool.fetchConnection(1000);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                    }
                } catch (Exception ex) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}

随着客户端线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。虽然客户端线程在这种超时获取的模式下会出现连接无法获取的情况,但是它能够保证客户端线程不会一直挂在连接获取的操作上,而是 “按时”返回,并告知客户端连接获取出现问题,是系统的一种自我保护机制。

模拟简单线程池

顶层接口

顶层接口,决定该做什么

/**
 * 线程池顶层接口
 */
public interface ThreadPool<Job extends Runnable> {
    // 执行一个Job,这个Job需要实现Runnable
    void execute(Job job);

    // 关闭线程池
    void shutdown();

    // 增加工作者线程
    void addWorkers(int num);

    // 减少工作者线程
    void removeWorker(int num);

    // 得到正在等待执行的任务数量
    int getJobSize();
}

默认实现类

默认实现类,实现所有方法实现

public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> {
    // 线程池最大限制数
    private static final int MAX_WORKER_NUMBERS = 10;
    // 线程池默认的数量
    private static final int DEFAULT_WORKER_NUMBERS = 5;
    // 线程池最小的数量
    private static final int MIN_WORKER_NUMBERS = 1;
    // 这是一个工作列表,将会向里面插入工作
    private final LinkedList<Job> jobs = new LinkedList<>();
    // 工作者列表
    private final List<Worker> workers = Collections.synchronizedList(new ArrayList<Worker>());
    // 工作者线程的数量
    private int workerNum = DEFAULT_WORKER_NUMBERS;
    // 线程编号生成
    private final AtomicLong threadNum = new AtomicLong();

    public DefaultThreadPool() {
        initializeWorkers(DEFAULT_WORKER_NUMBERS);
    }

    public DefaultThreadPool(int num) {
        workerNum = num > MAX_WORKER_NUMBERS ? MAX_WORKER_NUMBERS : Math.max(num, MIN_WORKER_NUMBERS);
        initializeWorkers(workerNum);
    }

    public void execute(Job job) {
        if (job != null) {
            // 添加一个工作,然后进行通知
            synchronized (jobs) {
                jobs.addLast(job);
                jobs.notify();
            }
        }
    }

    public void shutdown() {
        for (Worker worker : workers) {
            worker.shutdown();
        }
    }

    public void addWorkers(int num) {
        synchronized (jobs) {
            // 限制新增的Worker数量不能超过最大值
            if (num + this.workerNum > MAX_WORKER_NUMBERS) {
                num = MAX_WORKER_NUMBERS - this.workerNum;
            }
            initializeWorkers(num);
            this.workerNum += num;
        }
    }

    public void removeWorker(int num) {
        synchronized (jobs) {
            if (num >= this.workerNum) {
                throw new IllegalArgumentException("beyond workNum");
            }
            // 按照给定的数量停止Worker
            int count = 0;
            while (count < num) {
                Worker worker = workers.get(count);
                if (workers.remove(worker)) {
                    worker.shutdown();
                    count++;
                }
            }
            this.workerNum -= count;
        }
    }

    public int getJobSize() {
        return jobs.size();
    }

    // 初始化线程工作者
    private void initializeWorkers(int num) {
        for (int i = 0; i < num; i++) {
            Worker worker = new Worker();
            workers.add(worker);
            Thread thread = new Thread(worker, "ThreadPool-Worker-" + threadNum.incrementAndGet());
            thread.start();
        }
    }

    // 工作者,负责消费任务
    class Worker implements Runnable {
        // 是否工作
        private volatile boolean running = true;

        public void run() {
            while (running) {
                Job job = null;
                synchronized (jobs) {
                    // 如果工作者列表是空的,那么就wait
                    while (jobs.isEmpty()) {
                        try {
                        	// 等待被唤醒
                            jobs.wait();
                        } catch (InterruptedException ex) {
                            // 感知到外部对WorkerThread的中断操作,返回
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    // 取出一个Job
                    job = jobs.removeFirst();
                }
                if (job != null) {
                    try {
                        job.run();
                    } catch (Exception ex) {
                        // 忽略Job执行中的Exception
                    }
                }
            }
        }

        public void shutdown() {
            running = false;
        }
    }
}

使用时,只需往线程池里添加任务即可。

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

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

相关文章

【基于 GitLab 的 CI/CD 实践】01、GitLab CI/CD 基础概念

目录 一、为什么要做 CI/CD &#xff1f; 1.1 背景-传统的应用开发发布模式 问题 1.2 持续集成与持续交付 持续集成&#xff08;CI&#xff09; 持续交付&#xff08;CD&#xff09; 持续部署&#xff08;CD&#xff09; 1.3 CI/CD 的价值体现 1.4 推荐常用的 CI/CD 工…

Linux内核结构与特性简介

系统调用接口&#xff1a;位于最上层&#xff0c;实现了一些基本的功能&#xff0c;如read和write等系统调用。这是用户空间程序与内核交互的接口&#xff0c;提供了对内核功能的访问。 内核代码&#xff1a;位于系统调用接口之下&#xff0c;可以看作是独立于体系结构的通用内…

linux之Ubuntu系列(四)用户管理 用户和权限 chmod 超级用户root, R、W、X、T、S 软链接和硬链接 shell

r(Read&#xff0c;读取)&#xff1a;对文件而言&#xff0c;具有读取文件内容的权限&#xff1b;对目录来说&#xff0c;具有浏览目 录的权限。 w(Write,写入)&#xff1a;对文件而言&#xff0c;具有新增、修改文件内容的权限&#xff1b;对目录来说&#xff0c;具有删除、移…

基于单片机快递柜的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;液晶显示当前信息&#xff0c;最多可存储几十个&#xff1b;按下存储按键液晶显示当前快递柜剩余数量&#xff1b;继电器打开&#xff0c;表示用来放物品&#xff1b;正次按下存储按键将取消存快递&#xff0c;继电器关闭快递柜可用…

Spark编程-使用SparkCore求TopN,Max_Min_Value

简介 使用SparkCore求top5值编程&#xff0c;最大最小值 求订单前五的TOP5值 数据 数据字段如下&#xff1a;orderid,userid,payment,productid 需求如下&#xff1a;从文本文件中读取数据&#xff0c;并计算出前5个payment(订单的付款金额)值 //字段 orderid,userid,payme…

在 3ds Max 中对二战球形炮塔进行建模

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 实际上被称为“斯佩里球炮塔”&#xff0c;它被用于二战的B-17和B-24轰炸机。 本教程介绍如何在 3ds Max 中对球形转塔进行建模。建模时&#xff0c;您将使用背景图片作为辅助。首先创建一个低多边形球体。…

视频融合平台EasyCVR登录后通道数据及菜单栏页面显示异常的排查与解决

EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。 有用…

Windows11 C盘瘦身

1.符号链接 将大文件夹移动到其他盘&#xff0c;创建成符号链接 2.修改Android Studio路径设置 1.SDK路径 2.Gradle路径 3.模拟器路径 设置环境变量 ANDROID_SDK_HOME

存量市场下,雅迪的高端化之路举步维艰?

为了让自家的高端产品成功“突围”&#xff0c;雅迪在营销上无所不用其极。 继在央视大楼高调发布后&#xff0c;近日雅迪冠能探索E10完成了力战70吨游艇、无惧24吨雨水冲刷、制霸百公里全地形等极限挑战&#xff0c;“树立起新一代两轮电动车豪华标杆旗舰”。 图源&#xff1…

字节跳动后端面试,笔试部分

var code "7022f444-ded0-477c-9afe-26812ca8e7cb" 背景 笔者在刷B站的时候&#xff0c;看到了一个关于面试的实录&#xff0c;前半段是八股文&#xff0c;后半段是笔试部分&#xff0c;感觉笔试部分的题目还是挺有意思的&#xff0c;特此记录一下。 笔试部分 问…

Jmeter性能测试,通过插件监控服务器资源使用情况

Jmeter作为性能测试的首选工具&#xff0c;那么在性能测试过程中如何方便快捷的监测服务器资源使用情况&#xff1f; 可以通过jmeter 安装"PerfMon(Servers Performance Monitoting)"插件并配合服务端资源监控工具进行实现&#xff0c;详细操作流程如下&#xff1a;…

【微信机器人开发

现在并没有长期免费的微信群机器人&#xff0c;很多都是前期免费试用&#xff0c;后期进行收费&#xff0c;或者核心功能需要付费使用的。 这时如果需要群机器人帮助我们管理群聊&#xff0c;建议大家使有条件的可以自己开发微信管理系统。了解微信群机器人的朋友都知道&#x…

教程 | 如何10秒内一键生成高质量PPT

Hi! 大家好&#xff0c;我是赤辰&#xff01; 近期新进的学员不少职场小白&#xff0c;对AI工具提效办公很感兴趣&#xff0c;今天火速给大家安排&#xff0c;ChatGPTMindShow强强联合&#xff0c;30秒内快速生成PPT&#xff0c;对于策划小白来说简直是福音呀&#xff01; 市…

第三方api对接怎么做?淘宝1688api接口怎么对接?

在今天的互联网上&#xff0c;第三方API对接是必不可少的。这种技术将不同的应用程序/服务连接在一起&#xff0c;创造了无限的可能性。 第三方api对接怎么做&#xff1f; 1、与支付公司签约 首先&#xff0c;通过正规的渠道&#xff0c;如支付公司官网或正规服务商&#xf…

Echarts 修改背景颜色、全屏自适应屏幕

修改背景色&#xff1a; 全屏自适应屏幕 首先拿到外面的div的高度 通过DOM获取clientHeight即为无论全屏与否都是DIV的整个高度 在通过高度去做自适应就好了

Redis可视化工具(Redis Desktop Manager)

redis是我们平时开发工作中经常用到的非关系型数据库&#xff0c;常用于做数据缓存&#xff0c;分布式锁等。 为了更方便的使用redi&#xff0c;这里给大家推荐一款可视化工具&#xff1a;Redis Desktop Manager。 1.下载与安装 直接到gihub下载&#xff0c;地址 Release 0.…

搭建Redis主从集群和哨兵

说明&#xff1a;单机的Redis存在许多的问题&#xff0c;如数据丢失问题、高并发问题、故障恢复问题、海量数据的存储能力问题&#xff0c;针对这四个问题&#xff0c;对应解决方式有&#xff1a;数据持久化&#xff08;参考&#xff1a;http://t.csdn.cn/SSyBi&#xff09;、搭…

Stable Diffusion学习笔记

一些零散笔记 批量化生产的时候推荐提高生成批次&#xff0c;不建议提高每批数量 灰常好的模型网站 LiblibAI哩布哩布AI-中国领先原创AI模型分享社区 出图效率倍增&#xff01;47个高质量的 Stable Diffusion 常用模型推荐 - 优设网 - 学设计上优设 关键词Prompt顺序 画质…

16 | 视图:如何实现服务和数据在微服务各层的协作?

目录 服务的协作 1. 服务的类型 2. 服务的调用 微服务内跨层 微服务之间的服务调用 领域事件驱动 3. 服务的封装与组合 基础层 领域层 应用层 用户接口层 4. 两种分层架构的服务依赖关系 松散分层架构的服务依赖 严格分层架构的服务依赖 数据对象视图 基础层 领…

Hugging Face开源库accelerate详解

官网&#xff1a;https://huggingface.co/docs/accelerate/package_reference/accelerator Accelerate使用步骤 初始化accelerate对象accelerator Accelerator()调用prepare方法对model、dataloader、optimizer、lr_schedluer进行预处理删除掉代码中关于gpu的操作&#xff0…