【Java多线程】1——多线程知识回顾

1 多线程知识回顾

⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
笔记仓库👉https://github.com/A-BigTree/tree-learning-notes
个人主页👉https://www.abigtree.top
⭐⭐⭐⭐⭐⭐


如果可以,麻烦各位看官顺手点个star~😊

如果文章对你有所帮助,可以点赞👍收藏⭐支持一下博主~😆


文章目录

  • 1 多线程知识回顾
    • 1.1 基础概念
      • 1.1.1 程序、进程、线程
        • 程序
        • 进程
        • 线程
      • 1.1.2 串行、并行、并发
        • 串行
        • 并行和并发
      • 1.1.3 sleep()和wait()
        • `sleep()`进入等待状态不释放锁
        • `wait()`进入等待状态释放锁
        • 小结
      • 1.1.4 同步方法和同步代码块
        • 相同点
        • 区别
        • 小结
    • 1.2 创建多线程
      • 1.2.1 继承Thread类
        • 实现方法
        • `start()` 方法和 `run()` 方法区别
        • 评价
      • 1.2.2 实现Runnable接口
        • 实现接口形式
        • 匿名内部类形式
        • Lambda表达式
      • 1.2.3 使用Callable接口配合FutureTask
        • `FutureTask`类和`Runnable`接口的关系
        • Future接口
        • `FutureTask`类的构造器
        • Callable接口
        • 测试代码
        • callable和Runnable对比
      • 1.2.4 线程池
        • 参考代码
      • 1.2.5 并行计算
      • 1.2.6 Timer定时任务
      • 1.2.7 Spring异步方法
        • 准备SpringBoot环境
        • 使用异步方法
    • 1.3 线程状态与生命周期
      • 1.3.1 线程状态枚举类
        • 源代码
        • 说明
      • 1.3.2 线程的生命周期
    • 1.4 线程间通信
      • 1.4.1 核心语法
        • `Object` 类的 `wait()` 方法
        • `Object` 类的 `notify()` 方法
        • `Object` 类的 `notifyAll()` 方法
      • 1.4.2 虚假唤醒

1.1 基础概念

1.1.1 程序、进程、线程

程序
  • 程序从开发到发布的过程:源程序(源代码) → 打包封装 → 应用软件 ;

  • 笼统的来说,源程序、应用软件都可以称之为『程序』;

  • 相对于进程、线程来说,程序是一个静态的概念;

进程
  • 内部视角:程序运行起来就是一个进程。所以相对于程序来说,进程是一个动态的概念;
  • 外部视角:站在操作系统的层次上来说,现代的大型操作系统都是支持多进程模式运行的,这样操作系统就可以同时执行很多个任务;
线程

在一个进程中,需要同时处理多个不同任务,每一个任务由一个线程来执行。从这个意义上来说,可以把进程看做是线程的容器。

在这里插入图片描述

1.1.2 串行、并行、并发

串行

多个操作在同一个线程内按顺序执行。这种情况下的工作模式我们往往也称之为:同步。按照同步模式执行的多个操作,当前操作没有结束时,下一个操作就必须等待。处于等待中的状态往往也称为:阻塞(block)。

并行和并发

并行和并发都是以异步的模式来执行操作的。异步工作模式下不同线程内的操作互相不需要等待。

  • 并行:多个 CPU(或 CPU 核心)同时执行多个任务
  • 并发:一个 CPU(或 CPU 核心)同时执行多个任务

1.1.3 sleep()和wait()

二者最关键的区别是下面两点:

  • sleep() 会让线程拿着锁去睡;
  • wait() 会让线程放开锁去睡;
sleep()进入等待状态不释放锁
// 1、创建一个对象,作为锁对象
Object lockObj = new Object();

// 2、创建执行 sleep 的线程
new Thread(()->{

    System.out.println(Thread.currentThread().getName() + " begin");
    // ※ 两个线程使用同一个锁对象,就会存在竞争关系
    synchronized (lockObj) {
        System.out.println(Thread.currentThread().getName() + " get lock");
        try {

            // ※ sleep() 方法拿着锁去睡
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " release lock");

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

}, "thread-a").start();

// ※ 让主线程睡一会儿,确保 a 线程先启动
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}

