JUC 总结:从 Java 内存模型到线程池的并发核心梳理

JUC

一、基础知识

1.进程是资源分配的最小单位:包括:内存空间、cpu时间片分配

线程是调度的最小单位,能够使cpu资源充分利用。更轻量,上下文切换成本低。

进程: 用来加载指令、管理内存、管理IO。

一个线程就是一个指令流,以一定顺序交给CPU执行。

2.线程创建方式:

  1. extends Thread(),然后重写run方法

  2. implements Runnable, 重写run

  3. implements Callable 重写call 然后call的返回类型是T,能上throws

  4. 线程池创建线程(常用):

    ExcutorService pool = Executors.newFixedThreadPool(3);

    pool.submit(new MyRunnable/ MyCallable并用Future<>接收)

3.runnable, callable区别:

  1. runnable没返回值,不能抛异常

  2. Callable 本身不能直接被线程 / 线程池执行,也没法自己把结果传出来,必须靠 Future/FutureTask 做「中间容器」:执行异步任务、存任务返回值、主线程主动取结果

  3. callable有返回值,可抛异常。

    为了让 Thread 能够运行 Callable,Java 设计者引入了 FutureTask

    FutureTask 的“双重身份”:

    FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 又继承了 RunnableFuture

    • 它是 Runnable:因为它实现了 Runnable 接口,所以它能被放进 new Thread() 的参数里。
    • 它是 Future:它包含了你想要的结果(Callable 的返回值)。
    MyCallable mc = new Mycallable();
    //FutureTask是Future接口的实现类(装Callable任务+返回值)
    FutureTask<String> ft = new FutrueTask<String>(mc);
    
    Thread t = new Thread(ft);  //适配器模式,只认Runnable的run()方法
    t.start();   //开启子线程,异步执行任务
    String result = ft.get();   //主线程通过Future接口拿结果
    
    Thead:
    	MyThread t = new MyThead();
    	t.start();		//调用start方法启动线程
    Runnable:
    	MyRunnable mr = new MyRunnable();
    	//创建Thread对象
    	Thread t = new Thread(mr);
    	t.start();
    

4.start()和run()方法区别:

  1. start()方法会通知jvm向操作系统创建一个子线程,调用run()方法并执行函数逻辑
  2. run()在当前线程同步执行,失去了并发性。

5.线程状态:

​ new、runnable、Blocked、waiting、timeWating、terminated

  1. new Thread()线程:新建态
  2. .start() 转换为 可运行态。
  3. 可运行态 synchronzied, 转换为 阻塞态
  4. 可运行态 在 synchronized调用lock.wait(),转换为Wating
  5. 可运行态 调用Thread.sleep(30)Thread 静态方法,转换为timeWating
  6. 线程执行结束, 变为终止态。

6.新建多个线程怎么保证按顺序执行?

• 如果已经新建了 3 个线程,想保证它们按顺序执行,常见做法有几种。

a.最简单:用 join()。

Thread t1 = new Thread(() -> {
System.out.println(“t1”);
});

等价:

// 传统匿名内部类写法

Thread t = new Thread(new Runnable() {

@Override public void run() { } });

Thread t2 = new Thread(() -> {
System.out.println(“t2”);
});

Thread t3 = new Thread(() -> {
System.out.println(“t3”);
});

t1.start();
t1.join();

t2.start();
t2.join();

t3.start();
t3.join();

执行顺序一定是:

t1 -> t2 -> t3

join() 也是一种阻塞等待。
  t.join();

  当前线程会停在这里,等待 t 线程执行结束。

  例如:

  Thread t = new Thread(() -> {
      try {
          Thread.sleep(3000);
          System.out.println("子线程结束");
      } catch (InterruptedException e) {
          System.out.println("子线程被打断");
      }
  });

  t.start();

  System.out.println("主线程等待子线程");
  t.join();
  System.out.println("主线程继续执行");

  执行顺序是:

  主线程等待子线程
  3 秒后
  子线程结束
  主线程继续执行

  这里阻塞的是:

  调用 join() 的主线程

  不是 t 自己。

