Java多线程快速入门

news2025/2/5 8:54:22

文章目录

  • Java多线程快速入门
    • 1、认识多线程
    • 2、多线程的实现
      • 2.1 继承Thread类
      • 2.2 实现Runnable接口
      • 2.3 利用Callable和Futrue接口
      • 2.4 三种方式的比较
    • 3、Thread类常用API
      • 3.1 守护线程
      • 3.2 礼让线程
      • 3.3 插入线程
      • 3.4 线程的生命周期
    • 5、线程安全问题
      • 5.1 synchronized
      • 5.2 Lock
    • 6、等待唤醒机制
    • 7、综合案例
      • 7.1 售票
      • 7.2 赠送礼物
      • 7.3 打印数字
      • 7.4 抢红包
      • 7.5 抽奖箱
      • 7.6 多线程统计并求最大值
      • 7.7 多线程之间的比较
    • 8、线程池
      • 8.1 自定义线程
      • 8.2 线程池最大并行数

Java多线程快速入门

趁着最近可少,复习一下Java多线程相关知识,顺便发一下以前的笔记

1、认识多线程

  • 什么是线程

    线程是指在一个进程中,执行的一个相对独立的、可调度的、可执行的代码片段。线程是操作系统能够运算调度的最小单位,它包含在进程之中,是进程中的实际运作单位,它独立地运行于进程中,并与同一进程内的其他线程共享进程的资源,如内存、文件描述符等。每个线程都有自己的栈、程序计数器和局部变量等,但它们共享进程的静态数据、堆内存和全局变量等。

    PS:可以简单理解线程是线程中的一条执行路径(可以参考流程图)

  • 线程的优缺点

    • 优点
      • 线程可以提高程序的并行性,增加程序的处理能力;

      • 线程创建和切换的开销比进程小,因此更加高效;

      • 线程可以与同一进程内的其他线程共享数据和资源,这样可以避免进程间的数据复制和通信开销。

    • 缺点
      • 同一进程内的线程都共享进程的资源,因此需要进行线程间的同步和互斥,否则容易出现竞争条件和死锁等问题;

      • 线程之间的通信和同步需要额外的开销和复杂度,因此需要仔细规划和设计线程间的通信和同步机制;

      • 由于线程共享进程的地址空间,因此需要避免线程间的访问冲突,否则容易出现数据不一致的问题。

  • 什么是进程

    进程是指在计算机中运行的程序和其相关执行状态的总和。更具体地说,进程包括程序代码、数据、内存中的栈、堆和共享库等资源。每个进程在执行时都有自己的地址空间、内存、堆和栈,以及相应的文件描述符、信号处理程序等。进程是操作系统中最基本的、最重要的资源之一。

    PS:可以简单理解进程就是正在运行的程序

  • 进程的特点

    • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    • 并发性:任何进程都可以同其他进程一起并发执行
  • 什么是单线程和多线程

    • 单线程:一个进程如果只有一条执行路径,则称为单线程程序
    • 多线程:一个进程如果有多条执行路径,则称为多线程程序
  • 多线程的应用场景

    • 拷贝、迁移大文件,可以单独使用一个线程去拷贝迁移大文件,从而可以空出时间去干其它事情
    • 聊天软件中使用多线程,服务器为每一个客户创建一个线程,处理聊天任务;客户端使用多线程进行界面更新,单独使用一个线程更新界面,一个线程用来接收消息
    • 在购物网站中,为了提高系统性能,单独使用一个线程去获取阻塞队列中的订单消息

    ……

    多线程的主要作用是为了提高系统的性能,充分利用CPU

  • 什么是并行与并发

    • 并行:在同一时刻,有多个指令在多个CPU上同时执行
    • 并发:在同一时刻,有多个指令在同一个CPU上交替执行
  • 什么是生产者和消费者

    • 生产者:负责向共享缓冲区中生产数据
    • 消费者:负责从共享缓冲区中消费数据

2、多线程的实现

2.1 继承Thread类

通过继承Thread类实现多线程

  • 写法一:传统编程方式

    • Step1:编写一个类,继承Thread类,重写Thread类的run方法

      package com.hhxy.thread;
      
      public class MyThread extends Thread {
          /**
           * 在线程开启后,此方法将被自动调用执行
           */
          @Override
          public void run() {
              for (int i = 0; i < 10; i++) {
                  System.out.println(getName() + "被执行了" + i + "次");
              }
          }
      }
      
    • Step2:编写测试类,创建多线程,并运行多线程

      package com.hhxy.test;
      
      import com.hhxy.thread.MyThread;
      
      public class ThreadTest {
          public static void main(String[] args) {
              // 创建线程对象
              MyThread t1 = new MyThread();
              MyThread t2 = new MyThread();
              // 为线程命名
              t1.setName("线程1");
              t2.setName("线程2");
              // 启动线程
              t1.start();
              t2.start();
              // 主线程输出
              for (int i = 0; i < 10; i++) {
                  System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
              }
          }
      }
      

      可以从下图中看出,程序被启动,有三个线程在运行,需要注意的是线程输出是随机的,并不是说谁先调用start方法就会先输出

      image-20230523201327225

  • 写法二:匿名内部类方式

    使用匿名内部类方式就不需要去单独创建一个类类继承Thread类了,而是直接实现Thread

    package com.hhxy.test;
    
    import com.hhxy.thread.MyThread;
    
    public class ThreadTest {
        public static void main(String[] args) {
            // 创建线程对象
            Thread t1 = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        System.out.println(getName() + "被执行了" + i + "次");
                    }
                }
            };
            Thread t2 = new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    // 注意这里由于使用了匿名内部类的写法,导致这里无法使用this,所以得调用currentThread后去当前线程名
                    System.out.println(Thread.currentThread().getName()+"被执行了" + i + "次");
                }
            });
            // 启动线程
            t1.start();
            t2.start();
         
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        }
    }
    
    

    image-20230523201930011