// 3、创建竞争锁的线程
new Thread(()->{

    System.out.println(Thread.currentThread().getName() + " begin");
    // ※ 两个线程使用同一个锁对象,就会存在竞争关系
    synchronized (lockObj) {
        System.out.println(Thread.currentThread().getName() + " get lock");
    }

    System.out.println(Thread.currentThread().getName() + " end");
}, "thread-b").start();

打印结果:

thread-a begin

thread-a get lock

thread-b begin

thread-a release lock

thread-b get lock

thread-b end

thread-a end

wait()进入等待状态释放锁
// 1、创建一个对象,作为锁对象
Object lockObj = new Object();

// 2、创建执行 sleep 的线程
new Thread(()->{

    System.out.println(Thread.currentThread().getName() + " begin");
    // ※ 两个线程使用同一个锁对象,就会存在竞争关系
    synchronized (lockObj) {
        System.out.println(Thread.currentThread().getName() + " get lock");
        try {

            // ※ wait() 方法放开锁去睡
            lockObj.wait(5000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " release lock");

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

}, "thread-a").start();

// ※ 让主线程睡一会儿,确保 a 线程先启动
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}

// 3、创建竞争锁的线程
new Thread(()->{

    System.out.println(Thread.currentThread().getName() + " begin");
    // ※ 两个线程使用同一个锁对象,就会存在竞争关系
    synchronized (lockObj) {
        System.out.println(Thread.currentThread().getName() + " get lock");
    }

    System.out.println(Thread.currentThread().getName() + " end");
}, "thread-b").start();

打印结果:

thread-a begin

thread-a get lock

thread-b begin

thread-b get lock

thread-b end

thread-a release lock

thread-a end

小结
wait()sleep()
声明位置Object 类Thread 类
影响线程的方式通过调用 wait() 方法的对象影响到线程直接影响当前线程
性质非静态方法静态方法
释放锁资源放开锁进入等待不释放锁进入等待
同步要求必须在同步上下文中使用不要求在同步上下文中
应用场景用于线程间通信用来让线程暂停一段时间

1.1.4 同步方法和同步代码块

相同点

都会用到synchronized关键字

区别
锁对象锁定范围
同步代码块由程序员指定代码块的范围(灵活)
同步方法静态:类.class
非静态:this
整个方法体
小结
  • 结论1:静态同步方法使用类.class作为锁对象;非静态同步方法使用this作为锁对象;
  • 结论2:多个线程如果使用同一个锁对象就会有竞争关系;否则没有竞争关系;

1.2 创建多线程

无论有多少种形式,创建多线程的真正的方法,其实只有两种:

继承 Thread 类

实现 Runnable 接口

其它形式都是这两种方式的变体

1.2.1 继承Thread类

实现方法
  • 第一步:继承 Thread 类;
  • 第二步:重写 run() 方法;
  • 第三步:创建 Thread 子类对象;
  • 第四步:调用 start() 方法启动线程;
public class CreateThread01Extends {

    public static void main(String[] args) {
        DemoThread demo = new DemoThread("AAA");
        demo.start();
    }

}

class DemoThread extends Thread {

    public DemoThread(String threadName) {
        super(threadName);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " thread working ...");
    }
}
start() 方法和 run() 方法区别

调用 run() 方法仅仅只是调用了一个子类中重写的父类方法,并没有真正开启一个新的线程,还是在当前线程运行,也就是 main 线程。

评价

因为 Java 是单继承的,一个类继承了 Thread 类就不能继承其它类,所以通常不采用这个办法创建多线程。

1.2.2 实现Runnable接口

实现接口形式
public class CreateThread02Impl {

    public static void main(String[] args) {

        // 第四步:创建实现了 Runnable 接口的类的对象
        MyRunnableThread runnable = new MyRunnableThread();

        // 第五步:创建 Thread 类对象
        // 参数1:runnable 对象
        // 参数2:线程名称
        Thread thread = new Thread(runnable, "thread 002");

        // 第六步:调用 Thread 对象的 start() 方法启动线程
        thread.start();
    }

}