b. 如果要求 3 个线程都先启动,但执行内容按顺序,可以用 CountDownLatch:java.util.concurrent(JUC)并发包,JDK1.5 开始提供。

  CountDownLatch latch12 = new CountDownLatch(1);	// 一次性计数器=1
  CountDownLatch latch23 = new CountDownLatch(1);

  Thread t1 = new Thread(() -> {
      System.out.println("t1");
      latch12.countDown();  //计数器-1
  });

  Thread t2 = new Thread(() -> {
      try {
          latch12.await();   // 阻塞!等 latch12 计数器变为0
          System.out.println("t2");
          latch23.countDown();
      } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
      }
  });

  Thread t3 = new Thread(() -> {
      try {
          latch23.await();
          System.out.println("t3");   // 阻塞!等 latch23 计数器变为0
      } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
      }
  });

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

这里 3 个线程可以同时启动,但实际打印顺序一定是:

t1 -> t2 -> t3

**c.也可以用 ExecutorService 的单线程线程池:**newSingleThreadExecutor();

  ExecutorService executor = Executors.newSingleThreadExecutor();

  executor.submit(() -> System.out.println("task1"));
  executor.submit(() -> System.out.println("task2"));
  executor.submit(() -> System.out.println("task3"));

  executor.shutdown();

7.notify 和 notifyAll区别

notify: 随机唤醒一个wait线程

notifyAll()唤醒所有wait线程

8.wait 和sleep区别

  1. 共同点:wait(), wait(time), sleep(time)都能让线程放弃cpu使用权,进入阻塞态
    1. wait是Object成员方法,每个对象都有
    2. sleep是Tread的静态方法,Thread.sleep(30)
  2. 苏醒时机不同: wait(time), sleep(time)会在设置时间到期后醒来; wait(time)/ wait()可以被notify唤醒; wait()不唤醒就一直等待; 他们都能被打断唤醒t1.interrupt();。
  3. 锁特性不同: wait调用必须要获取wait对象的锁, sleep无限制。 wait方法执行会释放对象锁,允许其他线程获取对象锁。 sleep在synchronized执行,不会释放对象锁。(我放弃cpu, 其他还是用不了)

注意:被唤醒不代表马上执行。线程被唤醒后,还要重新竞争这把锁,拿到锁后才能继续从 wait() 后面执行。

Object lock = new Object();

  Thread t1 = new Thread(() -> {
      synchronized (lock) {
          try {
              System.out.println("t1 wait 前");
              lock.wait();  //释放锁,同时等待被唤醒
              System.out.println("t1 wait 后");
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  });

  Thread t2 = new Thread(() -> {
      synchronized (lock) {
          System.out.println("t2 notify 前");
          lock.notify();  //唤醒 t1
          System.out.println("t2 notify 后");

          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }

          System.out.println("t2 synchronized 结束");
      }
  });

  执行过程:

  1. t1 进入 synchronized(lock),拿到 lock 锁
  2. t1 执行 lock.wait()
  3. t1 释放 lock 锁,进入等待状态
  4. t2 进入 synchronized(lock),拿到 lock 锁
  5. t2 执行 lock.notify(),唤醒 t1
  6. 但是 t2 还没退出 synchronized,所以 lock 锁还在 t2 手里
  7. t1 虽然醒了,但拿不到 lock 锁,所以不能继续执行
  8. t2 sleep 3 秒,仍然没有释放 lock,因为还在 synchronized 里面
  9. t2 退出 synchronized,释放 lock
  10. t1 重新抢到 lock
  11. t1 才能从 wait() 后面继续执行

9. 如何停止运行的线程?

不要强杀,用“协作式停止”:设置停止标记或调用 interrupt(),让线程自己退出
  1. 最常见是用 interrupt()。

      Thread t = new Thread(() -> {
          while (!Thread.currentThread().isInterrupted()) {
              System.out.println("working...");
    
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  // sleep 被打断后,中断标记会被清除,所以这里重新设置
                  Thread.currentThread().interrupt();
              }
          }
    
          System.out.println("线程退出");
      });
    
      t.start();
    
      Thread.sleep(3000);
    
      // 请求线程停止
      t.interrupt();
    
      关键点:
    
      t.interrupt();
    
      不是直接杀死线程,而是告诉线程:
    
      你该停了
    
      线程内部要配合检查:
    
      Thread.currentThread().isInterrupted()
    
      如果线程在 sleep()wait()join() 中,interrupt() 会让它抛出 InterruptedException,从阻塞中醒来。
    
  2. 也可以用 volatile 标记:

