首页 > 编程语言 >Java 线程机制

Java 线程机制

时间:2024-09-23 15:21:19浏览次数:10  
标签:Java Thread int void final 线程 机制 public

目录

1 进程和线程

2 串行、并行和并发

3 Java实现多线程的三种方法

3.1 继承 Thread 类

3.2 实现 Runnable 接口

3.3 实现 Callable 接口

4 线程常用API

4.1 设置优先级

4.2 线程休眠

4.3 线程让步

4.4 线程重点(加入)

4.5 线程中断

4.6 线程守护

4.7 获取线程ID

4.8 获取当前线程

5 线程的生命周期

5.1 新建状态(NEW)

5.2 可运行状态(RUNNABLE)

5.3 阻塞状态(BLOCKED)

5.4 等待状态(WAITING)

5.5 定时等待状态(TIMED_WAITING)

5.6 终止状态(TERMINATED)

6 线程的安全性

6.1 线程安全

6.1.1 线程安全的特点

6.1.2 实现线程安全的方式

6.1.2.1 synchronized 关键字

6.1.2.2 volatile 关键字 

6.1.2.3  ThreadLocal 变量

6.2 非线程安全

6.2.1 非线程安全问题

7 线程池

7.1 线程池基本概念

7.2 线程池的使用

8 并发集合容器

8.1 ConcurrentHashMap

8.1.1 ConcurrentHashMap 的主要特性

8.1.2 ConcurrentHashMap 的主要方法

8.2 CopyOnWriteArrayList

8.2.1 CopyOnWriteArrayList 的主要特性

8.2.2 CopyOnWriteArrayList 的主要方法

8.3 CopyOnWriteArraySet

8.3.1 CopyOnWriteArraySet 的主要特性

8.3.2 CopyOnWriteArraySet 的主要方法

8.4 ConcurrentLinkedQueue

8.4.1 ConcurrentLinkedQueue 的主要特性

8.4.2 ConcurrentLinkedQueue 的主要方法

8.5 BlockingQueue 接口及其实现

8.5.1 BlockingQueue 的主要特性

8.5.2 BlockingQueue 的主要方法

8.6 注意事项

9 原子操作 

9.1 基本类型的原子操作

9.1.1 AtomicInteger

9.1.2 AtomicLong

9.1.3 AtomicBoolean

9.2 数组类型的原子操作

9.2.1 AtomicIntegerArray

9.2.2 AtomicLongArray

9.2.3 AtomicReferenceArray 

9.3 引用类型的原子操作

9.3.1 AtomicReference

9.3.2 AtomicReferenceFieldUpdater 

9.3.3 AtomicMarkableReference 

9.4 字段类型的原子操作

9.4.1 AtomicIntegerFieldUpdater

9.4.2 AtomicLongFieldUpdater

9.4.3 AtomicStampedReference 


Java 的线程机制是 Java 平台的核心特性之一,它允许程序同时执行多个任务。下面我将详细介绍 Java 线程的基本概念、创建方法、生命周期以及同步机制。

1 进程和线程

  • 进程:进程(process)是计算机中的程序关于某数据集合一次运行活动,是操作系统进行资源分配和调度的基本单位。可以把进程简单理解为操作系统中正在运行的一个程序。
  • 线程:进程中执行运算的最小单位,是程序中单一顺序控制流程。进程是线程的容器,一个进程至少有一个线程。
  • 主线程:每个 Java 应用程序至少有一个线程,即主线程,它是 JVM 启动时自动创建的,该主线程负责执行main方法。
  • 子线程:JAVA中的线程不是孤立的,线程之间也会存在一些联系。如果A线程创建了B线程,那么A线程就是B线程的父线程,B线程就是A线程的子线程。

2 串行、并行和并发

  • 串行(Sequential):串行是指一次只能执行一个任务,并且按照一定的顺序依次执行。
  • 并行(Parallel):并行是指同时执行多个任务,通常利用多核处理器来提高执行速度。
  • 并发(Concurrency):并发是指在同一时间段内交替执行多个任务,通过时间片轮转等机制使得看起来像是同时进行。

3 Java实现多线程的三种方法

3.1 继承 Thread 类

这是最直接的方法,通过创建一个继承自 java.lang.Thread 的子类,并重写 run() 方法来定义线程要执行的任务。

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread: " + getName() + " - Count: " + i);
            try {
                Thread.sleep(100); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start(); // 启动线程
    }
}

当调用start()方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()方法。 

3.2 实现 Runnable 接口

这种方法更加灵活,因为它不需要继承特定的类。你可以创建一个实现了 java.lang.Runnable 接口的类,并将其实例传递给 Thread 的构造函数。这种方式适用于已经继承了其他类的情况。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable: " + Thread.currentThread().getName() + " - Count: " + i);
            try {
                Thread.sleep(100); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start(); // 启动线程
    }
}

3.3 实现 Callable 接口

如果你需要线程返回结果或者抛出异常,可以使用 java.util.concurrent.Callable 接口和 java.util.concurrent.FutureCallable 类似于 Runnable,但它可以返回结果并抛出异常。Future 用于获取异步计算的结果。

import java.util.concurrent.*;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<Integer> future = executor.submit(new MyCallable());

        System.out.println("Doing other work...");

        // 获取异步计算的结果
        int result = future.get();
        System.out.println("Result: " + result);

        executor.shutdown(); // 关闭线程池
    }
}
  • 继承 Thread 类:简单直接,但不适合已经有父类的情况。
  • 实现 Runnable 接口:更灵活,适合已经有父类的情况。
  • 使用 Callable 与 Future:适合需要返回结果或处理异常的情况,通常与线程池一起使用。
     

4 线程常用API

4.1 设置优先级

  • 当线程的优先级没有指定时,所有线程都携带普通优先级(普通优先级是5)。
  • 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,默认情况下,线程的优先级是 5。
  • 优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  • 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  • 由调度程序决定哪一个线程被执行。
  • t.setPriority()用来设定线程的优先级。
  • 在线程开始方法被调用之前,线程的优先级应该被设定。
  • 可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级


/**
 * 线程可以具有的最低优先级
 */
public final static int MIN_PRIORITY = 1;

/**
 * 分配给线程的默认优先级。
 */
public final static int NORM_PRIORITY = 5;

/**
 * 线程可以具有的最大优先级。
 */
public final static int MAX_PRIORITY = 10;

4.2 线程休眠

sleep方法方法会使当前线程进人指定毫秒数的休眠,暂停执行,虽然给定了一个休眠的时间,但是最终要以系统的定时器和调度器的精度为准,休眠有一个非常重要的特性,那就是其不会放弃监视器锁的所有权,即线程休眠时会释放 CPU 资源,但不会释放锁资源。

sleep是一个静态方法,其有两个重载方法,其中一个需要传入毫秒数,另外一个既需要毫秒数也需要纳秒数。

  • static void sleep(long millis) throws InterruptedException: 使当前正在执行的线程暂停执行指定的毫秒数。
  • static void sleep(long millis, int nanos) throws InterruptedException: 使当前正在执行的线程暂停执行指定的毫秒数和纳秒数。
try {
    Thread.sleep(1000); // 休眠1000毫秒
} catch (InterruptedException e) {
    e.printStackTrace();
}

在JDK1.5以后,JDK引入了一个枚举TimeUnit,其对sleep方法提供了很好的封装,使用它可以省去时间单位的换算步骤。

// 休眠3秒
Thread.sleep(3000);
TimeUnit.SECONDS.sleep(3);

4.3 线程让步

  • static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。具体实现依赖于底层的操作系统调度器,可能会导致当前线程重新被选中执行。

yield() 方法用于提示调度器给其他线程一个执行的机会,但它并不保证一定会切换到其他线程。调用yield方法会使当前线程从RUNNING状态切换到READY状态,一般这个方法不太常用。主要是为了测试和调试。

Thread.yield(); // 当前线程让出CPU时间片

yieldsleep的区别 :

  • 控制权:

    • yield() 只是建议性的,可能不会立即导致线程切换。
    • sleep() 则强制线程暂停特定时间,直到设定的时间过去才会恢复到可运行状态。
  • 状态转换:

    • yield() 不会改变线程的状态,线程保持在Runnable状态。
    • sleep() 会使线程暂时离开Runnable状态,进入Timed Waiting状态。
  • 参数:

    • yield() 没有参数,表示简单地让出CPU。
    • sleep() 需要提供一个时间参数,指定了线程应该暂停多久。
  • 异常处理:

    • yield() 不抛出异常。
    • sleep() 可能抛出 InterruptedException,因此通常需要捕获此异常或声明方法可能会抛出该异常。

4.4 线程重点(加入)

  • void join(): 等待该线程终止。
  • void join(long millis): 等待该线程终止,最多等待指定的时间。
  • void join(long millis, int nanos): 等待该线程终止,最多等待指定的时间(以毫秒和纳秒为单位)。

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。 

示例代码:

public class JoinExample {
    public static void main(String[] args) {
        // 创建一个子线程
        Thread childThread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Child thread: " + i);
                try {
                    Thread.sleep(500); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动子线程
        childThread.start();

        // 主线程等待子线程完成
        try {
            childThread.join(); // 等待子线程结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 子线程完成后,主线程继续执行
        for (int i = 0; i < 5; i++) {
            System.out.println("Main thread: " + i);
        }
    }
}

运行结果:

Child thread: 0
Child thread: 1
Child thread: 2
Child thread: 3
Child thread: 4
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4

代码解释:

  • 子线程:我们创建了一个新的线程 childThread,它会在循环中打印数字并休眠 500 毫秒。
  • 启动子线程:通过调用 childThread.start() 来启动子线程。
  • 主线程等待子线程:在主线程中,我们调用 childThread.join() 来等待子线程完成。这意味着主线程会暂停执行,直到 childThread 执行完毕。
  • 主线程继续执行:一旦 childThread 完成,主线程将继续执行其后续的代码,即打印自己的数字。

这个例子展示了如何使用 join() 方法来确保一个线程在另一个线程完成后才开始执行。这种方式常用于需要同步多个线程的情况,以保证某个线程的操作依赖于另一个线程的结果。

4.5 线程中断

  • void interrupt(): 中断线程。
  • boolean isInterrupted(): 测试该线程是否已被中断。
  • static boolean interrupted(): 测试当前线程是否已被中断,并清除当前线程的中断状态。

取消一个任务的执行,最好的,同时也是最合理的方法,就是通过中断。在Java中,线程中断是一种协作机制,允许一个线程请求另一个线程停止它正在做的事情。这通常用于优雅地停止长时间运行的任务或者处理不再需要的线程。下面是一个简单的例子来展示如何使用线程中断。 

例子:使用 Thread.interrupt() 和检查中断状态

假设我们有一个执行耗时操作的工作线程,比如模拟一个长时间运行的任务,我们想要提供一种方式来安全地停止这个任务。

工作线程(Worker Thread)

public class WorkerThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000000 && !isInterrupted(); i++) { // 检查是否被中断
            // 执行一些工作
            System.out.println("Working... " + i);
            
