Java多线程

简介: Java多线程



面试

多线程的使用场景

>手机app应用的图片加载

>迅雷等下载软件

>Tomcat服务器上的web应用,多个客户端发起请求,Tomcat针对多个请求开辟多个线程处理

stop()和suspend()方法为何不推荐使用

stop():一旦执行,线程就结束了,导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全问题

suspend():与resume()搭配使用,会造成死锁。

线程同步与阻塞关系

同步一定会阻塞,阻塞不一定会同步

为什么wait()和notify()方法要放在同步块中调用

因为调用者必须时同步监视器

饿汉式自身创建了单例,所以时安全的,懒汉式不安全;内部类

Java线程优先级定义

三个常量【1,10】

用什么关键字修饰同步方法

synchronized

synchronzied加在静态方法和普通方法的区别

同步监视器不同。静态:当前类本身 非静态:this

操作系统相关知识

程序、进程与线程

进程作为操作系统调度和分配资源的最小单位

线程最为CPU调度和执行的最小单位

  • 程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
  • 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。如:运行中的QQ,运行中的网易音乐播放器。
  • 每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(生命周期)
  • 程序是静态的,进程是动态的
  • 进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。
  • 现代的操作系统,大都是支持多进程的,支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。
  • 线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。
  • 一个进程同一时间若并行执行多个线程,就是支持多线程的。
  • 线程作为CPU调度和执行的最小单位
  • 一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
  • 下图中,红框的蓝色区域为线程独享,黄色区域为线程共享。

注意:

不同的进程之间是不共享内存的。

进程之间的数据交换和通信的成本很高。

线程调度
  • 分时调度

所有线程轮流使用 CPU 的使用权,并且平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

让优先级高的线程以较大的概率优先使用 CPU。如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

并行与并发
  • 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个CPU上同时执行。比如:多个人同时做不同的事。

  • 并发(concurrency):指两个或多个事件在同一个时间段内发生。即在一段时间内,有多条指令在单个CPU上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。

在操作系统中,启动了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单核 CPU 系统中,每一时刻只能有一个程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多核 CPU 系统中,则这些可以并发执行的程序便可以分配到多个CPU上,实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

创建线程

在Java种创建线程一共两种方式,继承Thread类,和实现Runnable接口

继承Thread类
  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程
//自定义线程类
public class MyThread extends Thread {
    //定义指定线程名称的构造方法
    public MyThread(String name) {
        //调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }
    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+":正在执行!"+i);
        }
    }
}
public class TestMyThread {
    public static void main(String[] args) {
        //创建自定义线程对象1
        MyThread mt1 = new MyThread("子线程1");
        //开启子线程1
        mt1.start();
        //创建自定义线程对象2
        MyThread mt2 = new MyThread("子线程2");
        //开启子线程2
        mt2.start();
        //在主方法中执行for循环
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程!"+i);
        }
    }
}
实现Runnable接口
  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target参数来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法,启动线程。调用Runnable接口实现类的run方法。
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class TestMyRunnable {
    public static void main(String[] args) {
        //创建自定义类对象  线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "长江");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("黄河 " + i);
        }
    }
}
实现Callable接口(jdk5.0新增)

与之前的方式对比:

好处:

call()可以有返回值,更灵活

call()可以使用throws的方式处理异常,更灵活

callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活

缺点:

如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态

/*
 * 创建多线程的方式三:实现Callable (jdk5.0新增的)
 */
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class CallableTest {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
//      接收返回值
        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
使用线程池(jdk5.0新增)

好处

提高了程序执行的效率(因为线程已经提前创建好了)

提高了资源的复用率(因为执行完的线程并未销毁,而是可以继续执行其他的任务)

可以设置相关的参数,对线程池中的线程的使用进行管理

class NumberThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}
class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}
class NumberThread2 implements Callable {
    @Override
    public Object call() throws Exception {
        int evenSum = 0;//记录偶数的和
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                evenSum += i;
            }
        }
        return evenSum;
    }
}
public class ThreadPoolTest {
    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//        //设置线程池的属性
//        System.out.println(service.getClass());//ThreadPoolExecutor
        service1.setMaximumPoolSize(50); //设置线程池中线程数的上限
        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable
        try {
            Future future = service.submit(new NumberThread2());//适合使用于Callable
            System.out.println("总和为:" + future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //3.关闭连接池
        service.shutdown();
    }
}
使用匿名内部类对象来实现线程的创建和启动
//one
new Thread("新的线程!"){
  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(getName()+":正在执行!"+i);
    }
  }
}.start();
//two
new Thread(new Runnable(){
  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(Thread.currentThread().getName()+":" + i);
    }
  }
}).start();