// 第一步:实现 Runnable 接口
class MyRunnableThread implements Runnable {

    // 第二步:实现 run() 方法
    @Override
    public void run() {

        // 第三步:编写线程中的逻辑代码
        System.out.println(Thread.currentThread().getName() + " is working");
    }
}
匿名内部类形式
// 第一步:以匿名内部类的方式创建 Runnable 接口类型的对象
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // 第二步:编写线程中的逻辑代码
        System.out.println(Thread.currentThread().getName() + " is working");
    }
};

// 第三步:创建 Thread 类对象
// 参数1:runnable 对象
// 参数2:线程名称
Thread thread = new Thread(runnable, "thread 003");

// 第四步:调用 Thread 对象的 start() 方法启动线程
thread.start();
Lambda表达式

声明变量:

// 编写 Lambda 表达式的口诀:
// 复制小括号
// 写死右箭头
// 落地大括号

// 第一步:以匿名内部类的方式创建 Runnable 接口类型的对象
Runnable runnable = () -> {
    // 第二步:编写线程中的逻辑代码
    System.out.println(Thread.currentThread().getName() + " is working");
};

// 第三步:创建 Thread 类对象
// 参数1:runnable 对象
// 参数2:线程名称
Thread thread = new Thread(runnable, "thread 004");

// 第四步:调用 Thread 对象的 start() 方法启动线程
thread.start();

不声明变量:

// 第一步:创建 Thread 类对象并调用 start() 方法启动线程
// 参数1:以Lambda 表达式形式创建的 runnable 对象
// 参数2:线程名称
new Thread(() -> {
    // 第二步:编写线程中的逻辑代码
    System.out.println(Thread.currentThread().getName() + " is working");
}, "thread 005").start();

1.2.3 使用Callable接口配合FutureTask

该方案最核心的价值是:使用 Callable 接口限定的功能 + Future 接口限定的功能 = 汇总各个线程执行结果 最终执行汇总操作的这一步会被阻塞,直到前面各个线程完成了计算。

FutureTask类和Runnable接口的关系

在这里插入图片描述

从继承关系能够看到,FutureTask本身也间接实现了Runnable接口。FutureTask类的对象也是Runnable接口的实例,可以用于在创建Thread对象时,传入Thread构造器。

Future接口

停止任务:

boolean cancel(boolean mayInterruptIfRunning);

如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true 时才会中断任务。

获取任务的结果:

V get() throws InterruptedException, ExecutionException;

如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。

判断任务是否完成:

boolean isDone();

如果任务完成,则返回true,否则返回false。

FutureTask类的构造器

介绍:

FutureTask 类兼具 RunnableFuture 接口的功能,并方便地将两种功能组合在一起。关于 FutureTask 类的使用有如下建议:

  • 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成;
  • 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态;
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get() 方法;
  • 一旦计算完成,就不能再重新开始或取消计算;
  • get() 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;
  • get() 只执行一次,因此get() 方法放到最后;

可以使用的构造器:

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

根据这个构造器,我们知道,创建 FutureTask 对象时,传入一个 Callable 类型的对象即可。

Callable接口
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

从 call() 方法的声明我们可以看出,它有一个返回值。这个返回值可以将当前线程内计算结果返回。

测试代码
// 1.创建三个FutureTask对象,封装三个线程的执行逻辑
FutureTask<Integer> task01 = new FutureTask<>(() -> {

    int result = (int) (Math.random() * Math.random() * 100);

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

    return result;
});
FutureTask<Integer> task02 = new FutureTask<>(() -> {

    int result = (int) (Math.random() * Math.random() * 1000);

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

    return result;
});
FutureTask<Integer> task03 = new FutureTask<>(() -> {

    int result = (int) (Math.random() * Math.random() * 10000);

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

    return result;
});

// 2.创建三个线程对象,然后启动线程
new Thread(task01, "thread01").start();
new Thread(task02, "thread02").start();
new Thread(task03, "thread03").start();

// 3.上面三个线程执行完成后,可以收集它们各自运算的结果
Integer task01Result = task01.get();
Integer task02Result = task02.get();
Integer task03Result = task03.get();