            // 模拟耗时操作
            try {
                Thread.sleep(100); // 可能会抛出InterruptedException
            } catch (InterruptedException e) {
                // 如果捕获到中断异常,则设置中断状态并退出
                interrupt(); // 重新设置中断状态
                break;
            }
        }

        // 清理资源等
        System.out.println("Task has been interrupted and stopped.");
    }
}

在这个WorkerThread类中,我们通过isInterrupted()方法检查当前线程是否被中断。如果线程被中断了,我们将跳出循环并进行清理工作。同时,在调用Thread.sleep()时,如果线程在此期间被中断,将捕获InterruptedException,此时我们也应该立即退出,并且最好重新设置中断状态以保持中断信号的一致性。

主程序(Main Class)

public class Main {
    public static void main(String[] args) throws InterruptedException {
        WorkerThread worker = new WorkerThread();
        worker.start(); // 启动工作线程

        // 让主线程等待几秒
        Thread.sleep(2000);

        // 请求中断工作线程
        worker.interrupt();

        // 等待工作线程结束
        worker.join();
        System.out.println("Main thread is done.");
    }
}

在主程序中,我们创建了一个WorkerThread实例并启动它。然后让主线程休眠2秒钟,之后调用worker.interrupt()来请求中断工作线程。最后,我们调用join()等待工作线程完全停止。

当运行这段代码时,你会看到工作线程打印一些信息,直到被中断后打印“Task has been interrupted and stopped.”,表明它已经响应了中断请求并正常终止。

这就是一个基本的例子展示了如何使用线程中断机制来控制和停止线程。这种做法比直接强制停止线程更加安全和可靠。

4.6 线程守护

在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。

用户线程:我们平常创建的普通线程。
守护线程:守护线程是一种特殊的线程,它的主要作用是为其他线程提供服务。当所有非守护线程结束时,即使守护线程还在运行,JVM也会自动退出。守护线程通常用于执行一些后台任务,如垃圾回收、内存管理等。

Java垃圾回收线程就是一个典型的守护线程,因为我们的垃圾回收是一个一直需要运行的机制,但是当没有用户线程的时候,也就不需要垃圾回收线程了,守护线程刚好满足这样的需求。

  • void setDaemon(boolean on): 将该线程标记为守护线程或用户线程。
  • boolean isDaemon(): 测试该线程是否为守护线程。

下面是一个简单的例子来展示如何创建和使用守护线程。

守护线程示例:

假设我们有一个主程序,它启动了一个工作线程来执行某些长时间运行的任务。同时,我们希望有一个守护线程来定期打印一条消息,以表明它正在运行。当工作线程完成其任务并终止时,整个JVM将退出,守护线程也将随之停止。 

工作线程(Worker Thread)

public class WorkerThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Working... " + i);
            try {
                Thread.sleep(1000); // 模拟耗时操作
            } catch (InterruptedException e) {
                System.out.println("Worker thread interrupted.");
                return;
            }
        }
        System.out.println("Worker thread finished.");
    }
}

守护线程(Daemon Thread)

public class DaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("Daemon thread is running...");
            try {
                Thread.sleep(500); // 每隔500毫秒打印一次
            } catch (InterruptedException e) {
                System.out.println("Daemon thread interrupted.");
                break;
            }
        }
    }
}

主程序(Main Class)

public class Main {
    public static void main(String[] args) {
        // 创建并启动工作线程
        WorkerThread worker = new WorkerThread();
        worker.start();

        // 创建守护线程
        DaemonThread daemon = new DaemonThread();
        
        // 设置为守护线程
        daemon.setDaemon(true);
        
        // 启动守护线程
        daemon.start();

        // 等待工作线程完成
        try {
            worker.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 工作线程完成后,主线程继续执行
        System.out.println("Main thread is done.");
    }
}

输出示例:

Working... 0
Daemon thread is running...
Daemon thread is running...
Working... 1
Daemon thread is running...
Daemon thread is running...
...
Working... 9
Daemon thread is running...
Daemon thread is running...
Worker thread finished.
Main thread is done.

在这个例子中,守护线程会一直运行,直到工作线程完成其任务。一旦工作线程完成,JVM会退出,守护线程也就自然停止了。这种方式可以确保后台任务不会阻止应用程序的正常退出。

注意事项:

  • 设置守护线程的方法很简单,调用setDaemon方法即可,true代表守护线程,false代表正常线程。
  • 线程是否为守护线程和它的父线程有很大的关系,具体来说,当一个新线程被创建时,它默认继承其父线程的守护状态,如果父线程是正常线程,则子线程也是正常线程,反之亦然。但是我们可以通过setDaemon方法显示的修改线程的守护状态。
  • setDaemon(true)必须在t.start()之前设置,否则会抛出IllegalThreadStateException异常。

4.7 获取线程ID

  • long getId(): 返回该线程的唯一标识符(ID)。

每个线程都有一个唯一的 ID,这个 ID 在 JVM 内部是唯一的,在整个 JVM 生命周期内不会重复,并且是从0开始逐次递增。在一个JVM进程启动的时候,实际上是开辟了很多个线程,自增序列已经有了一定的消耗,因此我们自己创建的线程可能并不是第0号线程。

MyThread myThread = new MyThread();
long id = myThread.getId();

4.8 获取当前线程

  • static Thread currentThread(): 返回对当前正在执行的线程对象的引用。

通过 currentThread() 方法,你可以获得当前正在执行的线程对象,然后可以调用该对象上的其他方法来获取更多信息或控制其行为。 

下面是一个简单的示例,展示了如何使用这些方法:

public class ThreadAPIExample {
    public static void main(String[] args) {
        // 创建一个新的线程
        Thread thread = new Thread(() -> {
            // 获取并打印当前线程的信息
            System.out.println("子线程开始运行...");
            System.out.println("子线程名称: " + Thread.currentThread().getName());
            System.out.println("子线程ID: " + Thread.currentThread().getId());

            try {
                // 模拟耗时操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子线程结束运行...");
        }, "MyChildThread");

        // 启动子线程
        thread.start();

        // 获取并打印主线程的信息
        System.out.println("主线程名称: " + Thread.currentThread().getName());
        System.out.println("主线程ID: " + Thread.currentThread().getId());

        // 等待子线程完成
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程继续运行...");
    }
}

运行结果:当你运行这段代码时,可能会看到如下输出(具体顺序可能因系统调度而略有不同)

主线程名称: main
子线程开始运行...
主线程ID: 1
子线程名称: MyChildThread
子线程ID: 12
子线程结束运行...
主线程继续运行...

这些方法对于理解和控制多线程程序的行为非常重要。特别是 currentThread() 方法,它经常被用来获取当前线程的上下文信息,以便进行更细粒度的控制和日志记录。

5 线程的生命周期

Java 线程的生命周期包括几个不同的状态,这些状态描述了线程从创建到终止的过程。线程生命周期可以通过getState()方法获得,线程的状态由 java.lang.Thread.State 枚举类型定义。以下是 Java 线程的生命周期及其各个状态的详细说明:

线程状态图:

5.1 新建状态(NEW)

当一个 Thread 对象被创建但还没有调用 start() 方法时,线程处于新建状态。

Thread thread = new Thread();
System.out.println(thread.getState()); // 输出:NEW

5.2 可运行状态(RUNNABLE)

当线程对象调用了 start() 方法后,线程进入可运行状态,它包括READY和RUNNING两个状态。READY状态表示线程可以被线程调度器进行调度,来使它变成RUNNING状态。RUNNING状态表示该线程正在被执行。Thread.yield()方法可以把线程由RUNNING状态转换为READY状态。

Thread thread = new Thread();
thread.start();
System.out.println(thread.getState()); // 输出:RUNNABLE

5.3 阻塞状态(BLOCKED)

线程发起阻塞的IO操作,或者申请由其他线程占用的独占资源(当一个线程试图进入一个已经被其他线程占用的同步代码块或方法时,它会被阻塞,直到获得所需的监视器锁),线程会转化为BLOCKED阻塞状态。处于阻塞状态的线程不会占用CPU资源。当阻塞IO操作执行完,或者线程获得了其申请的资源,线程可以转换为RUNNABLE。

例子:线程进入 BLOCKED 状态

public class BlockedExample {
    public static void main(String[] args) throws InterruptedException  {
        final Object lock = new Object();

        // 创建并启动第一个线程
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1: Holding the lock.");
                try {
                    // 模拟长时间操作,持有锁5秒钟
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Releasing the lock.");
            }
        });

        // 创建并启动第二个线程
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2: Acquired the lock.");
            }
        });

        // 启动第一个线程
        t1.start();

        System.out.println(t2.getState()); // 输出:RUNNABLE
        Thread.sleep(1000);
        System.out.println(t2.getState()); // 输出:BLOCKED

        // 确保第一个线程先启动并持有锁
        try {
            Thread.sleep(1000); // 给t1一点时间来获取锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 启动第二个线程
        t2.start();

        // 等待两个线程完成
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread is done.");
    }
}

输出示例:

Thread 1: Holding the lock.
RUNNABLE
BLOCKED
Thread 1: Releasing the lock.
Thread 2: Acquired the lock.
Main thread is done.

在这个例子中,t2 线程在尝试获取 lock 对象的监视器锁时会被阻塞,直到 t1 线程释放锁。这个过程展示了线程如何进入 BLOCKED 状态,并且只有当 t1 释放锁后,t2 才能继续执行。

5.4 等待状态(WAITING)

当线程无限期地等待另一个线程执行特定的动作时,线程处于等待状态。线程执行了object.wait()、thread.join()或者LockSupport.park()方法,会把线程转换为WAITING等待状态。执行object.notify()方法,或者加入的线程执行完毕,当前线程会转换为RUNNABLE状态。