Thread类的常用结构

线程中的常用方法:

start():①启动线程,②调用线程的run()

run():将线程要执行的操作,声明在run()中

currentThread():获取当前执行代码的对应的线程

getName():获取线程名

setName():设置线程名

sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数

yield():静态方法,一旦执行此方法,就释放CPU的执行权

join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞,继续执行

isAlive():判断当前线程是否存活

构造器
  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法系列1
  • public void run() :此线程要执行的任务在此处定义代码。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public String getName() :获取当前线程名称。
  • public void setName(String name):设置该线程名称。
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
常用方法系列2
  • public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
  • void join() :等待该线程终止。
    void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
    void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
  • public final void stop():已过时,不建议使用。强行结束一个线程的执行,直接进入死亡状态。run()即刻停止,可能会导致一些清理性的工作得不到完成,如文件,数据库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。
  • void suspend() / void resume() : 这两个操作就好比播放器的暂停和恢复。二者必须成对出现,否则非常容易发生死锁。suspend()调用会导致线程暂停,但不会释放任何锁资源,导致其它线程都无法访问被它占用的锁,直到调用resume()。已过时,不建议使用。
常用方法系列3

每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。

  • Thread类的三个优先级常量:
  • MAX_PRIORITY(10):最高优先级
  • MIN _PRIORITY (1):最低优先级
  • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
  • public final int getPriority() :返回线程优先级
  • public final void setPriority(int newPriority) :改变线程的优先级,范围在[1,10]之间。

线程的生命周期

JDK1.5之前:5种状态

新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

JDK1.5及之后:6种状态

public enum State {
  NEW,//新建
  RUNNABLE,//可运行
  BLOCKED,//锁阻塞
  WAITING,//无限等待
  TIMED_WAITING,//计时等待
  TERMINATED;//死亡
}

线程同步 — 解决线程安全问题

方式1 : 同步代码块
synchronized(同步监视器){
//需要同步的代码
}

说明:

多个线程必须共用同一个同步监视器

在实现Runnable接口中,同步监视器可以考虑使用:this

在继承Thread类的方式中,同步监视器慎用this,可以考虑使用当前类.class

方式2 : 同步方法

非静态的同步方法,默认同步监视器是this

静态的同步方法,默认同步监视器是当前类本身

同步代码块和同步方法,需要关注的两个事情:①共享数据②操作共享数据的代码

方式3 :实现Lock接口(jdk5.0新增)
  1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例
  2. 调动lock(),实现需共享的代码的锁定
  3. 调用unlock(),释放共享代码的锁定

如果同步代码有异常,要将unlock()写入finally语句块。

class A{
    //1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例
  private final ReentrantLock lock = new ReenTrantLock();
  public void m(){
        //2. 调动lock(),实现需共享的代码的锁定
    lock.lock();
    try{
      //保证线程安全的代码;
    }
    finally{
                //3. 调用unlock(),释放共享代码的锁定
      lock.unlock();  
    }
  }
}

死锁产生原因:

  • 互斥条件
  • 占用且等待
  • 不可抢夺(或不可抢占)
  • 循环等待

以上4个条件,同时出现就会触发死锁。

解决死锁:

死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。

针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。

针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。

针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。

针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

懒汉式的线程安全写法:
//实现1:
package com.atguigu.single.lazy;
public class LazyOne {
    private static LazyOne instance;
    private LazyOne(){}
    //方式1:
    public static synchronized LazyOne getInstance1(){
        if(instance == null){
            instance = new LazyOne();
        }
        return instance;
    }
    //方式2:
    public static LazyOne getInstance2(){
        synchronized(LazyOne.class) {
            if (instance == null) {
                instance = new LazyOne();
            }
            return instance;
        }
    }
    //方式3:
    public static LazyOne getInstance3(){
        if(instance == null){
            synchronized (LazyOne.class) {
                try {
                    Thread.sleep(10);//加这个代码,暴露问题
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(instance == null){
                    instance = new LazyOne();
                }
            }
        }
        return instance;
    }
    /*
    注意:上述方式3中,有指令重排问题
    mem = allocate(); 为单例对象分配内存空间
    instance = mem;   instance引用现在非空,但还未初始化
    ctorSingleton(instance); 为单例对象通过instance调用构造器
    从JDK2开始,分配空间、初始化、调用构造器会在线程的工作存储区一次性完成,然后复制到主存储区。但是需要   
    volatile关键字,避免指令重排。
    */
}
//实现2:内部类
package com.atguigu.single.lazy;
public class LazySingle {
    private LazySingle(){}
    public static LazySingle getInstance(){
        return Inner.INSTANCE;
    }
    private static class Inner{
        static final LazySingle INSTANCE = new LazySingle();
    }
}

synchronized与Lock的对比
  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域、遇到异常等自动解锁
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类),更体现面向对象。
  4. (了解)Lock锁可以对读不加锁,对写加锁,synchronized不可以
  5. (了解)Lock锁可以有多种获取锁的方式,可以从sleep的线程中抢到锁,synchronized不可以

