(Java高并发)Java学习路线(漫漫人生路,步步架构梦)Java Learning Path (Long Life Path, Step by step Structure Dream)

前言

本篇博客主要汇总Java高并发相关的知识点,通过阅读博客,希望能对相关知识点进行串联起来,形成体系O(∩_∩)O哈哈~

Java多线程基础知识总结

  1. 线程的生命周期:新建状态->就绪状态->运行状态->阻塞状态->终止状态
  2. 线程的创建方式:
    • 继承Thread类,重写run()方法
    • 实现Runnable接口,实现run()方法,传递给Thread类的构造方法
    • 实现Callable接口,重写call()方法,传递给FutureTask类,再传递给Thread类
  3. 线程的启动:
    • 调用start()方法,让线程进入就绪状态,等待JVM调用,进入运行状态
    • 不能直接调用run()方法,那样就变成了普通方法调用,没有开启新的线程
  4. 线程的中断:
    • 调用线程的interrupt()方法,设置中断标识位
    • 被中断的线程可以通过isInterrupted()判断中断标识位是否设置,进行相应处理
    • 静态方法Thread.interrupted()可以清除当前线程的中断标识位
  5. 线程的挂起与恢复:
    • 调用线程的suspend()方法,将线程挂起,进入阻塞状态
    • 调用线程的resume()方法,将线程恢复,重新进入就绪状态
  6. 线程的优先级:
    • 可以通过setPriority()设置线程的优先级,优先级高的线程比较易获取CPU执行
    • 优先级范围是1-10,默认是5
    • 不能保证高优先级的线程一定先执行
  7. 线程间通信:
    • wait():让当前线程等待,直到其他线程调用notify()或notifyAll()方法唤醒当前线程
    • notify():唤醒等待在对象的监视器上的单个线程
    • notifyAll():唤醒等待在对象的监视器上的所有线程

Java线程池详解

  1. 为什么要用线程池?
    • 重用线程,减少对象创建和销毁的开销,提高响应速度
    • 有限制的线程数量,防止过度消耗系统资源
    • 方便线程管理,统一调度
  2. 线程池的结构
    • 任务队列:用于存放等待执行的任务
    • 工作线程:执行任务的线程
    • 线程池管理器:管理线程的增减和调度等
  3. Java中的线程池框架:Executor框架
    • Executor:定义了执行任务的规范
    • ExecutorService:继承Executor,定义了管理执行器生命周期的服务
    • ThreadPoolExecutor:实现ExecutorService,表示线程池
    • Executors:工厂类,用于创建不同配置的线程池
  4. ThreadPoolExecutor的参数说明
    • corePoolSize:核心池大小,默认情况下创建的线程数
    • maximumPoolSize:最大线程数,超过这个数字就会用SynchronousQueue来暂存任务
    • keepAliveTime:非核心线程闲置时间,超时就回收
    • unit:keepAliveTime的单位
    • workQueue:任务队列,缓存执行任务的队列
    • threadFactory:线程工厂,用于创建线程
    • handler:拒绝策略,当队列和线程池都满了,采取的策略
  5. ThreadPoolExecutor的执行流程
    1. 首先将任务加入任务队列workQueue,如果成功,则等待被工作线程执行
    2. 如果工作线程少于corePoolSize,则创建新线程执行任务
    3. 如果工作线程等于或多于corePoolSize,将任务加入workQueue
    4. 如果workQueue已满,但工作线程少于maximumPoolSize,则创建新线程执行任务
    5. 如果workQueue已满和工作线程等于maximumPoolSize,采取handler的拒绝策略处理任务
    6. 空闲的工作线程等待keepAliveTime后会被回收