例子:线程进入WAITING状态

public class WaitingExample {
    public static void main(String[] args) throws InterruptedException {
        final Object lock = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 1: Waiting...");
                    lock.wait(); // 进入 WAITING 状态
                    System.out.println("Thread 1: Notified and resumed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(2000); // 等待两秒
                    System.out.println("Thread 2: Notifying thread 1...");
                    lock.notify(); // 唤醒 t1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        Thread.sleep(1000); // 等待1秒
        System.out.println("Thread 1 State:" + t1.getState()); // 输出:Thread 1 State:WAITING
        t2.start();
    }
}

输出示例:

Thread 1: Waiting...
Thread 1 State:WAITING
Thread 2: Notifying thread 1...
Thread 1: Notified and resumed.

5.5 定时等待状态(TIMED_WAITING)

当线程在指定的时间内等待另一个线程执行特定的动作时,线程处于定时等待状态。如果线程没有在指定的时间范围内完成期望操作,该线程自动转换为RUNNABLE状态。通常通过以下方法进入定时等待状态:

  • Thread.sleep(long millis)
  • Object.wait(long timeout)
  • Thread.join(long millis)
  • LockSupport.parkNanos(long nanos)
  • LockSupport.parkUntil(long deadline)

例子:线程进入 TIMED_WAITING 状态

public class TimedWaitingExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建并启动一个线程
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("Thread 1: Sleeping for 3 seconds...");
                // 进入 TIMED_WAITING 状态
                Thread.sleep(3000); // 休眠3秒
                System.out.println("Thread 1: Woke up.");
            } catch (InterruptedException e) {
                System.out.println("Thread 1: Interrupted while sleeping.");
                e.printStackTrace();
            }
        });

        // 启动线程
        t1.start();
        Thread.sleep(1000); // 休眠1秒

        System.out.println("Thread 1 State: " + t1.getState()); // 输出:Thread 1 State: TIMED_WAITING

        // 主线程等待 t1 完成
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread is done.");
    }
}

输出示例:

Thread 1: Sleeping for 3 seconds...
Thread 1 State: TIMED_WAITING
Thread 1: Woke up.
Main thread is done.

通过这个例子,你可以清楚地看到线程如何进入 TIMED_WAITING 状态,并且只有当等待的时间到期后,线程才会继续执行。

5.6 终止状态(TERMINATED)

当线程的 run() 方法执行完毕或因异常退出时,线程进入终止状态。一旦线程终止,就不能再回到其他状态。

例子:线程进入 TERMINATED 状态

public class TerminatedExample {
    public static void main(String[] args) {
        // 创建并启动一个线程
        Thread t1 = new Thread(() -> {
            System.out.println("Thread 1: Starting...");
        });

        // 启动线程
        t1.start();

        // 主线程等待 t1 完成
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Thread 1 state:" + t1.getState());

        System.out.println("Main thread is done.");
    }
}

输出示例:

Thread 1: Starting...
Thread 1 state:TERMINATED
Main thread is done.

6 线程的安全性

Java 线程的安全性通常指的是在多线程环境中,如何确保数据的一致性和完整性。

6.1 线程安全

当一个类、方法或数据结构被设计成能够在多线程环境中正确运行,并且不需要额外的同步机制来防止数据损坏或不一致状态时,我们称它是线程安全的。这意味着多个线程可以同时访问这个对象而不会导致任何问题。为了确保线程安全,通常需要使用锁或其他同步技术来控制对共享资源的访问。

6.1.1 线程安全的特点
  • 原子性:保证一系列操作作为一个整体被执行,中间不会被打断。
  • 可见性:一个线程修改了某个变量的值,这个新值对于其他线程来说是立即可见的。
  • 有序性:程序执行的顺序和代码书写的顺序是一致的。
6.1.2 实现线程安全的方式
  • 使用 synchronized 关键字
  • 使用 volatile 关键字(仅保证可见性和禁止指令重排序)
  • 使用 java.util.concurrent 包中的工具类
  • 设计不可变对象
  • 使用 ThreadLocal 变量

 实现线程安全的方式包含但不限于以上方法。

6.1.2.1 synchronized 关键字

synchronized 是 Java 中用于控制多线程访问共享资源的关键字。它提供了一种内置的锁机制,可以确保同一时间只有一个线程能够执行特定的代码块或方法。这有助于防止多个线程同时修改共享数据而导致的数据不一致问题。

synchronized 的特点

  • 互斥性:确保同一时刻只有一个线程可以执行同步代码块或方法。

  • 可见性:当一个线程释放了锁之后,它之前所做的所有更改都会对其他线程可见。

  • 原子性:确保操作是不可分割的,即不会被线程调度机制中断。

  • 可重入性:如果一个线程已经拥有某个对象的锁,那么它可以再次获取这个对象的锁而不被阻塞。

以下是synchronized 的几种使用方式:

1.同步方法:当一个方法被声明为 synchronized 时,该方法的整个代码块会被自动加上锁。这个锁是基于对象的,对于实例方法,锁的是当前对象(即 this);对于静态方法,锁的是类的 Class 对象。

public synchronized void method() {
    // 同步代码块
}

2.同步代码块:可以更细粒度地控制同步范围,只对需要同步的部分加锁,而不是整个方法。

public void method() {
    synchronized (lockObject) {
        // 同步代码块
    }
}

3.同步静态方法:静态方法的锁是基于类的 Class 对象,而不是实例对象。

public static synchronized void method() {
    // 同步代码块
}

4.同步静态代码块:类似于同步代码块,但锁的对象是类的 Class 对象。

public void method() {
    synchronized (YourClass.class) {
        // 同步代码块
    }
}
6.1.2.2 volatile 关键字 

volatile 是 Java 中的一个关键字,用于确保变量的可见性和禁止指令重排序。它主要用于多线程环境中,以保证一个线程对 volatile 变量的修改能够立即被其他线程看到,而不需要等待缓存同步。

volatile 的主要特性

  • 可见性:当一个线程修改了 volatile 变量的值,新值会立即被写回到主内存中,并且所有其他线程可以立即看到这个新值。这避免了线程之间的数据不一致问题。

  • 禁止指令重排序:volatile 变量的读写操作不会被编译器和处理器重排序。这意味着在 volatile 变量的读写操作前后发生的其他操作顺序是固定的,从而保证了程序的正确性。

  • 不保证原子性:volatile 不能替代 synchronized,因为它不提供原子性。例如,volatile 不能保证复合操作(如 i++)的原子性。

使用场景

  • 状态标志:用于控制线程的状态,比如启动、停止标志。
  • 一次性安全发布:用于确保对象被正确地初始化并且发布给其他线程使用。
  • 简单的计数器:对于只需要简单递增或递减的操作,volatile 可以用来替代 synchronized 以提高性能。

示例代码:下面是一个简单的例子,展示了如何使用 volatile 来实现一个线程安全的停止标志。

public class VolatileExample {

    // 定义一个 volatile 变量作为停止标志
    private volatile boolean stop = false;

    public void start() {
        // 启动一个后台线程
        Thread backgroundThread = new Thread(() -> {
            while (!stop) {
                // 模拟一些工作
                doWork();
            }
            System.out.println("Background thread is stopping.");
        });

        backgroundThread.start();

        // 主线程休眠一段时间后设置停止标志
        try {
            Thread.sleep(5000); // 休眠5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 设置停止标志
        stop = true;
    }

    private void doWork() {
        // 模拟一些耗时操作
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Working...");
    }

    public static void main(String[] args) {
        VolatileExample example = new VolatileExample();
        example.start();
    }
}

 输出结果:运行这个程序后,你会看到输出类似于以下

Working...
Working...
...
Working...
Background thread is stopping.

 这表明后台线程在 stop 标志变为 true 之前一直在执行 doWork 方法。一旦 stop 标志被设置为 true,后台线程就会退出循环并停止。

注意事项

  • 原子性:volatile 不保证复合操作的原子性。例如,i++ 或 i += 1 这样的操作不是原子的。如果需要原子性操作,应该使用 AtomicInteger 等原子类或者 synchronized 关键字。

  • 性能:虽然 volatile 提供了较好的性能,但频繁的读写操作可能会导致总线锁定,影响性能。因此,在高并发环境下,应谨慎使用 volatile

6.1.2.3 ThreadLocal 变量

ThreadLocal 是 Java 中的一个类,用于在多线程环境中为每个线程提供独立的变量副本。这样可以确保每个线程都能独立地操作自己的变量副本,而不会影响其他线程。

ThreadLocal 通常用于以下场景:

  • 线程隔离的数据:当需要为每个线程维护独立的状态信息时,例如用户会话、事务ID等。

  • 避免线程安全问题:通过为每个线程提供独立的变量副本,避免了多线程访问共享资源时可能出现的竞态条件。

  • 简化代码:在某些情况下,使用 ThreadLocal 可以简化代码逻辑,避免显式的同步机制。

ThreadLocal 的主要特性:

  • 线程隔离:每个线程都有自己独立的变量副本,互不干扰。

  • 生命周期:ThreadLocal 变量的生命周期与线程的生命周期一致。当线程结束时,ThreadLocal 变量也会被垃圾回收。

  • 内存泄漏:如果线程长时间运行且没有及时清理 ThreadLocal 变量,可能会导致内存泄漏。因此,建议在不再需要时显式地清除 ThreadLocal 变量。

主要方法

  • set(T value):设置当前线程的 ThreadLocal 变量值。

  • T get():获取当前线程的 ThreadLocal 变量值。如果该线程尚未设置此 ThreadLocal 变量,则返回初始值(默认为 null)。

  • void remove():删除当前线程的 ThreadLocal 变量值,有助于防止内存泄漏。

示例代码:

public class ThreadLocalExample {

    // 定义一个 ThreadLocal 变量
    private static final ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0; // 初始化值为 0
        }
    };

    public static void main(String[] args) {
        // 创建两个线程
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                increment();
                System.out.println("Thread 1: " + threadLocalCounter.get());
            }
            threadLocalCounter.remove(); // 清除 ThreadLocal 变量
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                increment();
                System.out.println("Thread 2: " + threadLocalCounter.get());
            }
            threadLocalCounter.remove(); // 清除 ThreadLocal 变量
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待两个线程完成
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 增加计数器的方法
    private static void increment() {
        int count = threadLocalCounter.get();
        threadLocalCounter.set(count + 1);
    }
}