说明:开发建议中处理线程安全问题优先使用顺序为:

• 开发中,Lock ----> 同步代码块 ----> 同步方法

线程通信

为什么要处理线程间通信:

当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。

比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即—— 等待唤醒机制

线程唤醒三个方法

wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用。

notify() : 一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程(如果被wait()的多个线程的优先级相同,则随机唤醒一个)被唤醒的线程从当初被wait的位置继续执行。

notifyAll() : 一旦执行此方法,就会唤醒所有被wait的线程。

此三个方法的使用,必须是在同步代码块或同步方法中。

此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorStateExcpetion异常

此三个方法声明在object类中

//例题:使用两个线程打印 1-100。线程1, 线程2 交替打印
class Communication implements Runnable {
    int i = 1;
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (i <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + i++);
                } else
                    break;
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在Java中的消息同步机制中,notify在前、wait在后是因为wait()和notify()是用于实现线程间通信的方法,它们通常和synchronized一起使用来实现同步。具体原因如下:

  1. wait() 方法是用于使当前线程(也就是调用该方法的线程)等待,放弃锁并进入等待状态,直到其他线程调用notify()或notifyAll()唤醒该线程。
  2. notify() 方法则是用于唤醒在对象上调用wait()方法进入等待状态的线程,并使其进入就绪状态,以便争夺锁。
  3. 由于 wait() 和 notify() 方法是作用于同一个对象锁上的,因此在调用这两个方法时,必须要拥有这个对象的锁,这就要求在调用 wait() 和 notify() 方法时,要先获得对象的锁,在释放对象锁。

因此,在通常的线程同步代码中,我们会先获取对象的锁(通常是通过synchronized关键字),然后调用wait()方法,进入等待状态,直到其他线程调用notify()方法唤醒线程。这就是为什么在 Java 中,notify在前,wait在后。

wait()和sleep()的区别

相同点:一旦执行,当前线程都会进入阻塞状态

不同点:

声明的位置:wait():声明在Object类中

sleep():声明在Thread类中

使用场景不同:wait():只能使用在同步代码块或同步方法中

sleep():可以在任何需要使用的场景

使用在同步代码块或同步方法中:wait():一旦执行,会释放同步监视器

sleep():一旦执行,不会释放同步监视器

结束阻塞的方式:wait():到达指定时间自动结束阻塞,或通过被notify唤醒,结束阻塞

sleep():到达指定时间自动结束阻塞

生产者消费者问题

package prodconsumer;
/**
 * ClassName: ConsumerProducerTest
 * Package: prodconsumer
 * Description:
 * 问题描述:
 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),
 * 如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,
 * 如果店中有产品了再通知消费者来取走产品。
 *问题解析:
 * 1.是否是多线程问题? 是,生产者,消费者
 * 2.是否有共享数据? 有!共享数据是产品
 * 3.是否有线程安全问题? 有!因为有共享数据
 * 4.是否需要处理线程安全问题? 是!如何处理,使用同步机制
 * 5.是否存在线程间的通信?存在。
 * @Author 南城余
 * @Create 2023/11/9 16:46
 * @Version 1.0
 */