总结

通过Thread类实现多线程主要有以下几步:

  1. 创建一个类,继承Thread类重写run方法(或者直接使用匿名内部类的方式直接在实现run方法)
  2. 调用无参构造器,创建Thread对象
  3. 调用Thread对象的start方法启动线程

2.2 实现Runnable接口

  • 方式一:传统写法

    • Step1:编写一个类实现Runnable接口,然后重写run方法

      package com.hhxy.runnable;
      
      public class MyRunnable implements Runnable{
      
          /**
           * 线程任务,当Runnable对饮的Thread对象调用start方法,就立刻执行
           */
          @Override
          public void run() {
              for (int i = 0; i < 10; i++) {
                  // 由于没有继承Thread类,所以不能调用getName获取线程名
                  System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
              }
          }
      }
      
    • Step2:编写一个测试类,创建Step1编写的Runnable实现类对象,创建Thread对象,调用Thread的有参构造,将Runnable对象放入Thread构造器中

      package com.hhxy.test;
      
      import com.hhxy.runnable.MyRunnable;
      
      public class RunnableTest {
          public static void main(String[] args) {
              // 创建Runnable对象,表示线程任务
              MyRunnable myRunnable = new MyRunnable();
              // 创建Thread对象
              Thread t1 = new Thread(myRunnable);
              Thread t2 = new Thread(myRunnable);
              // 启动线程
              t1.start();
              t2.start();
      
              for (int i = 0; i < 10; i++) {
                  System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
              }
          }
      }
      

      image-20230523212743523

  • 方式二:匿名内部类写法

    package com.hhxy.test;
    
    import com.hhxy.runnable.MyRunnable;
    
    public class RunnableTest {
        public static void main(String[] args) {
            // 创建Thread对象(直接使用匿名内部类实现Runnable接口)
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
                    }
                }
            });
            Thread t2 = new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
                }
            });
    
            // 启动线程
            t1.start();
            t2.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        }
    }
    

    image-20230523212723729


总结

  1. 创建一个类,实现Runnable接口并重写run方法(或者使用匿名内部类的方式直接实现run方法)
  2. 调用有参构造器,创建Thread对象
  3. 调用Thread对象的start方法启动线程

2.3 利用Callable和Futrue接口

  • Step1:创建一个类,实现Callable接口,重写call方法

    package com.hhxy.callable;
    
    import java.util.concurrent.Callable;
    
    public class MyCallable implements Callable<String> {
        /**
         * 线程任务
         * @return 返回线程任务执行后的线程结果
         */
        @Override
        public String call() throws Exception {
            for (int i = 0; i < 10; i++) {
                // 由于没有继承Thread类,所以不能调用getName获取线程名
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
            return Thread.currentThread().getName() + "线程执行完毕!";
        }
    }
    

    注意:类的泛型要与call方法的返回值类型保持一致

  • Step2:编写测试类,创建Callable对象,调用有参构造器(参数为Callable对象)创建FutureTask对象,调用有参构造器(参数为FutureTask对象)创建Thread对象,调用Thread对象的start方法

    package com.hhxy.test;
    
    import com.hhxy.callable.MyCallable;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class CallableTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 创建Callable对象
            MyCallable myCallable = new MyCallable();
            // 创建FutureTask对象
            FutureTask ft1 = new FutureTask<>(myCallable);
            FutureTask ft2 = new FutureTask<>(myCallable);
            // 创建Thread对象
            Thread t1 = new Thread(ft1);
            Thread t2 = new Thread(ft2);
    
            // 启动线程
            t1.start();
            t2.start();
    
            // 获取线程任务执行后的结果
            String result1 = ft1.get().toString();
            String result2 = ft2.get().toString();
            System.out.println(result1);
            System.out.println(result2);
    
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        }
    }
    

    注意:一个Thread对象要对应一个FutureTask对象,如果两个Thread对象共用一个FutureTask对象,获取线程任务的结果会是一致的,并且结果以第一个线程任务的结果为准

    image-20230523215415364


总结

  1. 创建一个类,实现Callable接口并重写call方法
  2. 创建Callable对象,调用有参构造器(参数为Callable对象)创建FutureTask对象,调用有参构造器(参数为FutureTask对象)创建Thread对象
  3. 调用Thread对象的start方法启动线程
  4. 调用FutureTask对象的get方法,获取线程任务执行后的结果

2.4 三种方式的比较

image-20230523220113375

  • 如果我们想要获取线程任务的执行结果,请使用方式三
  • 如果我们不需要获取线程任务的执行结果,同时对扩展性要求不要,请使用方式一
  • 如果我们不需要获取线程任务的执行结果,同时对扩展性要求较高,请使用方式二

3、Thread类常用API

API介绍