输出结果:

Thread 1: 1
Thread 2: 1
Thread 1: 2
Thread 2: 2
Thread 1: 3
Thread 2: 3
Thread 1: 4
Thread 2: 4
Thread 1: 5
Thread 2: 5

这表明每个线程都有自己的独立计数器,互不影响。Thread 1Thread 2 分别递增自己的计数器,并打印出各自的结果。 

注意事项

  • 内存泄漏:长时间运行的线程如果不及时清除 ThreadLocal 变量,可能会导致内存泄漏。因此,在不需要 ThreadLocal 变量时,应调用 remove 方法进行清理。

  • 性能:ThreadLocal 提供了一种方便的方式来管理线程局部变量,但在高并发环境下,频繁的读写操作可能会影响性能。因此,应根据具体需求权衡使用。

6.2 非线程安全

多线程并发访问共享资源时缺乏适当的同步机制导致的,多个线程同时读取和修改同一数据时,一个类、方法或数据结构没有提供足够的同步措施来保护其内部状态不受并发访问的影响,会出现值被更改,值不同步的问题,这个就是非线程安全的。

6.2.1 非线程安全问题

在并发编程中,多个线程可能会同时读取和修改同一数据,如果没有合适的控制措施,就可能产生以下几种问题:

  • 竞态条件:两个或更多线程试图同时修改同一个数据项,最终结果取决于这些线程的相对时间点。例如,如果一个线程正在计算某个值,并且另一个线程在同一时间也尝试修改这个值,那么最终的结果可能是不正确的。
  • 死锁:两个或更多的线程互相等待对方持有的资源。
  • 活锁:线程虽然没有被阻塞,但由于某种条件无法继续执行。例如,两个线程都试图解决冲突,但是它们的解决方案总是相互抵触,导致两者不断地改变状态而无法进展。
  • 脏读/丢失更新:一个线程读取了一个未提交的数据或者更新被另一个线程的操作覆盖。

为了解决这些问题,程序员需要使用各种同步机制,如 synchronized 关键字、volatile 变量、java.util.concurrent 包提供的工具类等,来确保在多线程环境下对共享资源的正确访问。通过合理的同步策略,可以避免上述提到的各种线程安全问题。 

7 线程池

Java 线程池是用于管理和复用线程的一种机制,它可以帮助你更高效地使用线程资源,减少线程创建和销毁的开销。Java 通过 java.util.concurrent 包中的 ExecutorExecutors 工具类提供了对线程池的支持。

7.1 线程池基本概念

  • 线程池:线程池是一组预先创建并初始化好的线程,这些线程在等待执行新的任务时处于空闲状态。当有新的任务需要执行时,线程池会分配一个线程来处理这个任务。如果所有的线程都在忙,新任务会被放入队列中等待。

  • 任务队列:任务队列是用来存放待执行任务的数据结构。常见的队列类型包括 LinkedBlockingQueueArrayBlockingQueueSynchronousQueue 等。

  • 核心线程数 (corePoolSize):线程池中始终保持活动状态的核心线程数量。即使这些线程处于空闲状态,也不会被回收。

  • 最大线程数 (maximumPoolSize):线程池允许的最大线程数量。当任务队列满了并且当前线程数小于最大线程数时,线程池会创建新的线程来处理任务。

  • 保持存活时间 (keepAliveTime):当线程数超过核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间。

  • 拒绝策略 (RejectedExecutionHandler):当线程池无法接受新任务时(例如,队列已满且线程数达到最大值),线程池将采用某种策略来处理新提交的任务

7.2 线程池的使用

之前文章已有介绍,详情参考:Java ExecutorService-CSDN博客

8 并发集合容器

在 Java 中,并发集合容器是专为多线程环境设计的数据结构,它们提供了内置的线程安全性,允许在没有额外同步的情况下安全地并发访问。这些集合位于 java.util.concurrent 包中,通常比使用传统的集合(如 ArrayList, HashMap 等)加上外部同步机制更加高效和易于使用。以下是几种常用的并发集合容器:

8.1 ConcurrentHashMap

ConcurrentHashMapjava.util.concurrent 包中的一个线程安全的哈希表实现。它在多线程环境中提供了高效的并发访问,而不需要显式的同步(如使用 synchronized 关键字)。ConcurrentHashMap 通过将数据分割成多个段(segments)来减少锁的竞争,并且每个段都是独立锁定的,从而提高了并发性能。

8.1.1 ConcurrentHashMap 的主要特性
  • 分段锁:ConcurrentHashMap 使用分段锁机制,即将整个哈希表分成若干个段(默认是 16 个),每个段都有自己的锁。这样可以允许多个读/写操作同时进行,只要它们作用于不同的段。

  • 无锁读取:对于读操作,ConcurrentHashMap 不需要获取任何锁。这是因为它的内部结构保证了在没有写操作的情况下,读操作总是能够看到一致的数据视图。

  • 高效迭代器:ConcurrentHashMap 的迭代器是弱一致性的,这意味着它们不会抛出 ConcurrentModificationException。在创建迭代器时,它会反映迭代器创建时刻的数据状态,但在迭代过程中可能会看到其他线程对数据的修改。

  • 原子更新:提供了一系列原子更新的方法,如 putIfAbsentremovereplace 等,这些方法可以在不阻塞整个哈希表的情况下执行。

  • 高并发性能:由于其设计,ConcurrentHashMap 在高并发环境下表现优异,适合用于多线程环境下的缓存和共享数据存储。

8.1.2 ConcurrentHashMap 的主要方法
  • 构造器:

    • ConcurrentHashMap(): 创建一个空的 ConcurrentHashMap
    • ConcurrentHashMap(int initialCapacity): 创建一个具有指定初始容量的 ConcurrentHashMap
    • ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel): 创建一个具有指定初始容量、负载因子和并发级别的 ConcurrentHashMap
  • 添加和移除元素:

    • V put(K key, V value): 将指定的键值对放入此映射中。
    • V putIfAbsent(K key, V value): 如果此映射中尚未存在指定键,则将其与给定值关联。
    • boolean remove(Object key, Object value): 如果当前映射包含指定键并且该键对应的值等于指定值,则删除该键值对。
    • V replace(K key, V value): 如果此映射中已存在指定键,则替换其值。
  • 读取元素:

    • V get(Object key): 返回指定键所映射的值。
    • V getOrDefault(Object key, V defaultValue): 返回指定键所映射的值,如果不存在则返回默认值。
  • 批量操作:

    • void putAll(Map<? extends K,? extends V> m): 将指定映射的所有映射关系复制到此映射中。
    • void clear(): 移除此映射中的所有映射关系。
  • 原子更新:

    • boolean replace(K key, V oldValue, V newValue): 只有当满足旧值条件时才替换键的值。
    • V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction): 计算给定键的新值并将其放入此映射中。

示例代码: 

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentHashMapExample {

    // 创建一个 ConcurrentHashMap 实例
    private static final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(() -> {
                // 每个任务尝试更新映射
                updateMap(index);
            });
        }

        // 关闭线程池
        executor.shutdown();

        // 打印最终的映射
        System.out.println("Final map: " + map);
    }

    // 更新映射的方法
    private static void updateMap(int index) {
        String key = "key" + index;
        // 使用 putIfAbsent 方法确保每个键只被插入一次
        map.putIfAbsent(key, index);
        // 使用 replace 方法更新值
        map.replace(key, index, index * 2);
    }
}

输出结果:

Final map: {key1=2, key2=4, key0=0, key5=10, key6=12, key3=6, key4=8, key9=18, key7=14, key8=16}

8.2 CopyOnWriteArrayList

CopyOnWriteArrayListjava.util.concurrent 包中的一个线程安全的列表实现。它通过在写操作时创建整个底层数组的一个新副本,从而避免了对读操作加锁的需求。这种方式使得读取操作非常高效,因为它们不需要任何同步机制。然而,写操作(如添加、删除或修改元素)可能会比较昂贵,因为每次写操作都需要复制整个数组。

8.2.1 CopyOnWriteArrayList 的主要特性
  • 无锁读取:读操作是完全无锁的,因为读取的是底层数据结构的快照,而不会受到写操作的影响。

  • 高效的迭代器:迭代器也是弱一致性的,这意味着它们不会抛出 ConcurrentModificationException。迭代器反映的是创建时刻的数据状态,但在迭代过程中可能会看到其他线程对数据的修改。

  • 适用于读多写少的场景:由于写操作成本较高,CopyOnWriteArrayList 更适合于读取频繁而写入较少的情况。

  • 原子更新:写操作是原子的,保证了线程安全。

  • 内存使用:在写操作频繁的情况下,可能会消耗较多内存,因为它需要为每次写操作创建一个新的数组副本。

8.2.2 CopyOnWriteArrayList 的主要方法
  • 构造器:

    • CopyOnWriteArrayList(): 创建一个空的 CopyOnWriteArrayList
    • CopyOnWriteArrayList(Collection<? extends E> c): 创建一个包含指定集合元素的 CopyOnWriteArrayList
  • 添加和移除元素:

    • boolean add(E e): 将指定的元素添加到此列表的尾部。
    • void add(int index, E element): 将指定的元素插入此列表中的指定位置。
    • E remove(int index): 移除此列表中指定位置上的元素。
    • boolean remove(Object o): 移除此列表中第一次出现的指定元素(如果存在的话)。
  • 访问元素:

    • E get(int index): 返回此列表中指定位置上的元素。
    • int size(): 返回此列表中的元素数。
  • 批量操作:

    • void addAll(Collection<? extends E> c): 将指定集合中的所有元素追加到此列表的末尾。
    • void clear(): 移除此列表中的所有元素。
  • 迭代器:

    • Iterator<E> iterator(): 返回在此列表中的元素上进行迭代的迭代器。

示例代码:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CopyOnWriteArrayListExample {

    // 创建一个 CopyOnWriteArrayList 实例
    private static final CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(() -> {
                // 每个任务尝试更新列表
                updateList(index);
            });
        }

        // 关闭线程池
        executor.shutdown();

        // 打印最终的列表
        System.out.println("Final list: " + list);

        // 使用迭代器遍历列表
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

    // 更新列表的方法
    private static void updateList(int index) {
        String item = "Item" + index;
        // 添加元素
        list.add(item);
        // 输出当前列表内容
        System.out.println("After adding " + item + ": " + list);
    }
}