public class ConsumerProducerTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor p1 = new Productor(clerk);
        Customer c1 = new Customer(clerk);
        Customer c2 = new Customer(clerk);
        p1.setName("生产者p1");
        c1.setName("消费者c1");
        c2.setName("消费者c2");
        p1.start();
        c1.start();
        c2.start();
    }
}
//资源类
class Clerk{
    private int productNum = 0;
    private static final  int MAX_PRODUCT = 20;
    private static  final int MIX_PRODUCT = 1;
    //增加产品
    public synchronized void addProduct(){
        if(productNum < MAX_PRODUCT){
          productNum++;
            System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");
            this.notify();
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public synchronized  void minusProduct(){
        if (productNum >= MIX_PRODUCT){
            System.out.println(Thread.currentThread().getName()+"消费了第" + productNum + "个产品");
           productNum--;
           this.notify();
        }
        else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
//生产者
class Productor extends Thread{
     private Clerk clerk;
     public Productor(Clerk clerk){
         this.clerk = clerk;
     }
/*是的,这段代码中的`private Clerk clerk;`是一个属性。它是`Producer`类中的一个私有属性,
用来存储引用类型变量`Clerk`的实例。在`Producer`类的构造方法中,我们通过`this.clerk = clerk;`
将传入的`Clerk`对象赋值给了类中的`clerk`属性。这样做的目的是让`Producer`类能够访问和操作`Clerk`对象的属性和方法。
*/
    @Override
    public void run() {
        System.out.println("========生产者开始生产商品=========");
      /*这段代码中的 while(true) 是为了创建一个无限循环,也就是一个始终为真的条件。它会一直循环执行里面的代码,直到程序被显式地中断或者退出。
在这种特定的情况下,这个无限循环被用来模拟一个持续的生产过程。生产者线程会一直执行商品生产的任务,不停地往货架上增加商品,直到某个条件触发停止。
但是,要特别注意的是,使用while(true)需要谨慎,因为它可能导致程序陷入死循环,从而影响整个程序的运行。在实际的生产环境中,我们通常会结合特定的条件来控制循环的终止,如使用 break 语句或者设定一个结束条件。*/
        while(true){
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
                e.printStackTrace();
            }
            clerk.addProduct();
        }
    }
}
//消费者
class Customer extends Thread{
    private Clerk clerk;
    public Customer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run() {
        System.out.println("========消费开者始消费商品=========");
            while(true){
            try {
                Thread.sleep(80);
            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
                e.printStackTrace();
            }
            clerk.minusProduct();
        }
    }
}
结语

我是南城余!欢迎关注我的博客!一同成长!

一名从事运维开发的worker,记录分享学习。

专注于AI,运维开发,windows Linux 系统领域的分享!

 

目录
相关文章
|
4天前
|
Java 测试技术
Java多线程的一些基本例子
【5月更文挑战第17天】Java多线程允许并发执行任务。示例1展示创建并启动两个`MyThread`对象,各自独立打印&quot;Hello World&quot;。示例2的`CounterExample`中,两个线程(IncrementThread和DecrementThread)同步地增加和减少共享计数器,确保最终计数为零。这些例子展示了Java线程的基本用法,包括线程同步,还有如Executor框架和线程池等更复杂的用例。
11 0
|
4天前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
2天前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
24 2
|
2天前
|
安全 Java 容器
Java一分钟之-并发编程:线程安全的集合类
【5月更文挑战第19天】Java提供线程安全集合类以解决并发环境中的数据一致性问题。例如,Vector是线程安全但效率低;可以使用Collections.synchronizedXxx将ArrayList或HashMap同步;ConcurrentHashMap是高效线程安全的映射;CopyOnWriteArrayList和CopyOnWriteArraySet适合读多写少场景;LinkedBlockingQueue是生产者-消费者模型中的线程安全队列。注意,过度同步可能影响性能,应尽量减少共享状态并利用并发工具类。
17 2
|
2天前
|
Java 程序员 调度
Java中的多线程编程:基础知识与实践
【5月更文挑战第19天】多线程编程是Java中的一个重要概念,它允许程序员在同一时间执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动和管理,以及如何通过多线程提高程序的性能和响应性。
|
3天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
13 5
|
3天前
|
消息中间件 安全 Java
理解Java中的多线程编程
【5月更文挑战第18天】本文介绍了Java中的多线程编程,包括线程和多线程的基本概念。Java通过继承Thread类或实现Runnable接口来创建线程,此外还支持使用线程池(如ExecutorService和Executors)进行更高效的管理。多线程编程需要注意线程安全、性能优化和线程间通信,以避免数据竞争、死锁等问题,并确保程序高效运行。
|
3天前
|
存储 Java
【Java】实现一个简单的线程池
,如果被消耗完了就说明在规定时间内获取不到任务,直接return结束线程。
11 0
|
3天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。
|
3天前
|
安全 Java 开发者
Java中的多线程编程:理解与实践
【5月更文挑战第18天】在现代软件开发中,多线程编程是提高程序性能和响应速度的重要手段。Java作为一种广泛使用的编程语言,其内置的多线程支持使得开发者能够轻松地实现并行处理。本文将深入探讨Java多线程的基本概念、实现方式以及常见的并发问题,并通过实例代码演示如何高效地使用多线程技术。通过阅读本文,读者将对Java多线程编程有一个全面的认识,并能够在实际开发中灵活运用。