ThreadLocal简介

  1. ThreadLocal的作用是提供线程本地变量,每个线程都可以有自己单独的变量副本,可以解决多线程程序中变量传递的问题。
  2. ThreadLocal内部的实现原理是维护一个Map,key是线程对象,value是线程本地变量副本。不同的线程访问ThreadLocal变量时,会访问不同的副本,相互不会产生影响。
  3. ThreadLocal常见的使用场景:
    • 数据库连接:每个线程维护自己的数据库连接,相互隔离
    • 事务管理:每个线程管理自己的事务,不会对其他线程产生影响
    • Servlet:每个线程维护自己的HttpServletRequest和HttpServletResponse对象
    • 随机数:每个线程有自己独立的随机数生成器
  4. ThreadLocal的常用API:
    • ThreadLocal():创建ThreadLocal对象
    • set(T value):设置当前线程的线程本地变量的值
    • get():获取当前线程的线程本地变量的值
    • remove():移除当前线程的线程本地变量的值
    • initialValue():返回当前线程本地变量的初始值
  5. ThreadLocal内存泄漏问题:
    ThreadLocalMap是ThreadLocal维护的一个静态内部类,用于存储每个线程的变量副本。
    如果一个ThreadLocal没有被回收,它对ThreadLocalMap的引用就不会消失,导致线程无法被回收,造成内存泄漏。
    解决方法:
    手动调用remove()方法或重写ThreadLocal的finalize()方法。
    ThreadLocal为解决多线程变量传递问题提供了一种轻量级的机制,但也容易出现内存泄漏问题,需要在使用时充分考虑。

    Java守护线程

    Java守护线程(Daemon Thread)是运行在后台的一种特殊线程。
    它的主要作用是为其他线程的运行提供便利服务,守护线程并不属于程序中不可或缺的线程,如果所有的非守护线程结束了运行,程序也就终止,同时守护线程也会被强制结束。
    主要特征:

  6. 守护线程是为用户线程提供服务的线程,它们没有执行用户的主要任务;
  7. 当程序中所有的用户线程结束时,程序也就终止运行,同时守护线程也会被强制结束;
  8. 在程序启动时,Java Virtual Machine(JVM)会将所有的守护线程设置为守护线程。如果你的程序没有设置daemon线程,那默认该程序没有守护线程;
  9. 可以通过调用setDeamon()方法设置线程为守护线程或用户线程。但是,这个调用必须在线程启动前进行,一旦线程启动后,不能改变其线程身份;
  10. 主线程默认是用户线程。
    使用场景:
  11. 垃圾回收线程就是一个典型的守护线程示例;
  12. 定时分析内存使用率的线程;
  13. 定期执行日志文件与数据库同步的线程;
  14. 定期重启应用服务的线程,高可用性就需要用到;
    示例代码:

    public class DaemonThreadExample {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " end!");
            }
        });
        thread.setDaemon(true); // 将线程设置为守护线程
        thread.start();
        System.out.println(Thread.currentThread().getName() + " end!");
    } 
    }

    结果:
    main end!
    说明守护线程thread被随着主线程main的结束而结束。

    Java多线程之Executor框架

    Executor框架是Java提供的一种线程池管理框架。它提供了一种标准的任务执行和调度机制,可以大大简化在多线程情况下执行任务的代码。
    主要接口和类:

    • Executor:用于执行任务的简单接口。定义了execute(Runnable)方法,用于执行任务。
    • ExecutorService:继承自Executor,添加了线程池的控制和管理方法。添加了submit、invokeAll、invokeAny以及shutDown等方法。常见的实现有ThreadPoolExecutor和ScheduledThreadPoolExecutor。
    • ScheduledExecutorService:继承自ExecutorService,添加了定时任务和周期任务的执行方法。如schedule、scheduleAtFixedRate和scheduleWithFixedDelay。
    • ThreadPoolExecutor:线程池的主要实现类。构造方法比较复杂,一般使用Executors来创建。
    • Executors:线程池工厂类,用于创建不同类型的线程池。常用的有:
      • newCachedThreadPool():创建一个可缓存的线程池。线程池的线程数可以随需要增加到最大值,然后回收空闲线程,存活时间指定。
      • newFixedThreadPool(n):创建一个固定大小的线程池。线程数指定为n,允许的最大值也是n。
      • newSingleThreadExecutor():创建一个只有一个线程的线程池。相当于newFixedThreadPool(1)。
      • newScheduledThreadPool(n):创建一个大小为n的线程池,处理定时任务和周期任务。

示例代码:

public class ExecutorTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " running!");
                }
            });
        }
        executorService.shutdown();
    }
}

结果:
pool-1-thread-1 running!
pool-1-thread-3 running!
pool-1-thread-2 running!
pool-1-thread-2 running!
...
可以看到线程池会重复使用固定数量的线程来执行任务,并且按顺序从线程池获取线程。

同步工具类CountDownLatch