8.3 CopyOnWriteArraySet

CopyOnWriteArraySetjava.util.concurrent 包中的一个线程安全的集合实现。它是基于 CopyOnWriteArrayList 的,因此它继承了 CopyOnWriteArrayList 的特性,比如无锁读取和高效的迭代器。CopyOnWriteArraySet 保证了元素的唯一性,并且在添加或移除元素时会创建底层数组的新副本。

8.3.1 CopyOnWriteArraySet 的主要特性
  • 无锁读取:读操作是完全无锁的,因为读取的是底层数据结构的快照,而不会受到写操作的影响。

  • 高效的迭代器:迭代器也是弱一致性的,这意味着它们不会抛出 ConcurrentModificationException。迭代器反映的是创建时刻的数据状态,但在迭代过程中可能会看到其他线程对数据的修改。

  • 元素唯一性:CopyOnWriteArraySet 确保集合中没有重复的元素。

  • 适用于读多写少的场景:由于写操作成本较高,CopyOnWriteArraySet 更适合于读取频繁而写入较少的情况。

  • 原子更新:写操作是原子的,保证了线程安全。

  • 内存使用:在写操作频繁的情况下,可能会消耗较多内存,因为它需要为每次写操作创建一个新的数组副本。

8.3.2 CopyOnWriteArraySet 的主要方法
  • 构造器:

    • CopyOnWriteArraySet(): 创建一个空的 CopyOnWriteArraySet
    • CopyOnWriteArraySet(Collection<? extends E> c): 创建一个包含指定集合元素的 CopyOnWriteArraySet
  • 添加和移除元素:

    • boolean add(E e): 将指定的元素添加到此集合(如果该集合不包含该元素)。
    • boolean remove(Object o): 移除此集合中第一次出现的指定元素(如果存在的话)。
  • 访问元素:

    • int size(): 返回此集合中的元素数。
    • boolean contains(Object o): 如果此集合包含指定的元素,则返回 true
  • 批量操作:

    • void clear(): 移除此集合中的所有元素。
    • boolean addAll(Collection<? extends E> c): 将指定集合中的所有元素追加到此集合中。
  • 迭代器:

    • Iterator<E> iterator(): 返回在此集合中的元素上进行迭代的迭代器。

示例代码:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CopyOnWriteArraySetExample {

    // 创建一个 CopyOnWriteArraySet 实例
    private static final CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(() -> {
                // 每个任务尝试更新集合
                updateSet(index);
            });
        }

        // 关闭线程池
        executor.shutdown();

        // 打印最终的集合
        System.out.println("Final set: " + set);

        // 使用迭代器遍历集合
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

    // 更新集合的方法
    private static void updateSet(int index) {
        String item = "Item" + index;
        // 添加元素
        if (set.add(item)) {
            // 输出当前集合内容
            System.out.println("After adding " + item + ": " + set);
        } else {
            System.out.println(item + " already exists in the set.");
        }
    }
}

8.4 ConcurrentLinkedQueue

ConcurrentLinkedQueuejava.util.concurrent 包中的一个无界线程安全的队列实现。它基于链表结构,并且使用非阻塞算法来保证高并发环境下的性能。ConcurrentLinkedQueue 适合于需要高效地进行多线程插入和删除操作的场景,尤其是当不需要对队列大小进行限制时。适用于高并发场景下的消息传递或任务调度。

8.4.1 ConcurrentLinkedQueue 的主要特性
  • 无界:ConcurrentLinkedQueue 是无界的,这意味着它可以持续增长直到内存耗尽。

  • 线程安全:所有方法都是线程安全的,不需要外部同步机制。

  • 非阻塞算法:使用非阻塞算法(如 CAS 操作)来确保在高并发环境下的性能。

  • FIFO 顺序:遵循先进先出(FIFO)的原则,即最早加入队列的元素将被最先取出。

  • 弱一致性的迭代器:迭代器是弱一致性的,不会抛出 ConcurrentModificationException。迭代器反映的是创建时刻的数据状态,但在迭代过程中可能会看到其他线程对数据的修改。

  • 不支持 null 元素:ConcurrentLinkedQueue 不允许插入 null 元素。

8.4.2 ConcurrentLinkedQueue 的主要方法
  • 构造器:

    • ConcurrentLinkedQueue(): 创建一个新的空队列。
  • 添加元素:

    • boolean add(E e): 将指定的元素添加到此队列中。
    • boolean offer(E e): 将指定的元素添加到此队列中(等同于 add 方法)。
  • 移除元素:

    • E poll(): 获取并移除此队列的头,如果此队列为空,则返回 null
    • E remove(): 获取并移除此队列的头,如果此队列为空,则抛出 NoSuchElementException
  • 访问元素:

    • E peek(): 获取但不移除此队列的头;如果此队列为空,则返回 null
    • E element(): 获取但不移除此队列的头;如果此队列为空,则抛出 NoSuchElementException
  • 批量操作:

    • int size(): 返回此队列中的元素数。
    • boolean contains(Object o): 如果此队列包含指定的元素,则返回 true
    • void clear(): 移除此队列中的所有元素。
  • 迭代器:

    • Iterator<E> iterator(): 返回在此队列中的元素上进行迭代的迭代器。
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentLinkedQueueExample {

    // 创建一个 ConcurrentLinkedQueue 实例
    private static final ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个生产者任务到线程池
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(() -> {
                // 每个任务尝试向队列中添加元素
                produce(index);
            });
        }

        // 提交 5 个消费者任务到线程池
        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                // 每个任务尝试从队列中消费元素
                consume();
            });
        }

        // 关闭线程池
        executor.shutdown();

        // 等待所有任务完成
        while (!executor.isTerminated()) {
            // 等待线程池中的所有任务完成
        }

        // 打印最终的队列
        System.out.println("Final queue: " + queue);

        // 使用迭代器遍历队列
        Iterator<String> iterator = queue.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

    // 生产者方法
    private static void produce(int index) {
        String item = "Item" + index;
        // 添加元素到队列
        queue.offer(item);
        // 输出当前队列内容
        System.out.println("Produced: " + item + ", Queue: " + queue);
    }

    // 消费者方法
    private static void consume() {
        while (true) {
            String item = queue.poll();
            if (item == null) {
                // 如果队列为空,则退出循环
                break;
            }
            // 输出消费的元素
            System.out.println("Consumed: " + item + ", Queue: " + queue);
        }
    }
}

 输出结果:

Produced: Item1, Queue: [Item0, Item1, Item2, Item3, Item4]
Produced: Item6, Queue: [Item0, Item1, Item2, Item3, Item4, Item5, Item6]
Produced: Item3, Queue: [Item0, Item1, Item2, Item3, Item4]
Produced: Item8, Queue: [Item0, Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8]
Produced: Item4, Queue: [Item0, Item1, Item2, Item3, Item4]
Produced: Item0, Queue: [Item0, Item1, Item2, Item3, Item4]
Produced: Item5, Queue: [Item0, Item1, Item2, Item3, Item4, Item5]
Produced: Item2, Queue: [Item0, Item1, Item2, Item3, Item4, Item5]
Produced: Item9, Queue: [Item0, Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9]
Produced: Item7, Queue: [Item0, Item1, Item2, Item3, Item4, Item5, Item6, Item7]
Consumed: Item0, Queue: [Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9]
Consumed: Item1, Queue: [Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9]
Consumed: Item2, Queue: [Item3, Item4, Item5, Item6, Item7, Item8, Item9]
Consumed: Item6, Queue: [Item7, Item8, Item9]
Consumed: Item5, Queue: [Item6, Item7, Item8, Item9]
Consumed: Item4, Queue: [Item5, Item6, Item7, Item8, Item9]
Consumed: Item3, Queue: [Item4, Item5, Item6, Item7, Item8, Item9]
Consumed: Item9, Queue: []
Consumed: Item8, Queue: [Item9]
Consumed: Item7, Queue: [Item8, Item9]
Final queue: []

8.5 BlockingQueue 接口及其实现

BlockingQueuejava.util.concurrent 包中的一个接口,它定义了一个线程安全的队列,该队列在尝试添加元素到满队列或从空队列中移除元素时会阻塞。BlockingQueue 通常用于生产者-消费者问题,其中生产者线程将数据放入队列,而消费者线程从队列中取出数据进行处理。

8.5.1 BlockingQueue 的主要特性
  • 线程安全:

    • 所有实现 BlockingQueue 接口的类都是线程安全的,不需要外部同步机制。
  • 阻塞操作:

    • 当队列满时,插入操作(如 put)会阻塞,直到有空间可用。
    • 当队列空时,移除操作(如 take)会阻塞,直到有元素可用。
  • 支持超时和中断:

    • 提供了带超时的方法,如 offer 和 poll,可以在等待一段时间后返回。
    • 支持中断操作,允许线程在阻塞时被中断。
  • 多种实现:

    • ArrayBlockingQueue: 基于数组的有界阻塞队列。
    • LinkedBlockingQueue: 基于链表的可选有界阻塞队列。
    • PriorityBlockingQueue: 无界的优先级阻塞队列。
    • SynchronousQueue: 不存储元素的阻塞队列,每个插入操作必须等待另一个线程的相应移除操作。
    • DelayQueue: 无界的基于优先级的延迟阻塞队列。
8.5.2 BlockingQueue 的主要方法
  • 插入元素:

    • void put(E e) throws InterruptedException: 将指定的元素插入此队列,如果必要的话等待可用的空间。
    • boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException: 在指定的等待时间内,将指定的元素插入此队列,如果有必要则等待可用的空间。
  • 移除元素:

    • E take() throws InterruptedException: 检索并移除此队列的头,如果需要等待元素变为可用。
    • E poll(long timeout, TimeUnit unit) throws InterruptedException: 在指定的等待时间内检索并移除此队列的头,如果需要等待元素变为可用。
  • 检查元素:

    • E peek(): 获取但不移除此队列的头;如果此队列为空,则返回 null
  • 其他方法:

    • int size(): 返回此队列中的元素数。
    • boolean contains(Object o): 如果此队列包含指定的元素,则返回 true
    • void clear(): 移除此队列中的所有元素。