class Worker implements Runnable {
      private volatile boolean running = true;  //标记

      public void stop() {
          running = false;
      }

      @Override
      public void run() {
          while (running) {
              System.out.println("working...");
          }
          System.out.println("线程退出");
      }
  }

  使用:

  Worker worker = new Worker();
  Thread t = new Thread(worker);

  t.start();

  Thread.sleep(3000);
  worker.stop();              //停止线程

  为什么要用 volatile?

               因为一个线程改了 running = false,另一个线程要能马上看见。

  不推荐使用:

  thread.stop();
  thread.suspend();
  thread.resume();

  这些方法已经废弃,因为它们可能导致锁没有正确释放、对象状态被破坏。

  总结:

  // 线程正在运行:用 volatile 标记或 interrupt 标记,让它自己退出
  // 线程正在 sleep/wait/join:用 interrupt 让它醒来并退出
  不要用 Thread.stop() 强杀

  推荐写法通常是:

  while (!Thread.currentThread().isInterrupted()) {
      try {
          // do work
      } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          break;
      }
  }
关于 join 和 interrupt,要分清楚两个线程:
  main 线程调用 t.join()

  此时:

  main 被阻塞
  t 正在运行

  // 如果你想让 main 不再等了,要打断的是 main:

  mainThread.interrupt();

  不是 t.interrupt()。

  例子:

  Thread mainThread = Thread.currentThread();

  Thread t = new Thread(() -> {
      try {
          Thread.sleep(5000);
          System.out.println("子线程结束");
      } catch (InterruptedException e) {
          System.out.println("子线程被打断");
      }
  });

  Thread interrupter = new Thread(() -> {
      try {
          Thread.sleep(1000);
          mainThread.interrupt();   // 让 main 不再等了
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  });

  t.start();
  interrupter.start();

  try {
      t.join(); // main 在这里阻塞
      System.out.println("main 等到了 t 结束");  // 主线程调用 t.join()被打断,不能执行到这
  } catch (InterruptedException e) {
      System.out.println("main 的 join 被打断");
  }

  输出可能是:

  main 的 join 被打断
  子线程结束

二、线程中并发安全

1.sychronized的底层原理

在这里插入图片描述

sychronized底层是: Monitor

在这里插入图片描述

当一个对象进入synchronized,会将其于Monitor关联

Monitor结构:

在这里插入图片描述

  • 对象锁采用互斥的方法让同一时刻只有一个线程持有synchronized锁
  • 底层由monitor实现,monitor是jvm级别的对象(c++实现),线程获取锁需要:让synchronized锁关联monitor。
  • monitor内部有三个属性:owner、entrylist、waitset
  • owner是关联锁获得的线程,只能关联一个; entrylist关联被阻塞的线程; waitset关联处于waiting状态的线程
锁升级
a.重量级锁

monitor实现的锁 属于重量级锁,你了解锁升级吗?

  • monitor实现的锁属于重量级锁,涉及了用户态和内核态的切换进程上下文切换,成本高,性能较低。
  • JDK 1.6引入: 偏向锁轻量级锁, 以解决没有多线程竞争基本没有竞争(同一线程重复获取)的场景下因使用传统锁机制带来的性能开销问题。
当线程获取锁时,怎么让lock对象锁 与 monitor关联?

在这里插入图片描述

synchronized(lock) 不是“给对象加了一个独立的锁字段”,而是 JVM 把这个对象当成锁的载体。
而对象头里的 Mark Word,就是 JVM 用来记录这把锁状态的地方。

对象头 = JVM 管锁状态的地方
monitor = JVM 真正管理阻塞/唤醒的运行时结构

关系是这样的:

对象头里的 Mark Word
|
记录锁状态
|
竞争激烈 / 调用 wait() 时
|
膨胀成 monitor(ObjectMonitor)

“对象内存结构”不是废话,它是在解释**: 一个对象为什么能被 synchronized 锁住**

更具体:

  • 没竞争时,JVM 可能只在对象头里记个轻量状态
  • 有竞争时,锁会“膨胀”为 monitor
  • wait/notify 必须依赖 monitor,所以只能在 synchronized 里调用

在这里插入图片描述

对象锁lock能被monitor关联的原因: 对象的对象头中的markWord中记录了monitor的地址

b.轻量级锁

在很多的情况下,在 Java 程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此 JVM 引入了轻量级锁的概念。

static final Object obj = new Object();
public static void method1() {
    synchronized( obj ) {
        // 同步块A
        method2();
    }
}
public static void method2() {
    synchronized( obj ) {   //锁重入,同一线程持有的锁,不存在锁竞争
        // 同步块B
    }
}

在这里插入图片描述

加锁流程

  1. 在线程对应栈帧中创建一个 Lock Record(锁记录结构),将其 obj 字段指向锁对象Object。
  2. 通过 CAS 指令将 Lock Record 的地址存储在对象头的 mark word 中,如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。
  3. 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置 Lock Record 第一部分为 null,起到了一个重入计数器的作用。
  4. 如果 CAS 修改失败,说明发生了竞争,需要膨胀为重量级锁。

解锁过程

  1. 遍历线程栈,找到所有 obj 字段等于当前锁对象的 Lock Record。
  2. 如果 Lock Record 的 Mark Word 为 null,代表这是一次重入,将 obj 设置为 null 后 continue。
  3. 如果 Lock Record 的 Mark Word 不为 null,则利用 CAS 指令将对象头的 mark word 恢复成为无锁状态。如果失败则膨胀为重量级锁。
c.偏向锁

对CAS操作做优化, 多次重入只做一次CAS

在这里插入图片描述

在这里插入图片描述

Monitor 实现的锁属于重量级锁,你了解过锁升级吗?

Java 中的 synchronized 有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

锁类型描述
重量级锁底层使用的 Monitor 实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
轻量级锁线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是 CAS 操作,保证原子性
偏向锁一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个 CAS 操作,之后该线程再获取锁,只需要判断 mark word 中是否是自己的线程 id 即可,而不是开销相对较大的 CAS 命令

2. JMM(Java内存模型)

主内存:线程之间真正共享的数据存放处

工作内存:每个线程私有的“变量副本区

共享内存:更泛的说法,在 Java 里通常可以近似理解成“主内存里那部分被多个线程共同访问的数据

a.主内存是什么

在 Java 内存模型(JMM)里,实例字段、静态字段、数组元素这类数据,按模型都存在于主内存。

可以把它理解成:

  • 所有线程都能看到的“公共区”
  • 线程要操作共享变量,最终都得和主内存打交道

例如:

static int count = 0;
static final Object obj = new Object();

这里的 count 和 obj 引用,从 JMM 角度看都属于主内存中的共享变量。

———

b.工作内存是什么

每个线程都有自己的工作内存。

线程不能每次都直接操作主内存变量,而是会先把主内存里的变量值拷贝一份到自己的工作内存,然后:

  • 读取:先从工作内存读
  • 修改:先改工作内存里的副本
  • 某个时机再刷回主内存

所以会出现一个问题:

  • 线程 A 改了 count
  • 线程 B 可能还在用自己工作内存里的旧值
  • 这就产生了可见性问题(synchronized, lock, volatile解决)
Java 线程之间通过主内存来实现共享。

标答:

  • JMM是java内存模型,定义了共享内存多线程程序 读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
  • JMM把内存分为两块:一块是私有线程的工作内存,一块是所有线程的共享区域(主内存)
  • 线程间隔离,通信需要先从主内存读取共享变量到自己的工作内存,操作完后同步回主内存。

3.CAS

“我认为这个值现在还是 A,如果真是 A,就把它改成 B;如果不是 A,说明别人动过了,我就失败或重试。”
能保证并发安全: “比较 + 修改”这个动作是CPU 级别的原子操作

在这里插入图片描述
在这里插入图片描述

  • 先读旧值 -> 计算新值 -> 提交时比较旧值有没有变 -> 没变就更新,变了就重试。

    CAS 的典型特点

    优点:

    • 不用加互斥锁
    • 性能通常比阻塞锁更高
    • 适合低冲突场景

    缺点:

    • 高竞争时会一直自旋重试,浪费 CPU -> 一般会设置阈值
    • 会有 ABA 问题(线程1读到a准备改为c, 然后线程2把改为b,又改回a。线程1CAS成功,把a改为c。CAS只能确保值没变, 无法这个值在期间有没有被别人动过/节点是不是还是原来那个语义上的对象) -> 加版本号解决(A, version=1)
    • 只能保证一个共享变量的原子更新,复杂场景不够用

    ———

    和 synchronized 的区别

    • synchronized:悲观锁,先假设会冲突,先加锁再操作
    • CAS:乐观锁,先假设不会冲突,提交时再检查
底层实现

依赖于一个UnSage类直接调用OS底层的CAS指令

在这里插入图片描述

在这里插入图片描述

4.volatile

修饰共享变量**(类的成员变量、类的静态变量**)后, 能:

  • 保证线程间的可见性:

用volatile修饰共享变量,能够防止防止JIT等优化发生,让一个线程对共享变量可见。

  • 禁止指令重排

会在读、写共享变量时加入不同的屏障, 防止指令重排。

在这里插入图片描述

在这里插入图片描述

5.什么是AQS

全称是 AbstractQueuedSynchronizer,即抽象队列同步器。它是构建锁或者其他同步组件的基础框架。

AQS 与 Synchronized 的区别
synchronizedAQS
关键字,C++ 语言实现Java 语言实现
悲观锁,自动释放锁悲观锁,手动开启和关闭
锁竞争激烈时都是重量级锁,性能差锁竞争激烈的情况下,提供了多种解决方案
AQS 常见的实现类
  • ReentrantLock:阻塞式锁
  • Semaphore:信号量
  • CountDownLatch:倒计时锁

在这里插入图片描述

在这里插入图片描述

标答:
  • AQS是多线程中的队列同步器。是一种锁机制,作为一个基础框架使用,像是ReentrantLock, Semaphore,CountDownLatch倒计时锁等基于此实现。
  • AQS内部维护了FIFO的双向队列,存储排队的线程。
  • 内部还有一个属性state,相当于是个资源,默认是0无锁状态,如果队列中的线程成功修改state=1,则当前线程就获取了资源。
  • 保证多线程修改情况下的原子性:对state修改时用cas操作。

在这里插入图片描述

6.ReentrantLock

翻译过来是可重入锁,相对于synchronized锁 有以下特点:
  • 可中断
  • 可设置超时时间
  • 可设置公平锁/非公平锁
  • 支持多个条件变量
  • 与synchronized一样,都支持重入
// 创建锁对象
ReentrantLock lock = new ReentrantLock();
try{
	// 获取锁
	lock.lock();
	}finally{
	lock.unlock();
	}
}
实现原理