方法名说明
public void run()在线程开启后,run()方法将被自动调用执行
public synchronized void start()开启线程
public final synchronized void setName(String name)为线程命名
public final String getName()获取线程名
public final void setPriority(int newPriority)设置线程的优先级
public final intgetPriority()获取线程的优先级
public final void setDaemon(boolean on)设置为守护线程
public final void join()插入线程/插队线程
public static native void yield();出让线程/礼让线程
public static native Thread currentThread();获取当前线程
public static native void sleep(long millis);让线程休眠指定时间(单位ms)
public final void wait()当前线程等待,直到被其他线程唤醒
public final native void notify();随机唤醒单个线程
public final native void notifyAll();唤醒所有线程
public State getState()获取线程状态

备注

  1. ;结尾的是成员变量,而()结尾的是方法
  2. 优先级值越大,越优先执行。默认是5,最小值是1,最大值是10

3.1 守护线程

  • 守护线程,就是“备胎线程”,当主线程结束后,守护线程会结束(但不是立即结束,而是执行一段时间后结束)

守护线程的应用场景:当我们在进行QQ聊天时,主线程就是QQ程序,而当我们发送文件时,就可以开启一个守护线程,这个守护线程单独用于发送文件,当我们关闭QQ时,QQ这个主线程就结束了,而此时守护线程也没有存在的必要了,所以此时也会随着主线程的结束而结束

public class DaemonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        });

        // 将t2线程设置为t1的守护线程,t1结束后,t2也会跟着结束(但不是立即结束)
        t2.setDaemon(true);

        t1.start();
        t2.start();

//        for (int i = 0; i < 100; i++) {
//            System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
//        }
    }
}

可以看到,当t1执行完,此时整个程序中只有守护线程,此时JVM就会关闭守护线程(注意,如果我们开启主线程的打印,则t1执行完后,t2守护线程不会结束,因为此时系统中除了守护线程,还存在主线程,并不是只剩守护线程)

image-20230608165926061

3.2 礼让线程

  • 礼让线程,让出当前CPU

在需要多个线程协作、顺序执行的场景中,礼让线程是一种比较常用的线程协作方法,它可以让线程执行的顺序更加合理,提高系统的并发性能。

public class YieldTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
                // 让出当前线程的CPU
                Thread.yield();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        });

        t1.start();
        t2.start();
    }
}

可以发现当当前系统中同时存在其它线程时,Thread-0只会被执行一次,这就是礼让线程的一个特性:

image-20230608170553424

3.3 插入线程

  • 插入线程,让当前线程等待线程t执行完成后再继续执行

    它的应用场景较少,使用起来也要十分小心,因为很容易发生死锁

    public class JoinTest {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
                }
            });
            t.start();
            // 将t设置为插入线程,会阻塞当前线程,直到t执行完才重新执行当前线程
            t.join();
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "被执行了" + i + "次");
            }
        }
    }
    

image-20230608171529176

3.4 线程的生命周期

image-20230608173644133

image-20230609152856122

Java里没有定义运行态,因为当线程运行后直接将当前线程交给了CPU,此时JVM就不需要管这个线程了,所以Java中线程实际的状态只有6个

5、线程安全问题

5.1 synchronized

synchronized是Java中用来实现线程同步的关键字,它可以让多个线程在访问共享资源时,保证同一时刻只有一个线程访问,从而避免线程间的数据竞争和不一致性,实现线程安全。

示例

多线程买票

package com.hhxy.test;

/**
 * @author ghp
 * @date 2023/6/8
 * @title
 * @description
 */
public class ThreadSafeTest {

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

class MyThread extends Thread {

    int ticket = 0;

    @Override
    public void run() {
        while (true) {
            if (ticket < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println("正在卖第" + ticket + "张票");
            } else {
                break;
            }
        }
    }
}

当前代码存在,一下问题,每一个线程对于票数量的计算都是独立的,命名只有100张票,但是让三个线程来买,却卖了300张:

image-20230608193557945

同时还会出现超卖问题:

image-20230608194105696

1)代码优化:将ticket使用static修饰,这样多个线程就可以共享一个变量了

    static int ticket = 0;

但是仍然会出现这种情况,只是比例大幅度下降了:

image-20230608193920778

同样仍然会出现超卖问题!

2)代码优化:使用synchronized对同步代码块进行上锁

注意synchronized锁住的对象必须是唯一的

package com.hhxy.test;

public class ThreadSafeTest {

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

class MyThread extends Thread {

    //    int ticket = 0;
    static int ticket = 0;

    @Override
    public void run() {
        while (true) {
            synchronized (ThreadSafeTest.class) {
                if (ticket < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                } else {
                    break;
                }
            }
        }
    }
}

温馨提示synchronized不仅可以锁代码块,还可以锁方法。锁方法,不能自己指定,非静态的锁住的是this,静态的是当前类的字节码文件对象

5.2 Lock

Lock是JDK5提供的一种全新的锁对象,位于java.util.concurrent.locks包下,Lock提供了比使用synchronized方法和语句更为广泛的锁操作,通过lock()获取锁,通过unlock()释放锁。Lock是一个接口,不能够直接实例化,一般我们是使用它的实现类ReentrantLock来实例化。

package com.hhxy.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author ghp
 * @date 2023/6/8
 * @title
 * @description
 */
public class ThreadSafeTest2 {