示例代码: 

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);

        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    queue.put("Item " + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    String item = queue.take();
                    System.out.println("Consumed: " + item);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

8.6 注意事项

  • 并发集合容器虽然提供了线程安全性,但在某些情况下可能不如传统的集合加上适当的同步机制性能好。因此,在选择使用哪种集合时,应该考虑具体的应用场景和性能需求。
  • 并发集合容器通常不支持 null 值,因为 null 可能会被用来表示某种特殊状态,例如队列中的空值。
  • 在使用并发集合容器时,应当熟悉其特性和限制,以便更好地利用它们的优势。

9 原子操作 

原子操作是指不可分割的操作,即该操作在执行过程中不会被线程调度机制打断,要么全部执行,要么完全不执行。在多线程环境中,原子操作是确保数据一致性的基础。Java 提供了 java.util.concurrent.atomic 包来支持一系列的原子操作类,这些类可以用来替代传统的锁机制,从而提高并发性能。

9.1 基本类型的原子操作

9.1.1 AtomicInteger

AtomicInteger 提供了对整数的原子操作。这意味着这些操作是不可分割的,并且在多线程环境中执行时不需要额外的同步机制(如 synchronized 关键字)。AtomicInteger 内部使用了底层的硬件支持(如 CAS 操作)来确保原子性。

  • 构造器:

    • AtomicInteger():初始化为 0。
    • AtomicInteger(int initialValue):初始化为指定值。
  • 获取和设置:

    • int get(): 获取当前值。
    • void set(int newValue): 设置新的值。
    • void lazySet(int newValue): 最终设置新的值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • final int getAndSet(int newValue): 获取当前值并设置新值。
    • final int getAndIncrement(): 获取当前值并递增1。
    • final int getAndDecrement(): 获取当前值并递减1。
    • final int getAndAdd(int delta): 获取当前值并加上给定的增量。
    • final boolean compareAndSet(int expect, int update): 如果当前值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • final boolean weakCompareAndSet(int expect, int update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • String toString(): 返回当前值的字符串表示形式。
    • int intValue(): 返回当前值作为 int
    • long longValue(): 返回当前值作为 long
    • float floatValue(): 返回当前值作为 float
    • double doubleValue(): 返回当前值作为 double

示例代码: 下面是一个简单的例子,展示了如何在多线程环境中使用 AtomicInteger 来安全地递增计数器

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class AtomicIntegerExample {

    // 创建一个 AtomicInteger 实例
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                // 每个任务递增计数器 1000 次
                for (int j = 0; j < 1000; j++) {
                    incrementCounter();
                }
            });
        }

        // 关闭线程池,并等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 打印最终的计数值
        System.out.println("Final count: " + counter.get());
    }

    // 原子递增方法
    private static void incrementCounter() {
        // 使用 AtomicInteger 的 incrementAndGet 方法进行原子递增
        counter.incrementAndGet();
    }
}

输出结果:

Final count: 10000

这表明 10 个线程各自递增了 1000 次,总共递增了 10000 次。由于 AtomicIntegerincrementAndGet 方法是原子的,所以即使在多线程环境下,计数器也能正确地递增,不会出现竞态条件导致的数据不一致问题。

通过这种方式,AtomicInteger 提供了一种简单而高效的方式来处理多线程环境中的整数操作。

9.1.2 AtomicLong

AtomicLong 提供了对长整型(long)的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicLong 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 构造器:

    • AtomicLong(): 初始化为 0。
    • AtomicLong(long initialValue): 初始化为指定值。
  • 获取和设置:

    • long get(): 获取当前值。
    • void set(long newValue): 设置新的值。
    • void lazySet(long newValue): 最终设置新的值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • final long getAndSet(long newValue): 获取当前值并设置新值。
    • final long getAndIncrement(): 获取当前值并递增1。
    • final long getAndDecrement(): 获取当前值并递减1。
    • final long getAndAdd(long delta): 获取当前值并加上给定的增量。
    • final boolean compareAndSet(long expect, long update): 如果当前值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • final boolean weakCompareAndSet(long expect, long update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • String toString(): 返回当前值的字符串表示形式。
    • int intValue(): 返回当前值作为 int
    • long longValue(): 返回当前值作为 long
    • float floatValue(): 返回当前值作为 float
    • double doubleValue(): 返回当前值作为 double

示例代码:下面是一个简单的例子,展示了如何在多线程环境中使用 AtomicLong 来安全地递增计数器

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class AtomicLongExample {

    // 创建一个 AtomicLong 实例
    private static final AtomicLong counter = new AtomicLong(0);

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                // 每个任务递增计数器 10000 次
                for (int j = 0; j < 10000; j++) {
                    incrementCounter();
                }
            });
        }

        // 关闭线程池,并等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 打印最终的计数值
        System.out.println("Final count: " + counter.get());
    }

    // 原子递增方法
    private static void incrementCounter() {
        // 使用 AtomicLong 的 incrementAndGet 方法进行原子递增
        counter.incrementAndGet();
    }
}

输出结果:

Final count: 100000

这表明 10 个线程各自递增了 10000 次,总共递增了 100000 次。由于 AtomicLongincrementAndGet 方法是原子的,所以即使在多线程环境下,计数器也能正确地递增,不会出现竞态条件导致的数据不一致问题。

通过这种方式,AtomicLong 提供了一种简单而高效的方式来处理多线程环境中的长整型操作。

9.1.3 AtomicBoolean