主要利用CAS + AQS队列实现。支持公平锁和非公平锁。

构造方法接收一个可选公平参数,默认非公平锁。设置为true时,为公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在多线程访问情况下,非公平锁表现出较低的吞吐量。

ReentrantLock 本身更像对外接口, 真正同步控制核心在内部的 Sync(基于 AQS):

 abstract static class Sync extends AbstractQueuedSynchronizer

在这里插入图片描述

标答:
  • ReentrantLock表示可重入锁,调用lock方法获取锁之后,再次调用lock,不会再次阻塞。
  • ReentrantLock主要利用CAS + AQS队列来实现
  • 支持公平锁和非公平锁,在提供的构造器中:默认无参构造是:非公平锁,也可以传参true设置公平锁。
  // 1. 基本加锁

  ReentrantLock lock = new ReentrantLock();

  lock.lock();
  try {
      System.out.println("do something");
  } finally {
      lock.unlock();
  }

  // 2. 可重入

  // 同一个线程拿到锁后,可以再次拿同一把锁:

  class Demo {
      private final ReentrantLock lock = new ReentrantLock();

      public void a() {
          lock.lock();
          try {
              b();
          } finally {
              lock.unlock();
          }
      }

      public void b() {
          lock.lock();
          try {
              System.out.println("reentrant");
          } finally {
              lock.unlock();
          }
      }
  }

  // 这里 a() 持锁后调用 b(),不会死锁,因为它是可重入锁。

  但要注意:

  加锁几次,就要解锁几次
 // ReentrantLock 可以配合 Condition 做线程等待/唤醒,类似 wait/notify, 但更灵活。

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

  class Demo {
      private final ReentrantLock lock = new ReentrantLock();
      private final Condition condition = lock.newCondition();
      private boolean ready = false;

      public void awaitMethod() throws InterruptedException {
          // 共享数据在并发场景下不能随便看、随便改,所以要先加锁
          lock.lock();
          try {
              while (!ready) {
                  condition.await(); // 当前线程先放下锁,然后进入等待,直到别人唤醒它
              }
              System.out.println("继续执行");
          } finally {
              lock.unlock();
          }
      }
      public void signalMethod() {
          lock.lock();
          try {
              ready = true;
              condition.signal(); // 唤醒一个线程
          } finally {
              lock.unlock();
          }
      }
  }