System.out.println("task01Result = " + task01Result);
System.out.println("task02Result = " + task02Result);
System.out.println("task03Result = " + task03Result);
callable和Runnable对比
Runnable接口Callable接口
重写run()方法重写call()方法
run()没有返回值call()有返回值
run()没有声明抛出异常call()声明抛出Exception
没有汇总各个线程结果的机制有汇总各个线程结果的机制

1.2.4 线程池

参考代码
// 1.创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(5);

// 2.给线程池对象分配任务,每一个任务是一个线程
pool.execute(() -> {
    System.out.println(Thread.currentThread().getName() + " " + new Date());
});

pool.execute(() -> {
    System.out.println(Thread.currentThread().getName() + " " + new Date());
});

pool.execute(() -> {
    System.out.println(Thread.currentThread().getName() + " " + new Date());
});

1.2.5 并行计算

List<String> list = Arrays.asList("a", "b", "c", "d", "e");

// 串行计算
list.stream().forEach(System.out::print);

System.out.println();

// 并行计算
list.parallelStream().forEach(System.out::print);

1.2.6 Timer定时任务

// 1、创建 Timer 对象封装定时任务中要执行的操作
// 每一个 Timer 对象会使用一个线程来执行定时任务
Timer timer01 = new Timer();

// 2、调用 schedule() 指定任务和执行周期
// 参数1:timerTask 封装具体任务操作
// 参数2:delay 指定定时任务延迟多久后开始执行
// 参数3:period 指定定时任务执行的时间间隔
timer01.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +" is working");
    }
}, 0, 1000);

Timer timer02 = new Timer();

timer02.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +" is working");
    }
}, 0, 1000);

1.2.7 Spring异步方法

在 Spring 环境下,如果组件 A(假设是 ControllerA)要调用组件 B(假设是 ServiceB)的多个方法,而且希望这些方法能够异步执行。

准备SpringBoot环境

引入依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.5.2</version>
    </dependency>
</dependencies>

创建主启动类:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

创建Service:

@Service
public class DemoService {
    
    public void doSth() {
        System.out.println("Demo Service " + Thread.currentThread().getName());
    }

}

创建Controller:

@RestController
public class DemoController {

    @Autowired
    private DemoService demoService;

    @RequestMapping("/demo/test/async")
    public String callServiceMethod() {

        demoService.doSth();
        demoService.doSth();
        demoService.doSth();
        demoService.doSth();
        demoService.doSth();

        return "success";
    }

}
使用异步方法

开启异步功能:

在主启动类使用 @EnableAsync 注解:

// 开启支持异步方法调用功能
@EnableAsync
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

标记异步方法:

在想要异步调用的方法上使用 @Async 注解:

@Service
public class DemoService {

    // 在想要实现异步调用的方法上加 @Async注解
    @Async
    public void doSth() {
        System.out.println("Demo Service " + Thread.currentThread().getName());
    }

}

1.3 线程状态与生命周期

1.3.1 线程状态枚举类

源代码
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

说明
英文名称中文名称含义
NEW新建线程对象刚创建
RUNNABLE就绪等待 CPU 时间片
RUNNING运行得到了 CPU 时间片,正在执行
BLOCKED阻塞等待同步锁
WAITING等待等待被唤醒
TIMED_WAITING限时等待在进入等待状态时设定了等待时间。时间一到自动回到就绪状态
TERMINATED终止线程因为代码执行完成或抛异常而停止执行

1.3.2 线程的生命周期

在这里插入图片描述

1.4 线程间通信

在多线程模式下进行工作,除了要考虑各个线程之间是否同步、如何竞争锁等问题,还要考虑这样一个问题:线程之间有的时候需要相互配合来共同完成一件事情。 把一个大的任务拆分成多个不同的任务线,每个任务线中都有更小的执行步骤。各个线程之间需要彼此配合:A 线程执行一步唤醒 B 线程,自己等待;B 线程执行一步,唤醒 A 线程,自己等待……

1.4.1 核心语法

Object 类的 wait() 方法