    public static void main(String[] args) {
        MyThread2 t1 = new MyThread2();
        MyThread2 t2 = new MyThread2();
        MyThread2 t3 = new MyThread2();
        t1.start();
        t2.start();
        t3.start();
    }
}



class MyThread2 extends Thread {

    //    int ticket = 0;
    static int ticket = 0;

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticket < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

可以看到又出现了多个窗口卖同一张票的情况:

image-20230608202258730

出现这个问题的原因很简单,因为Lock对象没有加static,导致每创建一个MyThread2对象,都会新建一个Lock对象,所以我们需要使用static修饰Lock对象

    static Lock lock = new ReentrantLock();

image-20230608202509443

6、等待唤醒机制

等待唤醒机制是Java中常见的线程同步机制之一,它通过Object类的wait()和notify()/notifyAll()方法实现线程间的通信,实现“生产者-消费者”模型等多线程编程场景。

示例

示例一:

这里将利用wait()notify()/notifyAll()方法实现等待唤醒机制

image-20230609145427471

  • 桌子:用来放面条,同时记录食客消费面条的数量,以及桌子上面条的数量

    public class Desk {
        // 消费者最大能消费的食物数量
        public static int count = 10;
        // 桌子上食物的数量 0-桌子上没有食物 1-桌子上有食物
        public static int foodFlag = 0;
        // 锁对象,用于上锁
        public static final Object lock = new Object();
    }
    
  • 生产者:生产面条

    public class Cook extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (Desk.lock) {
                    // 判断美食家是否还能吃下
                    if (Desk.count == 0) {
                        // 美食家已经吃饱了
                        break;
                    } else {
                        // 美食家还能吃,判断桌子上是否有食物
                        if (Desk.foodFlag == 0) {
                            // 桌子上没有食物,厨师做面条,然后唤醒正在等待的美食家
                            Desk.foodFlag++;
                            System.out.println("厨师做了" + Desk.foodFlag + "碗面条");
                            Desk.lock.notifyAll();
                        } else {
                            // 桌子上有食物,厨师等待
                            try {
                                System.out.println("厨师等待……");
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            }
        }
    }
    
  • 消费者:消费面条

    public class Foodie extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (Desk.lock) {
                    // 判断美食家是否吃饱
                    if (Desk.count == 0) {
                        // 美食家已经吃饱了
                        break;
                    } else {
                        // 美食家还没有吃饱,判断桌子上是否有食物
                        if (Desk.foodFlag == 1) {
                            // 桌子上有食物
                            Desk.count--;
                            Desk.foodFlag--;
                            System.out.println("美食家还能吃" + Desk.count + "碗面");
                            // 唤醒美食家,让他继续做面
                            Desk.lock.notifyAll();
                        } else {
                            // 桌子上没有食物
                            try {
                                System.out.println("美食家等待……");
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            }
        }
    }
    
  • 测试类:

    public class Main {
        public static void main(String[] args) {
            Cook cook = new Cook();
            Foodie foodie = new Foodie();
            cook.start();
            foodie.start();
        }
    }
    

    image-20230609145109448

示例二:

这里将使用阻塞队列来实现等待唤醒机制

备注:阻塞队列(Blocking Queue)是Java中的一种线程安全的队列,它支持在队列为空时阻塞获取元素,或者在队列已满时阻塞插入元素,可以很好地用于实现生产者-消费者模型等多线程编程场景。

  • 桌子:

    public class Desk {
        // 消费者最大能消费的食物数量
        public static int count = 10;
    }
    
    
  • 生产者:

    public class Cook extends Thread {
        ArrayBlockingQueue<String> queue;
    
        public Cook(ArrayBlockingQueue queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            while (true) {
                // 判断美食家是否还能吃下
                if (Desk.count == 0) {
                    // 美食家已经吃饱了
                    break;
                } else {
                    // 美食家还能吃,判断桌子上是否有食物
                    if (queue.isEmpty()) {
                        // 桌子上没有食物,厨师做面条
                        try {
                            queue.put("面条");
                            System.out.println("厨师做了1碗面条");
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        System.out.println("厨师等待……");
                    }
                }
            }
        }
    }
    
  • 消费者:

    public class Foodie extends Thread {
        ArrayBlockingQueue<String> queue;
    
        public Foodie(ArrayBlockingQueue queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            while (true) {
                // 判断美食家是否吃饱
                if (Desk.count == 0) {
                    // 美食家已经吃饱了
                    break;
                } else {
                    // 美食家还没有吃饱,判断桌子上是否有食物
                    if (queue.isEmpty()) {
                        // 桌子上没有食物了,美食家等待
                        System.out.println("美食家等待……");
                    } else {
                        // 桌子上有食物
                        try{
                            Desk.count--;
                            String food = queue.take();
                            System.out.println("美食家吃了1碗面条,美食家还能吃" + Desk.count + "碗" + food);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
    
            }
        }
    }
    
  • 测试类:

    public class Main {
        public static void main(String[] args) {
            ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
            Cook cook = new Cook(queue);
            Foodie foodie = new Foodie(queue);
            cook.start();
            foodie.start();
        }
    }
    

    image-20230609152220481

    打印出现重复,是由于打印语句在锁的外面,阻塞队列内部是使用了Lock锁,最终的实际效果是和示例一一致的,只是打印语句会发生错乱,并不影响最终效果

7、综合案例

7.1 售票

需求:一共有100张电影票,可以在两个窗口领取,假设每次领取的时间为100毫秒,请用多线程模拟卖票过程并打印剩余电影票的数量

  • 测试类:

    public class Main {
    
        public static void main(String[] args) {
            // synchronized实现
            Thread t1 = new MyThread();
            Thread t2 = new MyThread();
            // lock实现
    //        Thread t1 = new MyThread2();
    //        Thread t2 = new MyThread2();
            t1.start();
            t2.start();
        }
    }
    
    
  • 线程类:

    1)synchronized实现:

    public class MyThread extends Thread {
        public static int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                if (ticket==0){
                    break;
                }else {
                    synchronized (MyThread.class) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        if (ticket > 0) {
                            ticket--;
                            System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + ticket);
                        }
                    }
                }
            }
        }
    }
    