CountDownLatch 是 Java 提供的一个同步工具类,用于协调多个线程之间的同步。
主要作用是:使一个线程在等待其他线程完成各自工作之后再继续执行。
构造方法:

  • CountDownLatch(int count):创建一个CountDownLatch对象,初始化计数器count。
    主要方法:
  • countDown():当前计数器的值减1。
  • await():调用该方法的线程会被阻塞,直到计数器的值为0才返回继续执行。
  • getCount():返回当前计数器的值。
    示例代码:

    public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " running!");
                countDownLatch.countDown();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " running!");
                countDownLatch.countDown();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " running!");
                countDownLatch.countDown();
            }
        }).start();
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + " continue running!");
    }
    }

    运行结果:
    Thread-0 running!
    Thread-1 running!
    Thread-2 running!
    main continue running!
    可以看到,main线程调用await()方法后被阻塞,直到其他3个线程都运行完成调用countDown()后,才继续执行。
    这就是 CountDownLatch 的基本作用,用于线程之间的同步和执行顺序控制。

    通过socket、多线程、动态代理、反射 实现RPC远程方法调用

    RPC(Remote Procedure Call)远程过程调用,是一种进程间通信方式,允许一个进程调用另一个进程中的方法,让调用进程获得另一个网络地址空间中运行的方法的返回值。
    实现RPC的关键步骤:

    1. 在服务端开启ServerSocket,监听客户端连接
    2. 客户端通过Socket连接服务端,发送调用的方法名称和参数
    3. 服务端接收客户端发送的方法名称和参数
    4. 通过反射调用方法,获得返回值
    5. 将返回值发送给客户端
    6. 客户端接收返回值
      具体实现代码如下:
      服务端:

      public class RpcServer {
      public static void main(String[] args) throws Exception{
      ServerSocket serverSocket = new ServerSocket(8888);
      while (true) {
          Socket client = serverSocket.accept();
          new Thread(new Runnable() {
              @Override
              public void run() {
                  try {
                      ObjectInputStream input = new ObjectInputStream(client.getInputStream());
                      String methodName = input.readUTF();
                      Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                      Object[] arguments = (Object[]) input.readObject();
                      Object result = methodInvoke(methodName, parameterTypes, arguments);
                      ObjectOutputStream output = new ObjectOutputStream(client.getOutputStream());
                      output.writeObject(result);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }).start();
      }
      }
      private static Object methodInvoke(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
      Method method = Class.forName("java.lang.Math").getMethod(methodName, parameterTypes);
      return method.invoke(null, arguments);
      }
      }

      客户端:

      public class RpcClient {
      public static void main(String[] args) throws Exception{
      Socket socket = new Socket("127.0.0.1", 8888);
      ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
      output.writeUTF("pow");
      output.writeObject(new Class[] {double.class, double.class});
      output.writeObject(new Object[] {2, 3});
      ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
      Object result = input.readObject();
      System.out.println(result);
      } 
      }

      运行结果:
      8.0
      可以看到,客户端调用Math.pow(2, 3)方法,服务端通过反射执行该方法,并将返回值发送给客户端,实现了RPC调用。

      同步容器与并发容器

      同步容器与并发容器是Java提供的两种容器类,主要区别在于:
      同步容器:

  • 内部使用同步锁(synchronized)来控制线程对容器的访问,保证线程安全;
  • 但同一时间只允许一个线程访问容器,效率较低。
    并发容器:
  • 使用更高级的锁(如CAS算法)和数据结构来保证线程安全,可以允许多个线程并发访问容器;
  • 效率比同步容器高,适用于高并发场景。
    主要的同步容器有:
  • Hashtable:同步的HashMap;
  • Vector:同步的ArrayList;
  • Stack:同步的Stack。
    主要的并发容器有:
  • ConcurrentHashMap:高效的HashMap;
  • CopyOnWriteArrayList:线程安全的ArrayList;
  • BlockingQueue:阻塞队列,如ArrayBlockingQueue和LinkedBlockingQueue。
    示例代码:
    同步容器(Hashtable):

    Hashtable<String, Integer> hashtable = new Hashtable<>();
    hashtable.put("key1", 1);
    hashtable.put("key2", 2);

    并发容器(ConcurrentHashMap):

    ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    map.put("key1", 1);
    map.put("key2", 2);

    可以看出,在声明和使用上,并发容器与同步容器非常相似,但是在内部实现上使用了更高级的锁和算法来实现线程安全,比同步容器效率更高。
    所以,在选择容器类时,如果高并发是重要考量因素,应该优先选择并发容器;如果对并发要求不高,同步容器也可以满足需求。

    Java高并发编程实战-锁

    Java高并发编程中,锁是控制线程同步访问共享资源的主要手段。主要的锁有:

    1. 互斥锁(mutex):同一时间只允许一个线程访问共享资源,如synchronized关键字实现的锁。
    2. 读写锁(read-write lock):同一时间允许多个线程读共享资源,但只允许一个线程写共享资源。如ReentrantReadWriteLock实现。
    3. 信号量(semaphore):通过维持一个计数器和两个队列(可选的)来控制资源的访问,并发程度可以设置。常用来实现资源池。
    4. 栅栏(barrier):多个线程必须等待其他线程到达某个执行点后再继续执行,常用来实现多阶段计算。如CyclicBarrier实现。
      示例代码:
      互斥锁(synchronized):

      public synchronized void method() {
      // ...
      }

      读写锁(ReentrantReadWriteLock):

      ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
      ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
      ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
      readLock.lock();  // 上读锁
      // ... 读操作
      readLock.unlock();
      writeLock.lock(); // 上写锁 
      // ... 写操作
      writeLock.unlock();

      信号量(Semaphore):

      Semaphore semaphore = new Semaphore(5); // 最多5个并发线程
      semaphore.acquire();  // 获取一个许可
      // ... 执行操作
      semaphore.release(); // 释放一个许可

      栅栏(CyclicBarrier):

      CyclicBarrier barrier = new CyclicBarrier(3); // 三个线程到达栅栏
      new Thread(() -> {
      // 等待其他线程
      barrier.await(); 
      // 同时继续执行
      }).start();
      new Thread(() -> {
      // 等待其他线程
      barrier.await(); 
      // 同时继续执行  
      }).start();
      barrier.await(); 
      // 继续执行主线程

      Java高并发编程实战-原子性、可见性、有序性

      在Java高并发编程中,需要关注三个重要特征:

    5. 原子性:一个操作或者多次操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。简单来说,就是要么一块执行,要么一块不执行。
    6. 可见性:当一个线程修改了共享变量的值,其他线程能够立即看到修改的值。
    7. 有序性:代码在执行的顺序与编写的顺序一致。
      Java提供的主要手段来保证这三个特征分别是:
    8. 原子性:使用CAS(Compare And Swap)算法实现的原子操作类AtomicInteger、AtomicLong等;lock锁;synchronized等。
    9. 可见性: volatile关键字;synchronized及其同步方法和代码块;final关键字等。
    10. 有序性:happens-before规则;volatile关键字;synchronized;final关键字等。
      示例代码:
      原子性(AtomicInteger):

      AtomicInteger count = new AtomicInteger(0);
      count.incrementAndGet(); // 自增并返回,原子操作

      可见性(volatile):

      public volatile int count = 0;
      // 线程1对count++,线程2能立即见到修改的值 

      有序性(synchronized):

      synchronized(obj) {
      x = 1;   // 线程执行到此,读取x的值
      y = 2;   // 读取y的值,由有序性保证一定在x = 1后执行
      }

      这三个特征在并发编程中至关重要,synchronized、volatile和CAS等手段的运用能够保证这三个特征,从而实现正确的Java高并发程序。

      Java高并发编程实战-java内存模型与Java对象结构

      Java内存模型定义了线程与主内存之间的抽象关系,它规定了线程访问主内存的方式,以实现Java程序在多线程环境下的正确执行。
      Java对象在内存中的布局可以分为三块:

    11. 对象头(Header):包含对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID等。
    12. 实例数据(Instance Data):保存对象的字段值,包括父类的字段值。
    13. 对象数组的长度(Array Length):如果对象是一个数组,该部分记录数组的长度。
      Java内存模型规定:
    14. 线程本地内存与主内存之间是弱一致的,线程可以把主内存中的数据放入本地内存作为副本;本地内存比主内存的访问速度快,可以提高程序效率。
    15. 线程对主内存与本地内存的修改必须通过同步机制来保证一致性,如lock锁、volatile变量、final变量等。
    16. 线程无法直接读写主内存,只能通过同步机制来间接地对主内存进行操作。每个线程都有一个本地内存,线程的读写操作基本上都是对本地内存的读写。
    17. 主内存包含可变和不可变变量两种数据,不可变变量可以多个线程同时读,而可变变量需要使用同步机制来保证一致。
    18. 对象结构包含头信息、实例数据和数组长度等部分,其中部分数据用于同步和锁定信息。
      Java内存模型 bemroyjcdistryqjavamsL这些规则来定义程序在并发执行时的正确语义,保证线程之间的可见性与有序性。理解这些规则对并发编程至关重要。

      Java高并发编程实战-synchronized与Lock底层原理

      synchronized与Lock都是Java提供的实现互斥锁的重要手段,但底层实现原理不同:
      synchronized:

  • 基于JVM的monitor对象实现,每个对象都有一个monitor与之关联。
  • 当一个线程访问同步代码块(或方法)时,会自动获取与之关联的对象的monitor,其他试图访问该代码块的线程会被阻塞。
  • 释放monitor的时机是:同步代码块执行结束;synchronized方法返回;发生异常。
  • 实现可重入锁,同一个线程可以重复获取同一把锁。
    Lock:
  • 是java.util.concurrent.locks包下的接口,实现类有ReentrantLock、ReentrantReadWriteLock等。
  • 基于AQS(AbstractQueuedSynchronizer)实现,可以实现公平锁、非公平锁和可重入锁等。
  • 获取和释放锁需要通过lock()和unlock()方法手动控制。
  • 可以实现可轮询锁、读写锁和条件变量等高级功能。
    相比而言:
  • synchronized是JVM实现的轻量级锁,符合大多数场景需要,开销较小;Lock是Java实现的重量级锁,功能更强大,适用于更复杂的场景。
  • synchronized无法判断是否获取了锁,Lock可以判断是否获取到了锁。
  • synchronized会自动释放锁,Lock需要手动释放锁unlock()。
  • synchronized可重入锁,不可以中断等待锁的线程;Lock也是可重入锁,可以中断等待锁的线程。
    示例代码:
    synchronized:

    public synchronized void method() {
    // ...
    } 

    Lock:

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
    // ... 
    } finally {
    lock.unlock();  
    }

    Java高并发编程实战-异步注解@Async自定义线程池

    在Java高并发编程中,使用@Async注解可以很方便地实现异步方法调用。
    @Async会使用DefaultManagedExecutorService作为线程池执行异步方法。
    如果想自定义线程池,可以按如下步骤实现:

    1. 实现Executor接口,自定义线程池类,如以下MyExecutor:
      public class MyExecutor implements Executor {
      @Override
      public void execute(Runnable command) {
      // 自定义线程池逻辑
      }
      }
    2. 实现AsyncConfigurer接口,并覆盖getAsyncExecutor()方法,返回自定义的线程池实例:
      public class MyAsyncConfigurer implements AsyncConfigurer {
      @Override 
      public Executor getAsyncExecutor() {
      return new MyExecutor(); 
      }
      }
    3. 在SpringBoot启动类上添加@EnableAsync注解启用异步方法调用功能,并指定AsyncConfigurer Bean:
      @SpringBootApplication
      @EnableAsync(proxyTargetClass = true) 
      public class Application implements AsyncConfigurer {
      @Bean
      public AsyncConfigurer asyncConfigurer() {
      return new MyAsyncConfigurer();
      }
      } 
    4. 在需要异步执行的方法上添加@Async注解:
      @Async
      public void doSomething() {
      // ...
      }
    5. 调用异步方法,Spring会使用AsyncConfigurer中指定的Executor(线程池)来执行:
      public void call() {
      doSomething();
      }

      以上步骤实现自定义Executor(线程池)用于@Async异步方法调用。@Async会更优雅和方便地实现异步功能,避免手动创建和管理线程池。

Java高并发编程实战-通过AQS源码分析lock()锁机制

AQS(AbstractQueuedSynchronizer)是Java并发包下实现锁和同步器的基础框架,许多并发类都是基于AQS扩展实现的,如ReentrantLock、Semaphore、CountDownLatch等。
AQS使用一个volatile修饰的int类型的state来维护同步状态,通过CAS操作修改state来获取锁。当state的获取失败时,当前线程会被封装成一个Node加入到队列中,直到获取锁成功。
AQS的lock()方法实现了获取锁的主要逻辑:

public final void lock() {
    if (tryAcquire(1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return;
    }
    // 获取锁失败,加入队列
    addWaiter(Node.EXCLUSIVE); 
    for (;;) {
        Node node = getNextNode();
        node.predecessor.next = node;  // 设置node的前驱节点
        // 再次尝试获取锁
        if (tryAcquire(1)) {  
            setExclusiveOwnerThread(Thread.currentThread());
            node.predecessor = null; // 前驱节点设置为null
            return;
        }
        if (shouldParkAfterFailedAcquire(node) && context.park()) { 
            // 阻塞当前线程
        }
    } 
}

主要步骤:

  1. 尝试直接获取锁,通过tryAcquire()方法,成功则设置独占锁持有线程并返回。

  2. 若失败,则构造一个Node节点加入AQS的队列。

  3. 在循环中不断尝试获取锁,成功则设置持有线程,返回。

  4. 若失败,判断是否需要park(阻塞),是则park,等待unpark操作。

  5. 被unpark后继续循环,重新尝试获取锁。
    这就是AQS框架实现lock()获取锁的主要流程,理解AQS的原理对学习Java高并发至关重要。

    Java高并发编程实战-ConcurrentHashMap详解

    ConcurrentHashMap是Java并发包下的哈希表,适用于高并发的场景。它通过分段锁和CAS算法来实现线程安全,比HashTable的效率高很多。
    主要特征:

  6. 基于哈希表实现,支持快速查找和修改。

  7. 使用分段锁和CAS来保证线程安全,比HashTable高效。

  8. 允许key和value都可以为null。

  9. 支持计算容量和扩容,扩容时重新哈希和重组元素。
    主要方法:

    • put(K key, V value):添加键值对,如果键存在则替换值。
    • get(Object key):根据键获取值。
    • size():获取键值对数量。
    • isEmpty():判断是否为空。
    • containsKey(Object key):判断是否包含键。
    • remove(Object key):根据键删除键值对。
      迭代器:
    • 快速失败(fail-fast)的迭代器,遍历过程中发生修改,会快速失败,抛出ConcurrentModificationException。
    • KeySet、Values、EntrySet等视图的迭代器,用来遍历键、值、键值对。
      ConcurrentHashMap在JDK1.8之前使用分段锁实现,锁的粒度较大,并发度不高;JDK1.8使用CAS和Synchronized来实现,大大提高了并发度。
      示例代码:

      ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
      map.put("key1", 1);
      map.put("key2", 2);
      Integer value = map.get("key1");
      Set<String> keys = map.keySet();
      for (String key : keys) {
      // ...
      }

      ConcurrentHashMap是Java高并发编程中的重要数据结构,掌握其原理和用法是必须的。

      总结

  10. 进程与线程:进程是资源分配的最小单位,线程是CPU调度的最小单位。Java程序运行在JVM进程中,可以启动多个线程。

  11. 线程安全:多个线程访问同一资源时,需要采取措施保证正确性,这就是线程安全。主要手段有同步机制、原子操作类、并发容器等。

  12. 同步容器与并发容器:同步容器使用同步锁保证线程安全,效率低;并发容器使用高级锁和算法保证线程安全,效率高。

  13. lock锁:主要有互斥锁、读写锁、定时锁、条件锁等,用于控制线程对共享资源的访问。

  14. volatile、CAS、atomic:保证变量的可见性、有序性和原子性。

  15. 并发工具类:Semaphore信号量、CountDownLatch计数器、CyclicBarrier栅栏、BlockingQueue阻塞队列等。

  16. Java内存模型:定义了主内存和工作内存(本地内存)之间的抽象关系,以及线程对内存的访问规则。

  17. Java对象结构:包含对象头、实例数据和数组长度三部分,部分用于存储同步信息。

  18. AQS:AbstractQueuedSynchronizer,Java并发包底层同步器,许多并发类基于此扩展实现。用state状态和CLH队列维护同步状态。

  19. ConcurrentHashMap:高效的线程安全HashMap,使用分段锁和CAS来保证线程安全。

  20. @Async:基于线程池实现异步方法调用,避免手动创建和管理线程。可以自定义线程池的使用。

(Java高并发)Java学习路线(漫漫人生路,步步架构梦)Java Learning Path (Long Life Path, Step by step Structure Dream)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

滚动到顶部