Causes the current thread to wait until another thread invokes the java.lang.Object#notify() method or the java.lang.Object#notifyAll() method for this object.

  • wait() 方法会导致当前线程进入等待状态;
  • 必须另外一个线程调用 notify()notifyAll() 方法来唤醒
  • “for this object” 表示还是要使用 同一个对象 分别调用 wait()notify()notifyAll() 这些方法;
Object 类的 notify() 方法

Wakes up a single thread that is waiting on this object’s monitor. If any threads are waiting on this object, one of them is chosen to be awakened.

  • notify() 方法只唤醒一个线程;
  • 处于等待状态的线程会被存放在对象监视器中的一个数组中;
  • 如果在这个对象的监视器中维护的处于等待状态的线程是多个,那么 notify() 方法会随机唤醒一个;
  • notfiy() 方法无法精确唤醒一个指定的线程,这个需求可以通过 Lock + Condition 方式实现(定制化通信);
Object 类的 notifyAll() 方法

Wakes up all threads that are waiting on this object’s monitor.

唤醒当前对象监视器上等待的**所有**线程。

1.4.2 虚假唤醒

在这里插入图片描述

这种情况,我们称之为:虚假唤醒。

要解决虚假唤醒问题,就需要对线程间通信时的判断条件使用 while 循环结构来执行,而不是 if 分支判断。

在这里插入图片描述

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

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

相关文章

测试开发工程师(QA)职业到底需要干些什么?part6:数据测试工程师QA

常见任务和工作内容 作为数据测试工程师QA&#xff08;Quality Assurance&#xff09;&#xff0c;您的主要职责是确保数据的质量、准确性和一致性。以下是数据测试工程师QA的一些常见任务和工作内容&#xff1a; 数据验证和准确性测试&#xff1a;您将负责验证数据的准确性和…

数论问题代码模板

文章目录 一、质数1.1、质数筛&#xff08;筛1~n中的所有质数&#xff09;1.2、判断一个数是否为质数1.3、对一个数进行质因数分解 二、快速幂2.1、费马小定理——乘法逆元2.2、快速幂 三、约数3.1、N个数的正约数集合3.2、一个数的正约数集合 四、欧拉函数&#xff08;互质数数…

洗地机好用吗?哪款型号值得推荐?看完本文你就知道

在如今社会生活节奏不断加快的情况下&#xff0c;洗地机已经成为众多家庭的必备的清洁设备&#xff0c;面对市面上种类繁多的洗地机&#xff0c;我们常常会发出感叹“洗地机好用吗&#xff1f;洗地机哪个型好用&#xff1f;”等的疑问&#xff0c;今天&#xff0c;为了帮助大家…

一文搞定用python实现终身免费的听书工具

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

C语言程序练习——汉诺塔递归

1. 题目 在终端输入汉诺塔层数n&#xff0c;实现将n层汉诺塔通过三座塔座A、B、C进行排列 2. 代码 #include <stdio.h>int hannuota(int len, int str, int tmp, int dst) {if (1 len){printf("%c -> %c\n", str, dst);}else{hannuota(len-1, str, dst, …

好展位、抢先订!2024第二十二届上海国际涂料展|上海涂料展

致/To: 展会负责人、 市场部、 企划部、 销售部负责人 2024中国国际涂料博览会暨第二十二届中国国际涂料展览会 时间&#xff1a;2024年8月7-9日 地点&#xff1a;上海新国际博览中心 主办方&#xff1a; 中国涂料工业协会 承办方&#xff1a; 北京涂博国际展览有限公司 …

javaSwing坦克大战游戏

在游戏开发领域&#xff0c;坦克大战是一款经典的游戏&#xff0c;其简单而又耐玩的玩法吸引了无数玩家。而今&#xff0c;在Java编程技术的支持下&#xff0c;我们可以用Java Swing技术轻松实现这款经典游戏。本文将介绍如何使用Java Swing技术编写坦克大战游戏&#xff0c;并…

某对象存储元数据集群改造流水账