    2)lock实现:

    public class MyThread2 extends Thread {
        public static int ticket = 100;
        public static final Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                if (ticket == 0) {
                    break;
                } else {
                    lock.lock();
                    try {
                        Thread.sleep(100);
                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + ticket);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
    }
    

7.2 赠送礼物

需求:有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来。

  • 测试类:和7.1一样,略

  • 线程类:

    public class MyThread extends Thread {
        public static int count = 100;
    
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (MyThread.class) {
                    if (count < 10) {
                        break;
                    } else {
                        count--;
                        System.out.println(Thread.currentThread().getName() + "送出一个礼物,当前礼物还剩" + count);
                    }
                }
            }
        }
    }
    

    备注:这里有一个小疑惑,synchronized必须要把if-else全部锁住才能成功,如果和7.1一样,只锁else代码,会导致多多送一个礼物,线程1送出第90个礼物后,线程2还会送出第91个礼物,全部锁住就不会发生这样的事情。

    自我解惑:其实出现这个问题的原因,是由于当线程1进入else中,还没有执行count–操作,此时线程2也进入了else,但此时锁被线程1拿到了,线程2在else中等待,这就导致线程1执行完count–后释放锁,线程2接着又拿到锁执行count–,这就导致,线程1送出第90个礼物后,线程2还会送出第91个礼物,全部锁住就不会发生这样的事情。

7.3 打印数字

需求:同时开启两个线程,共同获取1-100之间的所有数字,输出所有的奇数。

  • 测试类:和7.1一样,略

  • 线程类:

    package com.hhxy.demo05;
    
    /**
     * @author ghp
     * @date 2023/6/9
     * @title
     * @description
     */
    public class MyThread extends Thread {
        public static int n = 0;
    
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (MyThread.class) {
                    if (n == 100) {
                        break;
                    } else {
                        n++;
                        if (n % 2 != 0) {
                            System.out.println(Thread.currentThread().getName() + "找到一个奇数" + n);
                        }
                    }
                }
            }
        }
    }
    

7.4 抢红包

需求:抢红包也用到了多线程。

​ 假设:100块,分成了3个包,现在有5个人去抢。

​ 其中,红包是共享数据。

​ 5个人是5条线程。

  • 测试类:略

  • 线程类:

    package com.hhxy.demo06;
    
    import java.util.Random;
    
    public class MyThread extends Thread {
        // 红包的金额
        public static double money = 100;
        // 红包的个数
        public static int count = 3;
        // 红包的最小值
        public static final double MIN = 0.01;
    
        @Override
        public void run() {
            synchronized (MyThread.class) {
                double price = 0;
                if (count == 1) {
                    // 只剩一个红包了,剩下的钱都是这个红包
                    count--;
                    price = money;
                    money -= price;
                } else {
                    if (count > 1) {
                        count--;
                        Random random = new Random();
                        // 红包的金额范围是 0.01~(money-(count-1)*0.01)
                        double t = random.nextInt(1001 - count);
                        price = t / 100;
                        if (price == 0) {
                            price = 0.01;
                        }
                        money -= price;
                    }
                }
                System.out.println(this.getName() + "抢一个" + price + "元的红包,红包金额还剩" + money + ",红包数量还剩" + count);
            }
        }
    }
    

    image-20230609171824954

7.5 抽奖箱

需求:有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};

创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”

  • 测试类:

        public static void main(String[] args) {
            Thread t1 = new MyThread();
            Thread t2 = new MyThread();
            t1.setName("抽奖箱一");
            t2.setName("抽奖箱二");
            t1.start();
            t2.start();
        }
    

    image-20230609173612338

  • 线程类:

    package com.hhxy.demo07;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * @author ghp
     * @date 2023/6/9
     * @title
     * @description
     */
    public class MyThread extends Thread {
    
        public static List<Integer> list = new ArrayList<Integer>() {{
            add(10);
            add(5);
            add(20);
            add(50);
            add(100);
            add(200);
            add(500);
            add(800);
            add(2);
            add(80);
            add(300);
            add(700);
        }};
    
    
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (MyThread.class) {
                    if (list.size() == 0) {
                        break;
                    } else {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        Collections.shuffle(list);
                        Integer res = list.remove(0);
                        System.out.println(this.getName() + "抽到了" + res + "元,抽奖箱中剩余" + list.size());
                    }
                }
            }
        }
    }
    

7.6 多线程统计并求最大值

需求:

​ 在上一题基础上继续完成如下需求:

​ 每次抽的过程中,不打印,抽完时一次性打印(随机)