和 synchronized 的区别

ReentrantLock:

  • 需要手动加锁解锁
  • 支持 tryLock()
  • 支持可中断锁 lockInterruptibly()
  • 支持 Condition做线程等待/唤醒
  • 功能更灵活

synchronized:

  • 写法简单
  • JVM 自动释放锁
  • 适合大多数简单同步场景

7.synchronized 和 Lock有什么区别 *

Lock 是接口: public interface Lock

常见实现类有:

  • ReentrantLock
  • ReentrantReadWriteLock.WriteLock
  • ReentrantReadWriteLock.ReadLock

所以平时写的是:

Lock lock = new ReentrantLock();

在这里插入图片描述

8.死锁产生及排查

产生条件

一个线程需要同时获取多把锁,这时就容易发生死锁。

排查

出现死锁现象,可以用jdk自带工具:jps和jstack

  • jps:输出JVM中运行的线程状态信息
  • jstack: 查看Java进程内线程的堆栈信息
  1. 先用 jps 找 Java 进程 ID:

    jps可能输出:

  12345 DeadLockDemo
  67890 Jps

​ 2.然后用 jstack 查看线程栈:

  jstack 12345

如果真的有死锁,jstack 通常会在末尾直接提示:

Found one Java-level deadlock:

  • Jconsole: jdk/bin启动

在这里插入图片描述

  • VisualVM: jdk/bin/jvisualvm.exe启动

9.ConcurrentHashMap

一种线程安全的高效Map集合

底层数据结构:

  • JDK1.7底层采用分段的数组+链表实现
  • JDK1.8采用的数据结构和HashMap1.8一样:数组+链表+红黑树

在这里插入图片描述

  1. 底层数据结构:
    • JDK1.7底层采用分段的数组+链表实现
    • JDK1.8采用的数据结构更HashMap1.8结构一样,数组、链表、红黑树
  2. 加锁方式:
    • JDK1.7采用Segment分段锁,底层使用的是ReentrantLock
    • JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好。

10.导致并发程序出现问题的根本原因:

Java程序中怎么保证多线程的执行安全?

Java并发编程三大特性:

  • 原子性:一个线程在cpu中操作不可暂停,也不可中断,要不执行完成,要不不执行

    synchronized: 同步加锁; JUC中的lock: 加锁

  • 内存可见性: 让一个线程对共享变量的修改对另一个线程可见

    volatile, synchronized, lock

  • 有序性: 处理器为提交效率,对输入代码优化不保证执行顺序

    volatile

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值