软件产品&#xff1a;某厂商提供的不便具名的对象存储产品&#xff0c;核心底层技术源自HDFS和Amazon S3&#xff0c;元数据集群采用了基于MongoDB的NOSQL数据库产品和MySQL数据库产品相结合。 该产品的元数据逻辑示意图如下&#xff1a; 业务集群现状&#xff1a;当前第3期建…

Qt 窗口MainWindow(上)

Qt 窗口是通过 QMainWindow 类来实现的。 QMainWindow 是一个为用户提供主窗口程序的类&#xff0c;继承自 QWidget 类&#xff0c;并且提供了⼀个预定义的布局。QMainWindow 包含一个菜单栏&#xff08;menubar&#xff09;、多个工具栏(toolbars)、多个浮动窗口&#xff08;…

JVM第八讲:GC - Java 垃圾回收基础知识

GC - Java 垃圾回收基础知识 本文是JVM第八讲&#xff0c; Java 垃圾回收基础知识。垃圾收集主要是针对堆和方法区进行&#xff1b;程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的&#xff0c;只存在于线程的生命周期内&#xff0c;线程结束之后也会消失&#xff0…

Vue3尚硅谷张天禹笔记

1. Vue3简介 2020年9月18日&#xff0c;Vue.js发布版3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;n 经历了&#xff1a;4800次提交、40个RFC、600次PR、300贡献者 官方发版地址&#xff1a;Release v3.0.0 One Piece vuejs/core 截止2023年10月&#xff0c;最…

Java零基础入门到精通_Day 3

37 switch default&#xff1a; 后面的break;可以省略 38 春夏秋冬 注意事项:在switch语句中&#xff0c;如果case控制的语句体后面不写break&#xff0c;将出现穿透现象&#xff0c;在不判断下一个case值的情况下&#xff0c;向下运行 直到遇到break&#xff0c;或者整体swi…

在Python中进行封装

在Python中&#xff0c;封装是一种面向对象编程&#xff08;OOP&#xff09;的特性&#xff0c;它允许我们将数据&#xff08;属性&#xff09;和操作这些数据的方法&#xff08;函数&#xff09;捆绑在一起&#xff0c;形成一个独立的对象。封装的主要目的是隐藏对象的内部状态…

如何保证缓存与数据库的双写一致性?

如何保证缓存与数据库的双写一致性&#xff1f; 概述同步策略更新缓存还是删除缓存&#xff1a;先操作数据库还是缓存&#xff1a;案例一、先删除缓存&#xff0c;在更新数据库案例二 先操作数据库&#xff0c;再删除缓存 延时双删策略&#xff08;不推荐&#xff09;使用分布式…

Java拆装箱及128陷阱

有以下一段代码&#xff1a; Integer a 123; Integer b 123; int c 123; int d 123; System.out.println(c d); System.out.println(a b); System.out.println(a c); 这段代码运行的结果是什么呢&#xff1f; c d 一定为True。 由于Java中存在自动拆装箱&#xff0…

刷到一个问题还请道友们解疑

问题如上&#xff0c;题目挺简单的&#xff0c;就是插入后排序的思路&#xff0c;我的代码如下&#xff1a; #include <bits/stdc.h>using namespace std; int f(int x,int y){return x < y;//其实要这个没有用&#xff0c;默认是就是从小到大排序 }int main(){int n…

【MySQL】详谈约束

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习计网、mysql和算法 ✈️专栏&#xff1a;MySQL学习 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac…

PostgreSQL中控制文件的解析与恢复

最近遇到有人问起PG中控制文件的一些使用问题,总结了一下。 1、PG控制文件简介 1.1、存储的位置 它的路径位于: 相关信息,可以用命令pg_controldata得到: [10:41:27-postgres@centos2:/var/lib/pgsql/14/data/global]$ pg_controldata -D $PGDATA pg_control version …

git提交和回退

目录 一. git 提交二. git commit 后准备回退&#xff0c;尚未 git push三. git add 添加多余文件 撤销操作四. 更改 Git commit 的默认编辑器五. 撤销某个commit的变更六. 回退到之前的commit状态总结&#xff1a; 一. git 提交 git pull # 更新代码 git status # 查看代码状…

【保姆级讲解如何Stable Diffusion本地部署】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…