​ 在此次抽奖过程中,抽奖箱1总共产生了6个奖项。

​ 分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元

​ 在此次抽奖过程中,抽奖箱2总共产生了6个奖项。

​ 分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元

通过创建共享变量实现:

public class MyThread extends Thread {

    public static List<Integer> list = new ArrayList<Integer>() {{
        add(10);
        add(5);
        add(20);
        add(50);
        add(100);
        add(200);
        add(500);
        add(800);
        add(2);
        add(80);
        add(300);
        add(700);
    }};

    public static List<Integer> list1 = new ArrayList<>();
    public static List<Integer> list2 = new ArrayList<>();


    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    if ("抽奖箱一".equals(this.getName())){
                        System.out.println(list1);
                    }else {
                        System.out.println(list2);
                    }
                    break;
                } else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    Collections.shuffle(list);
                    Integer res = list.remove(0);
                    if ("抽奖箱一".equals(this.getName())){
                        list1.add(res);
                    }else{
                        list2.add(res);
                    }
                    System.out.println(this.getName() + "抽到了" + res + "元,抽奖箱中剩余" + list.size());
                }
            }
        }
    }
}

通过创建局部变量实现:

public class MyThread2 extends Thread {

    public static List<Integer> list = new ArrayList<Integer>() {{
        add(10);
        add(5);
        add(20);
        add(50);
        add(100);
        add(200);
        add(500);
        add(800);
        add(2);
        add(80);
        add(300);
        add(700);
    }};


    @Override
    public void run() {
        List<Integer> currentList = new ArrayList<>();
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    System.out.println(this.getName() + currentList);
                    break;
                } else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    Collections.shuffle(list);
                    Integer res = list.remove(0);
                    currentList.add(res);
                    System.out.println(this.getName() + "抽到了" + res + "元,抽奖箱中剩余" + list.size());
                }
            }
        }
    }
}

7.7 多线程之间的比较

需求:在上一题基础上继续完成如下需求,比较两个线程的最大值

线程类:

package com.hhxy.demo08;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;

/**
 * @author ghp
 * @date 2023/6/9
 * @title
 * @description
 */
public class MyCallable implements Callable<Integer> {
    public static List<Integer> list = new ArrayList<Integer>() {{
        add(10);
        add(5);
        add(20);
        add(50);
        add(100);
        add(200);
        add(500);
        add(800);
        add(2);
        add(80);
        add(300);
        add(700);
    }};

    @Override
    public Integer call() throws Exception {
        List<Integer> currentList = new ArrayList<>();
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    System.out.println(Thread.currentThread().getName() + currentList);
                    break;
                } else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    Collections.shuffle(list);
                    Integer res = list.remove(0);
                    currentList.add(res);
                    System.out.println(Thread.currentThread().getName() + "抽到了" + res + "元,抽奖箱中剩余" + list.size());
                }
            }
        }
        // 获取当前线程抽取到的最大值
        int max = 0;
        if (currentList.size()!=0){
            max = Collections.max(currentList);
        }
        return max;
    }
}

测试类:

package com.hhxy.demo08;


import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // Callable
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> f1 = new FutureTask<>(myCallable);
        FutureTask<Integer> f2 = new FutureTask<>(myCallable);
        Thread t1 = new Thread(f1);
        Thread t2 = new Thread(f2);

        t1.setName("抽奖箱一");
        t2.setName("抽奖箱二");
        t1.start();
        t2.start();
        System.out.println("抽奖箱一的最大值" + f1.get());
        System.out.println("抽奖箱二的最大值" + f2.get());
    }
}

8、线程池

  • 什么是线程池

    线程池是一种多线程处理方式,它可以有效地管理和调度多个线程的执行。在使用线程池的情况下,可以避免因为创建大量线程而导致系统性能下降、内存消耗过大等问题。线程池中的线程都是已经创建好的线程对象,并保存在线程池中,每个线程可以执行多个任务,任务执行完毕后并不会立刻销毁线程,而是会保留在池中等待下次执行。

  • 为什么需要线程池

    在多线程编程中,往往需要创建大量的线程来执行任务。但是,直接创建线程会导致以下问题:

    1. 系统资源浪费:对于一些线程生命周期很短的任务(比如执行完一段代码后就会结束的任务),频繁地创建、销毁线程会消耗大量的系统资源,并且增加了系统开销。

    2. 系统性能下降:当同时需要执行大量的任务时,不加限制地创建线程可能会导致系统性能下降、运行速度变慢,因为线程的创建和销毁开销非常大。

    3. 系统不稳定:在高并发情况下,线程过多时会导致系统崩溃、运行不稳定。

    线程池的作用就是解决以上问题。它可以避免频繁地创建、销毁线程,可以提前准备好一定数量的线程,让线程复用,从而降低创建和销毁线程的开销,同时还可以严格地限制线程的数量和执行时间,实现对线程的调度和管理。

    使用线程池的好处:

    1. 提高系统效率:通过线程的复用和调度,可以充分利用系统资源,提高系统效率。

    2. 提高程序响应速度:线程池中的线程可以随时响应任务,从而提高程序的响应速度。

    3. 避免系统由于线程过多而不稳定:由于可以控制线程的数量,线程池可以避免系统出现由于线程过多而导致的不稳定状态,提高系统的可靠性。

    总而言之,线程池在多线程编程中是一种非常重要的工具,可以避免系统性能问题和不稳定问题,提高系统效率和可靠性。