AtomicBoolean 提供了对布尔值的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicBoolean 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 构造器:

    • AtomicBoolean(): 初始化为 false
    • AtomicBoolean(boolean initialValue): 初始化为指定值。
  • 获取和设置:

    • boolean get(): 获取当前值。
    • void set(boolean newValue): 设置新的值。
    • void lazySet(boolean newValue): 最终设置新的值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • final boolean getAndSet(boolean newValue): 获取当前值并设置新值。
    • final boolean compareAndSet(boolean expect, boolean update): 如果当前值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • final boolean weakCompareAndSet(boolean expect, boolean update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • boolean isLockFree(): 返回 true 如果这个变量是无锁的,即它的实现不使用传统的互斥锁。
    • String toString(): 返回当前值的字符串表示形式。

示例代码:下面是一个简单的例子,展示了如何在多线程环境中使用 AtomicBoolean 来控制某个条件的状态。假设我们有一个系统,需要根据某个条件来决定是否继续处理请求。我们将使用 AtomicBoolean 来管理这个条件。 

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class AtomicBooleanExample {

    // 创建一个 AtomicBoolean 实例
    private static final AtomicBoolean processingEnabled = new AtomicBoolean(true);

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                // 每个任务尝试处理请求
                processRequests();
            });
        }

        // 模拟一段时间后停止处理请求
        try {
            Thread.sleep(5000); // 等待 5 秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 停止处理请求
        stopProcessing();

        // 关闭线程池,并等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 打印最终状态
        System.out.println("Processing enabled: " + processingEnabled.get());
    }

    // 处理请求的方法
    private static void processRequests() {
        while (processingEnabled.get()) {
            // 模拟处理请求
            System.out.println(Thread.currentThread().getName() + " is processing a request.");
            try {
                Thread.sleep(100); // 模拟处理时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + " stopped processing requests.");
    }

    // 停止处理请求的方法
    private static void stopProcessing() {
        processingEnabled.set(false);
        System.out.println("Stopping all request processing...");
    }
}

输出结果:

pool-1-thread-2 stopped processing requests.
pool-1-thread-10 stopped processing requests.
pool-1-thread-6 stopped processing requests.
pool-1-thread-5 stopped processing requests.
pool-1-thread-9 stopped processing requests.
pool-1-thread-1 stopped processing requests.
...
Processing enabled: false

这表明 10 个线程各自处理了一些请求,然后在 processingEnabled 被设置为 false 后停止了处理。由于 AtomicBooleangetset 方法是原子的,所以即使在多线程环境下,状态也能正确地被读取和修改,不会出现竞态条件导致的数据不一致问题。

通过这种方式,AtomicBoolean 提供了一种简单而高效的方式来处理多线程环境中的布尔值操作。

9.2 数组类型的原子操作

9.2.1 AtomicIntegerArray

AtomicIntegerArray 提供了对整数数组的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicIntegerArray 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 构造器:

    • AtomicIntegerArray(int length): 创建一个指定长度的数组,所有元素初始化为 0。
    • AtomicIntegerArray(int[] array): 从给定的数组创建一个 AtomicIntegerArray 实例。
  • 获取和设置:

    • int get(int i): 获取索引 i 处的值。
    • void set(int i, int newValue): 设置索引 i 处的值。
    • void lazySet(int i, int newValue): 最终设置索引 i 处的值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • final int getAndSet(int i, int newValue): 获取并设置索引 i 处的值。
    • final int getAndIncrement(int i): 获取并递增索引 i 处的值。
    • final int getAndDecrement(int i): 获取并递减索引 i 处的值。
    • final int getAndAdd(int i, int delta): 获取并加上增量 delta 到索引 i 处的值。
    • final boolean compareAndSet(int i, int expect, int update): 如果索引 i 处的值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • final boolean weakCompareAndSet(int i, int expect, int update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • int length(): 返回数组的长度。
    • String toString(): 返回数组的字符串表示形式。
9.2.2 AtomicLongArray

AtomicLongArray 提供了对长整型数组的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicLongArray 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 构造器:

    • AtomicLongArray(int length): 创建一个指定长度的数组,所有元素初始化为 0。
    • AtomicLongArray(long[] array): 从给定的数组创建一个 AtomicLongArray 实例。
  • 获取和设置:

    • long get(int i): 获取索引 i 处的值。
    • void set(int i, long newValue): 设置索引 i 处的值。
    • void lazySet(int i, long newValue): 最终设置索引 i 处的值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • final long getAndSet(int i, long newValue): 获取并设置索引 i 处的值。
    • final long getAndIncrement(int i): 获取并递增索引 i 处的值。
    • final long getAndDecrement(int i): 获取并递减索引 i 处的值。
    • final long getAndAdd(int i, long delta): 获取并加上增量 delta 到索引 i 处的值。
    • final boolean compareAndSet(int i, long expect, long update): 如果索引 i 处的值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • final boolean weakCompareAndSet(int i, long expect, long update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • int length(): 返回数组的长度。
    • String toString(): 返回数组的字符串表示形式。
9.2.3 AtomicReferenceArray 

AtomicReferenceArray 提供了对引用对象数组的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicReferenceArray 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 构造器:

    • AtomicReferenceArray(int length): 创建一个指定长度的数组,所有元素初始化为 null
    • AtomicReferenceArray(E[] array): 从给定的数组创建一个 AtomicReferenceArray 实例。
  • 获取和设置:

    • E get(int i): 获取索引 i 处的值。
    • void set(int i, E newValue): 设置索引 i 处的值。
    • void lazySet(int i, E newValue): 最终设置索引 i 处的值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • final E getAndSet(int i, E newValue): 获取并设置索引 i 处的值。
    • final boolean compareAndSet(int i, E expect, E update): 如果索引 i 处的值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • final boolean weakCompareAndSet(int i, E expect, E update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • int length(): 返回数组的长度。
    • String toString(): 返回数组的字符串表示形式。

示例代码:以下是一个完整的例子,展示了如何使用 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray 与线程池结合。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicLongArray;
import java.util.concurrent.atomic.AtomicReferenceArray;

public class AtomicArraysExample {

    // 创建 AtomicIntegerArray 实例
    private static final AtomicIntegerArray intArray = new AtomicIntegerArray(10);

    // 创建 AtomicLongArray 实例
    private static final AtomicLongArray longArray = new AtomicLongArray(10);

    // 创建 AtomicReferenceArray 实例
    private static final AtomicReferenceArray<String> refArray = new AtomicReferenceArray<>(new String[10]);

    public static void main(String[] args) {
        // 初始化 AtomicReferenceArray
        for (int i = 0; i < 10; i++) {
            refArray.set(i, "A" + i);
        }

        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(() -> {
                // 每个任务递增对应的数组元素 1000 次
                for (int j = 0; j < 1000; j++) {
                    incrementElement(index);
                    incrementLongElement(index);
                    updateReferenceElement(index);
                }
            });
        }

        // 关闭线程池,并等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 打印最终的数组状态
        System.out.println("Final AtomicIntegerArray: " + intArray);
        System.out.println("Final AtomicLongArray: " + longArray);
        System.out.println("Final AtomicReferenceArray: " + refArray);
    }

    // 原子递增方法
    private static void incrementElement(int index) {
        // 使用 AtomicIntegerArray 的 getAndIncrement 方法进行原子递增
        intArray.getAndIncrement(index);
    }

    // 原子递增方法
    private static void incrementLongElement(int index) {
        // 使用 AtomicLongArray 的 getAndAdd 方法进行原子递增
        longArray.getAndAdd(index, 1L);
    }

    // 更新引用方法
    private static void updateReferenceElement(int index) {
        // 使用 AtomicReferenceArray 的 compareAndSet 方法进行原子更新
        String oldValue = refArray.get(index);
        String newValue = oldValue + "_updated";
        boolean wasUpdated = refArray.compareAndSet(index, oldValue, newValue);
        if (!wasUpdated) {
            System.out.println("Failed to update reference at index " + index);
        }
    }
}

输出结果:

Final AtomicIntegerArray: [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]
Final AtomicLongArray: [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]
Final AtomicReferenceArray: [A0_updated_updated_updated_updated_updated_updated_updated_updated_updated...

9.3 引用类型的原子操作

9.3.1 AtomicReference

AtomicReference 提供了对引用对象的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicReference 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 构造器:

    • AtomicReference(): 初始化为 null
    • AtomicReference(V initialValue): 初始化为指定值。
  • 获取和设置:

    • V get(): 获取当前引用的对象。
    • void set(V newValue): 设置新的引用对象。
    • void lazySet(V newValue): 最终设置新的引用对象,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • V getAndSet(V newValue): 获取当前引用的对象并设置新值。
    • boolean compareAndSet(V expect, V update): 如果当前引用的对象等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • boolean weakCompareAndSet(V expect, V update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
  • 其他方法:

    • String toString(): 返回当前引用对象的字符串表示形式。
9.3.2 AtomicReferenceFieldUpdater 

AtomicReferenceFieldUpdater 提供了对对象中某个字段的原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicReferenceFieldUpdater 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 静态工厂方法:

    • public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass, Class<W> vclass, String fieldName): 创建并返回一个新的 AtomicReferenceFieldUpdater 实例。需要指定包含字段的类、字段的类型以及字段名。
  • 获取和设置:

    • public final W get(U obj): 获取指定对象的字段值。
    • public final void set(U obj, W newValue): 设置指定对象的字段值。
    • public final void lazySet(U obj, W newValue): 最终设置指定对象的字段值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • public final boolean compareAndSet(U obj, W expect, W update): 如果指定对象的字段值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • public final boolean weakCompareAndSet(U obj, W expect, W update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
    • public final W getAndSet(U obj, W newValue): 获取并设置指定对象的字段值。

使用限制

  • 字段必须是 volatile 修饰的。
  • 字段不能是 static 或 final
  • 字段的访问级别必须是 public 或者提供给 AtomicReferenceFieldUpdater 的类能够访问该字段。
9.3.3 AtomicMarkableReference 

AtomicMarkableReference 提供了一个原子的引用和标记位。这个类允许你在多线程环境中对引用对象及其关联的布尔标记进行原子操作。AtomicMarkableReference 通过底层硬件支持(如 CAS 操作)来确保这些操作是线程安全的。

  • 构造器:

    • AtomicMarkableReference(V initialRef, boolean initialMark): 创建一个新的 AtomicMarkableReference 实例,初始引用为 initialRef,初始标记为 initialMark
  • 获取和设置:

    • V getReference(): 获取当前引用。
    • boolean isMarked(): 获取当前标记。
    • void set(V newReference, boolean newMark): 设置新的引用和标记。
    • void lazySet(V newReference, boolean newMark): 最终设置新的引用和标记,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • boolean attemptMark(V expectedReference, boolean newMark): 如果当前引用等于预期引用,则将标记设置为 newMark 并返回 true;否则不更改任何内容并返回 false
    • boolean weakCompareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark): 如果当前引用和标记等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false。此方法具有较弱的内存模型保证。
    • boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark): 类似于 weakCompareAndSet,但具有更强的内存模型保证。

示例代码: 下面是一个包含 AtomicReferenceAtomicReferenceFieldUpdaterAtomicMarkableReference 的简单例子,并结合线程池来展示这些原子类在多线程环境下的使用。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class AtomicReferencesExample {

    // 创建一个 AtomicReference 实例
    private static final AtomicReference<String> atomicRef = new AtomicReference<>("InitialValue");

    // 创建一个 AtomicMarkableReference 实例
    private static final AtomicMarkableReference<String> markableRef = new AtomicMarkableReference<>("InitialValue", false);

    // 定义一个包含可更新字段的类
    public static class RefHolder {
        volatile String ref;
    }

    // 创建一个 AtomicReferenceFieldUpdater 实例
    private static final AtomicReferenceFieldUpdater<RefHolder, String> fieldUpdater =
            AtomicReferenceFieldUpdater.newUpdater(RefHolder.class, String.class, "ref");

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                // 每个任务对 AtomicReference 进行操作
                updateAtomicReference();

                // 每个任务对 AtomicMarkableReference 进行操作
                updateAtomicMarkableReference();

                // 每个任务对 AtomicReferenceFieldUpdater 进行操作
                updateAtomicReferenceField();
            });
        }

        // 关闭线程池,并等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 打印最终的状态
        System.out.println("Final AtomicReference: " + atomicRef.get());
        System.out.println("Final AtomicMarkableReference: " + markableRef.getReference() + ", Mark: " + markableRef.isMarked());
        System.out.println("Final AtomicReferenceFieldUpdater: " + fieldUpdater.get(new RefHolder()));
    }

    // 原子更新方法
    private static void updateAtomicReference() {
        // 尝试将 AtomicReference 的值从 "InitialValue" 更新为 "UpdatedValue"
        boolean wasUpdated = atomicRef.compareAndSet("InitialValue", "UpdatedValue");
        if (wasUpdated) {
            System.out.println("AtomicReference updated to UpdatedValue");
        }
    }

    // 原子更新带标记的方法
    private static void updateAtomicMarkableReference() {
        // 尝试将 AtomicMarkableReference 的值从 ("InitialValue", false) 更新为 ("UpdatedValue", true)
        boolean wasUpdated = markableRef.compareAndSet("InitialValue", "UpdatedValue", false, true);
        if (wasUpdated) {
            System.out.println("AtomicMarkableReference updated to UpdatedValue, Mark: true");
        }
    }

    // 使用 AtomicReferenceFieldUpdater 更新字段的方法
    private static void updateAtomicReferenceField() {
        RefHolder holder = new RefHolder();
        holder.ref = "InitialValue";

        // 尝试将 holder.ref 的值从 "InitialValue" 更新为 "UpdatedValue"
        boolean wasUpdated = fieldUpdater.compareAndSet(holder, "InitialValue", "UpdatedValue");
        if (wasUpdated) {
            System.out.println("AtomicReferenceFieldUpdater updated to UpdatedValue");
        }
    }
}

输出结果:

AtomicReference updated to UpdatedValue
AtomicMarkableReference updated to UpdatedValue, Mark: true
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
AtomicReferenceFieldUpdater updated to UpdatedValue
Final AtomicReference: UpdatedValue
Final AtomicMarkableReference: UpdatedValue, Mark: true
Final AtomicReferenceFieldUpdater: null

9.4 字段类型的原子操作