8.1 自定义线程

  • 如何创建线程池

    package com.hhxy.demo09;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Demo01 {
        public static void main(String[] args) throws InterruptedException {
            // 创建线程池
            ExecutorService pool = Executors.newCachedThreadPool();
            // 提交任务
            pool.submit(new MyRunnable());
            // main线程休眠1s,这样的目的是为了让Thread-0尽快执行完任务,之后就都会是Thread-0执行
    //        Thread.sleep(1000);
            pool.submit(new MyRunnable()); // 不光可以
    //        Thread.sleep(1000);
            pool.submit(new MyRunnable());
    //        Thread.sleep(1000);
            // 销毁线程池(线程池一般不销毁)
            pool.shutdown();
        }
    }
    
  • 线程池相关概念

    • 先提交的任务不一定限制性

    • 当核心线程真在忙,且线程池等待队列中的任务已满时,会创建临时线程

    • 线程池能最大处理的任务数:核心线程数量+等待队列的长度+临时线程的数量,超过这个长度的任务会拒绝服务

      拒绝策略:

      • AbortPolicy:丢弃并抛出异常RejectedExecutionException异常(默认策略)
      • DiscardPolicy:丢弃任务,但不抛出异常(不推荐)
      • DiscardOldstPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中
      • CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行
package com.hhxy.demo10;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        /*
        参数一:核心线程数量 >=0
        参数二:最大线程数 >=核心线程数量
        参数三:空闲线程最大存活时间 >=0
        参数四:时间单位 
        参数五:任务队列 !=null
        参数六:创建线程工厂 !=null
        参数七:任务的拒绝策略 !=null
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, //  核心线程数量,不能小于0
                6, // 最大线程数量,不能小于核心线程数量,临时线程数量=最大线程数量-核心线程数量
                60, // 时间值
                TimeUnit.SECONDS, // 时间单位
                new ArrayBlockingQueue<>(3), // 阻塞队列长度
                Executors.defaultThreadFactory(), // 获取线程的方式
                new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
        );
    }
}

8.2 线程池最大并行数

  • CPU密集型运算:最大并行数+1

    第一种方式:

    image-20230614161653363

    从这里可以看出,我笔记本的最大并行数是16

    第二种方式:

            int count = Runtime.getRuntime().availableProcessors();
            System.out.println("当前电脑最大逻辑处理数:"+ count); // 16
    
  • I/O密集型运算: 最大并行数 ∗ 期望 C P U 利用率 ∗ 总时间 ( C P U 计算时间 + 等待时间 ) C P U 计算时间 最大并行数*期望CPU利用率*\frac{总时间(CPU计算时间+等待时间)}{CPU计算时间} 最大并行数期望CPU利用率CPU计算时间总时间(CPU计算时间+等待时间)

    比如:从本地文件中,读取两个数据(耗时1秒速),并进行相加(耗时1秒钟)

    则此时计算式为: 16 ∗ 100 % ∗ ( 2 s ) / 1 s = 16 16 *100\%*(2s)/1s = 16 16100%(2s)/1s=16,所以此时线程池的最大数量为16

    CPU的等待时间可以使用 thread dump 进行计算

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

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

相关文章

wsl安装ubuntu并设置gnome图形界面详细步骤(win11+ubuntu18)

0.前言 wsl确实是个好东西&#xff0c;不过之前配了好几次都没有成功&#xff0c;因为wsl本身确实是有bug。当时配的时候查到GitHub上的一个issue还没被修好。现在重新配一下。 我的环境是Windows11家庭版。区别于win10&#xff0c;win11安装完默认就是wsl2。 1.下载 首先打…

Linux之管理联网

目录 Linux之管理联网 rhel8与7的区别 NetworkManager 定义 NM能管理各种网络 使用方法 使用NM的原因 nmcli使用方法 nmcli的两个常用命令 nmcli connection 定义 两种状态 nmcli device 定义 四种状态 nmcli常用命令 查看ip&#xff08;类似于ifconfig、ip addr&a…

每周练习学习(一)1.1--nc的使用与系统命令执行

1.test_your_nc ----nc的使用与系统命令执行1 平台&#xff1a;buuctf 之前在攻防开学来考核的时候&#xff0c;遇到过一个nc的题&#xff0c;但自己完全不知道nc是什么意思&#xff0c;所以现在为了增强一点自己的知识面&#xff08;说的好听&#xff0c;也就是为了快期末…

4.8 Socket介绍 4.9字节序 4.10字节序转换函数

4.8 Socket介绍 所谓 socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲&#xff0c;套接字上…

使用EasyCode自定义模板,自动生成代码

首先创建spring boot项目&#xff0c;导入相关依赖是必须的#### 导入依赖&#xff1a; pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001…

设计模式(十八):行为型之观察者模式

设计模式系列文章 设计模式(一)&#xff1a;创建型之单例模式 设计模式(二、三)&#xff1a;创建型之工厂方法和抽象工厂模式 设计模式(四)&#xff1a;创建型之原型模式 设计模式(五)&#xff1a;创建型之建造者模式 设计模式(六)&#xff1a;结构型之代理模式 设计模式…

【RabbitMQ教程】第六章 —— RabbitMQ - 延迟队列

&#x1f4a7; 【 R a b b i t M Q 教程】第六章—— R a b b i t M Q − 延迟队列 \color{#FF1493}{【RabbitMQ教程】第六章 —— RabbitMQ - 延迟队列} 【RabbitMQ教程】第六章——RabbitMQ−延迟队列&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人…

数据提取概述

数据提取概述 一、响应内容的分类 在发送请求获取响应之后&#xff0c;可能存在多种不同类型的响应内容&#xff1b;而且很多时候&#xff0c;我们只需要响应内容中的一部分数据 结构化的响应内容 json字符串 可以使用re、json等模块来提取特定数据json字符串的例子如下图 x…

一.《UE5夜鸦》被动技能名字CALL和描述CALL

被动技能名字描述CALL 搜索名字寻找名字库的名字对象 1.搜索我们找名字,肯定是需要用CE搜索名字拉,由于是韩文,我们用翻译器截图获取韩文字符串 2.开始截图获取 3.我们用CE搜索字符串,这里注意是UTF-16勾上,找到了4个完全一样的结果, 我们修改确认哪一个才是真正技能库的名字 4…

[读论文]Referring Camouflaged Object Detection

摘要 In this paper, we consider the problem of referring camouflaged object detection (Ref-COD), a new task that aims to segment specified camouflaged objects based on some form of reference, e.g. , image, text. We first assemble a large-scale dataset, ca…

NLP——Translation 机器翻译

文章目录 为什么翻译任务困难Statistical Machine TranslationAlignment 对齐summary Neural Machine Translation如何训练 Neural MTloss 设定Trainingdecoding at Test TimeExposure BiasExhaustive Search DecodingBeam Search Decoding什么时候解码停止summary Attention M…

Linux 文件 io 的原子性与 O_APPEND 参数

转自&#xff1a;https://www.cnblogs.com/moonwalk/p/15642478.html 1. 文件 io 1.1 open() 系统调用 在进程/线程 struct task ---> struct files_struct 结构体中&#xff0c;添加一项新打开的文件描述符 fd&#xff0c;并指向文件表创建一个新的 struct file 即文件表…

Debezium系列之:为Debezium集群JMX页面增加监控,JMX页面出现异常时发送飞书告警,确保任务能够获取debezium集群指标

Debezium系列之:为Debezium集群JMX页面增加监控,JMX页面出现异常时发送飞书告警,确保任务能够获取debezium集群指标 一、需求背景二、相关技术博客三、监控JMX页面状态四、发送飞书告警五、定时执行脚本六、告警效果展示七、总结和延展一、需求背景 下游任务需要使用Debeziu…

【正项级数】敛散性判别

Hi&#xff01;&#x1f60a;&#x1f970;大家好呀&#xff01;欢迎阅读本篇文章正项级数敛散性判别。由于最近时间比较紧张&#xff0c;所以几乎没有使用公式编辑器&#xff0c;更多的内容多以图片形式呈现&#xff0c;希望本篇内容对你有帮助呀&#xff01; 可能对你有帮助的…

JavaSE进阶--网络编程

文章目录 前言一、网络编程二、通信1、两个重要的要素2、通信协议 三 、Socket四、基于TCP的网络编程1、单向通信1.1 服务端1.2 客户端 2、双向通信2.1 服务端2.2 客户端 3、传输对象3.1 服务端3.2 客户端 4、保持通信4.1 服务端4.2 客户端 五、基于UDP的网络编程1、单向通信1.…

深入理解深度学习——Transformer:解码器(Decoder)的多头注意力层(Multi-headAttention)

分类目录&#xff1a;《深入理解深度学习》总目录 相关文章&#xff1a; 注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;基础知识 注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;注意力汇聚与Nadaraya-Watson核回归 注意力机制&…

AI 绘画(1):生成一个图片的标准流程

文章目录 文章回顾感谢人员生成一个图片的标准流程前期准备&#xff0c;以文生图为例去C站下载你需要的绘画模型导入参数导入生成结果&#xff1f;可能是BUG事后处理 图生图如何高度贴合原图火柴人转角色 涂鸦局部重绘 Ai绘画公约 文章回顾 AI 绘画&#xff08;0&#xff09;&…

Fluent基于profile定义变量

1 概述 Profile中文可称呼为数据表&#xff0c;是Fluent中一种定义边界条件和体积域条件的方式。数据表主要用于将实验、第三方软件等其他数据源的物理场分布数据传递给Fluent。 Profile文件为CSV或PROF格式的文本文件&#xff0c;记录了物理场分布规律。 profile文件示意&…

智警杯初赛复现

eee考核的时候搭建环境出了问题。。虽然有点久远&#xff0c;但还能看看 1.克隆centos 先查看第一台的ip ifconfig 编辑另外两台 进入根目录 cd/ 编辑 vim /etc/sysconfig/network-scripts/ifcfg-ens33 更改项 IPADDR192.168.181.4 # 设置为想要的固定IP地址重启 2.…

K8S 基本概念

功能 1. 自动装箱 基于容器对应用运行环境的资源配置要求自动部署应用容器 2. 自我修复(自愈能力) 当容器失败时&#xff0c;会对容器进行重启。 当所部署的 Node 节点有问题时&#xff0c;会对容器进行重新部署和重新调度 当容器未通过监控检查时&#xff0c;会关闭此容器直到…