9.4.1 AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater 提供了一种机制来对对象中的整型字段进行原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicIntegerFieldUpdater 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 静态工厂方法:

    • public static <T> AtomicIntegerFieldUpdater<T> newUpdater(Class<T> tclass, String fieldName): 创建并返回一个新的 AtomicIntegerFieldUpdater 实例。需要指定包含字段的类以及字段名。
  • 获取和设置:

    • public final int get(T obj): 获取指定对象的字段值。
    • public final void set(T obj, int newValue): 设置指定对象的字段值。
    • public final void lazySet(T obj, int newValue): 最终设置指定对象的字段值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • public final boolean compareAndSet(T obj, int expect, int update): 如果指定对象的字段值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • public final boolean weakCompareAndSet(T obj, int expect, int update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
    • public final int getAndSet(T obj, int newValue): 获取并设置指定对象的字段值。
    • public final int getAndIncrement(T obj): 获取并递增指定对象的字段值。
    • public final int getAndDecrement(T obj): 获取并递减指定对象的字段值。
    • public final int getAndAdd(T obj, int delta): 获取并加上增量 delta 到指定对象的字段值。

使用限制

  • 字段必须是 volatile 修饰的。
  • 字段不能是 static 或 final
  • 字段的访问级别必须是 public 或者提供给 AtomicIntegerFieldUpdater 的类能够访问该字段。
9.4.2 AtomicLongFieldUpdater

AtomicLongFieldUpdater 提供了一种机制来对对象中的长整型字段进行原子操作。这些操作在多线程环境中是线程安全的,不需要额外的同步机制。AtomicLongFieldUpdater 通过底层硬件支持(如 CAS 操作)来确保操作的原子性。

  • 静态工厂方法:

    • public static <T> AtomicLongFieldUpdater<T> newUpdater(Class<T> tclass, String fieldName): 创建并返回一个新的 AtomicLongFieldUpdater 实例。需要指定包含字段的类以及字段名。
  • 获取和设置:

    • public final long get(T obj): 获取指定对象的字段值。
    • public final void set(T obj, long newValue): 设置指定对象的字段值。
    • public final void lazySet(T obj, long newValue): 最终设置指定对象的字段值,但不保证立即生效(通常用于减少内存屏障的影响)。
  • 原子更新:

    • public final boolean compareAndSet(T obj, long expect, long update): 如果指定对象的字段值等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false
    • public final boolean weakCompareAndSet(T obj, long expect, long update): 类似于 compareAndSet,但具有较弱的内存模型保证(通常用于性能优化)。
    • public final long getAndSet(T obj, long newValue): 获取并设置指定对象的字段值。
    • public final long getAndIncrement(T obj): 获取并递增指定对象的字段值。
    • public final long getAndDecrement(T obj): 获取并递减指定对象的字段值。
    • public final long getAndAdd(T obj, long delta): 获取并加上增量 delta 到指定对象的字段值。

使用限制

  • 字段必须是 volatile 修饰的。
  • 字段不能是 static 或 final
  • 字段的访问级别必须是 public 或者提供给 AtomicLongFieldUpdater 的类能够访问该字段。
9.4.3 AtomicStampedReference 

AtomicStampedReference 提供了一种机制来对引用对象及其关联的整型“戳记”(stamp)进行原子操作。这个类主要用于解决 ABA 问题(即在多线程环境下,一个值被修改为 B,然后再改回 A,导致其他线程无法检测到中间的变化)。通过引入一个额外的整数戳记,可以跟踪引用对象的变化次数,从而避免 ABA 问题。

  • 构造器:

    • public AtomicStampedReference(V initialRef, int initialStamp): 创建一个新的 AtomicStampedReference 实例,初始引用为 initialRef,初始戳记为 initialStamp
  • 获取和设置:

    • public V getReference(): 获取当前引用。
    • public int getStamp(): 获取当前戳记。
    • public V get(int[] stampHolder): 获取当前引用,并将当前戳记放入 stampHolder 数组中。
    • public void set(V newReference, int newStamp): 设置新的引用和戳记。
    • public boolean weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp): 如果当前引用和戳记等于预期值,则将其设置为新值,并返回 true;否则不更改任何内容并返回 false。此方法具有较弱的内存模型保证。
    • public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp): 类似于 weakCompareAndSet,但具有更强的内存模型保证。

示例代码: 下面是一个包含 AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicStampedReference 的简单例子,并结合线程池来展示这些原子类在多线程环境下的使用。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicUpdatersExample {

    // 定义一个包含可更新字段的类
    public static class NumberHolder {
        volatile int intValue;
        volatile long longValue;
    }

    // 创建 AtomicIntegerFieldUpdater 实例
    private static final AtomicIntegerFieldUpdater<NumberHolder> intFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(NumberHolder.class, "intValue");

    // 创建 AtomicLongFieldUpdater 实例
    private static final AtomicLongFieldUpdater<NumberHolder> longFieldUpdater =
            AtomicLongFieldUpdater.newUpdater(NumberHolder.class, "longValue");

    // 创建 AtomicStampedReference 实例
    private static final AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("InitialValue", 0);

    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // 创建一个 NumberHolder 实例
        NumberHolder numberHolder = new NumberHolder();
        numberHolder.intValue = 0;
        numberHolder.longValue = 0L;

        // 提交 10 个任务到线程池
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(() -> {
                // 每个任务对 AtomicIntegerFieldUpdater 进行操作
                updateIntField(numberHolder);

                // 每个任务对 AtomicLongFieldUpdater 进行操作
                updateLongField(numberHolder);

                // 每个任务对 AtomicStampedReference 进行操作
                updateStampedReference();
            });
        }

        // 关闭线程池,并等待所有任务完成
        executor.shutdown();
        try {
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 打印最终的状态
        System.out.println("Final intValue: " + numberHolder.intValue);
        System.out.println("Final longValue: " + numberHolder.longValue);
        System.out.println("Final AtomicStampedReference: " + stampedRef.getReference() + ", Stamp: " + stampedRef.getStamp());
    }

    // 原子更新整型字段的方法
    private static void updateIntField(NumberHolder holder) {
        // 尝试将 intValue 从 0 更新为 1
        boolean wasUpdated = intFieldUpdater.compareAndSet(holder, 0, 1);
        if (wasUpdated) {
            System.out.println("AtomicIntegerFieldUpdater updated to 1");
        }
    }

    // 原子更新长整型字段的方法
    private static void updateLongField(NumberHolder holder) {
        // 尝试将 longValue 从 0 更新为 1
        boolean wasUpdated = longFieldUpdater.compareAndSet(holder, 0L, 1L);
        if (wasUpdated) {
            System.out.println("AtomicLongFieldUpdater updated to 1");
        }
    }

    // 使用 AtomicStampedReference 更新引用和时间戳的方法
    private static void updateStampedReference() {
        String currentValue = stampedRef.getReference();
        int currentStamp = stampedRef.getStamp();

        // 尝试将引用从 "InitialValue" 更新为 "UpdatedValue",并增加时间戳
        boolean wasUpdated = stampedRef.compareAndSet(currentValue, "UpdatedValue", currentStamp, currentStamp + 1);
        if (wasUpdated) {
            System.out.println("AtomicStampedReference updated to UpdatedValue, Stamp: " + (currentStamp + 1));
        }
    }
}

输出结果:

AtomicLongFieldUpdater updated to 1
AtomicStampedReference updated to UpdatedValue, Stamp: 2
AtomicIntegerFieldUpdater updated to 1
AtomicStampedReference updated to UpdatedValue, Stamp: 4
AtomicStampedReference updated to UpdatedValue, Stamp: 3
AtomicStampedReference updated to UpdatedValue, Stamp: 1
AtomicStampedReference updated to UpdatedValue, Stamp: 5
AtomicStampedReference updated to UpdatedValue, Stamp: 6
AtomicStampedReference updated to UpdatedValue, Stamp: 7
AtomicStampedReference updated to UpdatedValue, Stamp: 8
AtomicStampedReference updated to UpdatedValue, Stamp: 9
Final intValue: 1
Final longValue: 1
Final AtomicStampedReference: UpdatedValue, Stamp: 9

标签:Java,Thread,int,void,final,线程,机制,public
From: https://blog.csdn.net/weixin_47363690/article/details/142410970

相关文章

  • java+vue计算机毕设儿童教育系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在信息化高速发展的今天,教育领域正经历着前所未有的变革。随着社会对儿童教育质量要求的不断提升,传统教育模式已难以满足个性化、高效化、互动化的学......
  • java+vue计算机毕设短视频的推荐平台【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展和智能设备的普及,短视频已成为人们日常生活中不可或缺的一部分,其碎片化、高传播性及强娱乐性的特点深受各年龄段用户的喜爱......
  • java+vue计算机毕设多吃点订餐系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着移动互联网技术的飞速发展,人们的日常生活方式正经历着前所未有的变革,特别是在餐饮消费领域。快节奏的生活使得“外卖经济”迅速崛起,成为现代人解......
  • 2024最新版Java面试题及答案汇总
    1.对字符串的都有哪些方法?详细说明下。具体有String、StringBuffer和StringBuilder这三个类。String是不可变类,每次操作都会生成新的String对象,并将结果指针指向新的对象,由此会产生内存碎片。如果要频繁对字符串修改,建议采用StringBuffer和StringBuilder。StringBuff......
  • 最新整理Java面试八股文(程序员必备)
    1.面向对象的三大特性?封装:核心思想就是“隐藏细节”,对外提供访问属性的方法。保证了数据的安全和程序的稳定。继承:将子类共性的属性或者方法抽取出来成为父类,子类继承后就不用重复定义父类的属性和方法,只需要扩展自己的个性化。强制继承父类非私有的属性和方法,破坏了封装性原......
  • 2024Java核心面试题合集
    1.保证并发安全的三大特性? 原子性:一次或多次操作在执行期间不被其他线程影响可见性:当一个线程在工作内存修改了变量,其他线程能立刻知道有序性:JVM对指令的优化会让指令执行顺序改变,有序性是禁止指令重排2.volatile保证变量的可见性和有序性,不保证原子性。使用了volatile......
  • 2024年全新Java面试题整理
    1、多线程的价值?(1)发挥多核CPU的优势多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。(2)防止阻塞从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线......
  • idea运行java项目main方法报build failure错误的解决方法BR
    当在使用IntelliJIDEA运行Java项目的main方法时遇到"BuildFailure"错误,这通常意味着在项目的构建过程中遇到了问题。解决这类问题通常需要系统地检查和调整项目设置、代码、依赖项等多个方面。以下是一些详细的解决步骤,以及一个简单的代码示例,用于展示如何确保Java程......
  • ECMAScript与JavaScript的区别:深入解析与代码实践
    ECMAScript与JavaScript的区别:深入解析与代码实践在Web开发领域,ECMAScript(通常缩写为ES)和JavaScript是两个密不可分但又有所区别的概念。尽管它们之间有着紧密的联系,理解它们之间的区别对于开发者来说至关重要。本文将深入探讨ECMAScript与JavaScript的区别,并通过代码示例......
  • 基于SSM的高校毕业设计(选题)管理系统。Javaee项目。
    演示视频:基于SSM的高校毕业设计(选题)管理系统。Javaee项目。项目介绍:采用M(model)V(view)C(controller)三层体系结构,通过Spring+SpringMvc+Mybatis+Jsp+Bootstrap来实现。MySQL数据库作为系统数据储存平台,实现了基于B/S结构的Web系统。系统设计思想一个成功的......