| 当多个线程访问某个类,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类为线程安全的 |
| public class UnSafeThread { |
| |
| private static int num = 0; |
| |
| private static CountDownLatch countDownLatch = new CountDownLatch(10); |
| |
| |
| |
| |
| public static void inCreate() { |
| num++; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(()->{ |
| for (int j = 0; j < 100; j++) { |
| inCreate(); |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| countDownLatch.countDown(); |
| }).start(); |
| } |
| |
| while (true) { |
| if (countDownLatch.getCount() == 0) { |
| System.out.println(num); |
| break; |
| } |
| } |
| |
| } |
| } |
| |
| C:\work\git\spring_learn_repo\concurrent\src\main\java\com\xdclass\synopsis>javac -encoding UTF-8 UnsafeThread.java |
| |
| C:\work\git\spring_learn_repo\concurrent\src\main\java\com\xdclass\synopsis>javap -c UnsafeThread.class |
| Compiled from "UnsafeThread.java" |
| public class com.xdclass.synopsis.UnSafeThread { |
| public com.xdclass.synopsis.UnSafeThread(); |
| Code: |
| 0: aload_0 |
| 1: invokespecial |
| 4: return |
| |
| public static void inCreate(); |
| Code: |
| 0: getstatic |
| 3: iconst_1 |
| 4: iadd |
| 5: putstatic |
| 8: return |
| |
| public static void main(java.lang.String[]); |
| Code: |
| 0: iconst_0 |
| 1: istore_1 |
| 2: iload_1 |
| 3: bipush 10 |
| 5: if_icmpge 29 |
| 8: new |
| 11: dup |
| 12: invokedynamic |
| 17: invokespecial |
| 20: invokevirtual |
| 23: iinc 1, 1 |
| 26: goto 2 |
| 29: getstatic |
| 32: invokevirtual |
| 35: lconst_0 |
| 36: lcmp |
| 37: ifne 29 |
| 40: getstatic |
| 43: getstatic |
| 46: invokevirtual |
| 49: goto 52 |
| 52: return |
| |
| static {}; |
| Code: |
| 0: iconst_0 |
| 1: putstatic |
| 4: new |
| 7: dup |
| 8: bipush 10 |
| 10: invokespecial |
| 13: putstatic |
| 16: return |
| } |
| 0: getstatic |
| 3: iconst_1 将int型1押入栈顶 |
| 4: iadd 将栈顶两个int型 |
| 相加,将结果押入栈顶 |
| 5: putstatic |
| 8: return |
| 1、获取静态域num = 0 |
| 2、第1个线程对num加1 |
| 3、若这时创建第2个线程,对num加1 |
| 4、最后的预期结果应该为2,实际结果为1 |
| |
| # 产生线程不安全问题的原因:num++ 不是原子性操作,被拆分成好几个步骤,在多线程并发执行的情况下,因为cpu调度,多线程快递切换,有可能两个同一时刻都读取了同一个num值,之后对它进行+1操作,导致线程安全性 |

| 一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行 |
| 使用synchronize关机字,使得操作具有原子性 |
| |
| volatile关键字仅仅保证可见性,并不保证原子性 |
| public class UnSafeThread { |
| |
| private static int num = 0; |
| |
| private static CountDownLatch countDownLatch = new CountDownLatch(10); |
| |
| |
| |
| |
| public static synchronized void inCreate() { |
| num++; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(()->{ |
| for (int j = 0; j < 100; j++) { |
| inCreate(); |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| countDownLatch.countDown(); |
| }).start(); |
| } |
| |
| while (true) { |
| if (countDownLatch.getCount() == 0) { |
| System.out.println(num); |
| break; |
| } |
| } |
| |
| } |
| } |
| 1、内置锁: 每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法 |
| 2、互斥锁: 内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去 |
| 3、修饰普通方法:锁住对象的实例 |
| 4、修饰静态方法:锁住整个类 |
| 5、修饰代码块:锁住一个对象 synchronized (lock) 即synchronized后面括号里的内容 |
| public class SynDemo { |
| |
| public synchronized void out() throws InterruptedException { |
| System.out.println(Thread.currentThread().getName()); |
| Thread.sleep(5000L); |
| } |
| |
| |
| public static void main(String[] args) { |
| SynDemo synDemo = new SynDemo(); |
| SynDemo synDemo2 = new SynDemo(); |
| |
| new Thread(() -> { |
| try { |
| synDemo.out(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| }).start(); |
| |
| new Thread(() -> { |
| try { |
| synDemo2.out(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| }).start(); |
| } |
| |
| } |
| |
| # 控制台结果:2个线程同时打印出结果,说明2个线程互不干扰 |
| Thread-0 |
| Thread-1 |
| public class SynDemo { |
| |
| public static synchronized void staticOut() throws InterruptedException { |
| System.out.println(Thread.currentThread().getName()); |
| Thread.sleep(5000L); |
| } |
| |
| public static void main(String[] args) { |
| SynDemo synDemo = new SynDemo(); |
| SynDemo synDemo2 = new SynDemo(); |
| |
| new Thread(() -> { |
| try { |
| synDemo.staticOut(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| }).start(); |
| |
| new Thread(() -> { |
| try { |
| synDemo2.staticOut(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| }).start(); |
| } |
| |
| } |
| |
| # 控制台结果:先打印线程1个线程,过了1段时间后,再打印第2个线程;说明锁住了整个类,有1个线程在执行时,其他线程是不能操作的 |
| Thread-0 |
| Thread-1 |
| public class SynDemo { |
| |
| private Object lock = new Object(); |
| |
| |
| public void myOut() { |
| synchronized (lock) { |
| System.out.println(Thread.currentThread().getName()); |
| try { |
| Thread.sleep(5000L); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| |
| public static void main(String[] args) { |
| SynDemo synDemo = new SynDemo(); |
| |
| new Thread(() -> { |
| synDemo.myOut(); |
| }).start(); |
| |
| new Thread(() -> { |
| synDemo.myOut(); |
| }).start(); |
| } |
| |
| } |
| |
| # 控制台结果:先打印线程1个线程,过了1段时间后,再打印第2个线程;说明锁住了lock对象,其中1个线程在操作时,其他线程不能操作 |
| Thread-0 |
| Thread-1 |
| 1、能且仅能修饰变量 |
| 2、保证该变量的可见性,volatile关键字仅仅保证可见性,并不保证原子性 |
| 3、禁止指令重排序 |
| 4、A,B两个线程同时读取volatile关键字修饰的对象,A读取之后,修改了变量的值,修改后的值,对B线程来说,是可见 |
| 5、使用场景 1:作为线程开关 2:单例,修饰对象实例,禁止指令重排序 |
| public class VolatileDemo implements Runnable { |
| |
| private static volatile boolean flag = true; |
| |
| @Override |
| public void run() { |
| while (flag) { |
| System.out.println(Thread.currentThread().getName()); |
| } |
| } |
| |
| public static void main(String[] args) { |
| |
| } |
| |
| } |
| 1、饿汉式:本身线程安全 |
| 在类加载的时候,就已经进行实例化,无论之后用不用到。如果该类比较占内存,之后又没用到,就白白浪费了资源 |
| 2、懒汉式:最简单的写法是非线程安全的 |
| 在需要的时候再实例化 |
| |
| public class HungerSingleton { |
| |
| private static HungerSingleton ourInstance = new HungerSingleton(); |
| |
| public static HungerSingleton getInstance() { |
| return ourInstance; |
| } |
| |
| |
| private HungerSingleton() { |
| } |
| |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(()->{ |
| System.out.println(HungerSingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:10个线程构建的对象是一样的,说明是线程安全的 |
| public class LazySingleton { |
| |
| private static LazySingleton lazySingleton = null; |
| |
| private LazySingleton() { |
| |
| } |
| |
| public static LazySingleton getInstance() { |
| |
| if (null == lazySingleton) { |
| |
| try { |
| Thread.sleep(1000L); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| |
| lazySingleton = new LazySingleton(); |
| |
| return lazySingleton; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(() -> { |
| System.out.println(LazySingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:创建了多个不同的对象,说明线程不安全 |
| com.xdclass.safe.LazySingleton@55b1e661 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@1b5b6dd2 |
| com.xdclass.safe.LazySingleton@15e99a61 |
| com.xdclass.safe.LazySingleton@30fefd9 |
| com.xdclass.safe.LazySingleton@5238f79d |
| com.xdclass.safe.LazySingleton@2d92d0a6 |
| com.xdclass.safe.LazySingleton@1b2721ec |
| com.xdclass.safe.LazySingleton@525feac5 |
| com.xdclass.safe.LazySingleton@5b5aee8d |
| public class LazySingleton { |
| |
| private static LazySingleton lazySingleton = null; |
| |
| private LazySingleton() { |
| |
| } |
| |
| |
| public static synchronized LazySingleton getInstance() { |
| |
| if (null == lazySingleton) { |
| |
| try { |
| Thread.sleep(1000L); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| |
| lazySingleton = new LazySingleton(); |
| } |
| |
| return lazySingleton; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(() -> { |
| System.out.println(LazySingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:加了锁之后,是可以保证线程安全的;但是该方式会造成线程阻塞 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@96ce312 |
| public class LazySingleton { |
| |
| private static LazySingleton lazySingleton = null; |
| |
| private LazySingleton() { |
| |
| } |
| |
| public static LazySingleton getInstance() { |
| |
| if (null == lazySingleton) { |
| |
| synchronized (LazySingleton.class) { |
| lazySingleton = new LazySingleton(); |
| } |
| } |
| |
| return lazySingleton; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(() -> { |
| System.out.println(LazySingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:线程不安全 |
| com.xdclass.safe.LazySingleton@79e8e17a |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@411dfe5 |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@6687d524 |
| public class LazySingleton { |
| |
| private static LazySingleton lazySingleton = null; |
| |
| private LazySingleton() { |
| |
| } |
| |
| public static LazySingleton getInstance() { |
| |
| if (null == lazySingleton) { |
| |
| try { |
| Thread.sleep(1000L); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| |
| synchronized (LazySingleton.class) { |
| lazySingleton = new LazySingleton(); |
| } |
| } |
| |
| return lazySingleton; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(() -> { |
| System.out.println(LazySingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:10个线程创建了10个对象,说线程不安全 |
| com.xdclass.safe.LazySingleton@15e99a61 |
| com.xdclass.safe.LazySingleton@557595cb |
| com.xdclass.safe.LazySingleton@5ba99877 |
| com.xdclass.safe.LazySingleton@66a71f1b |
| com.xdclass.safe.LazySingleton@3c01442a |
| com.xdclass.safe.LazySingleton@6687d524 |
| com.xdclass.safe.LazySingleton@96ce312 |
| com.xdclass.safe.LazySingleton@525feac5 |
| com.xdclass.safe.LazySingleton@2d92d0a6 |
| com.xdclass.safe.LazySingleton@29b41d6e |
| public class LazySingleton { |
| |
| private static LazySingleton lazySingleton = null; |
| |
| private LazySingleton() { |
| |
| } |
| |
| public static LazySingleton getInstance() { |
| |
| if (null == lazySingleton) { |
| |
| try { |
| Thread.sleep(1000L); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| |
| synchronized (LazySingleton.class) { |
| |
| if (null == lazySingleton) { |
| lazySingleton = new LazySingleton(); |
| } |
| } |
| } |
| |
| return lazySingleton; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(() -> { |
| System.out.println(LazySingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:10个线程创建的是相同的对象,但该方式也不能保证线程安全,可能会导致指令重排 |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| com.xdclass.safe.LazySingleton@1c8a8cea |
| public class LazySingleton { |
| |
| |
| private static volatile LazySingleton lazySingleton = null; |
| |
| private LazySingleton() { |
| |
| } |
| |
| public static LazySingleton getInstance() { |
| |
| if (null == lazySingleton) { |
| |
| try { |
| Thread.sleep(1000L); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| |
| synchronized (LazySingleton.class) { |
| |
| if (null == lazySingleton) { |
| lazySingleton = new LazySingleton(); |
| } |
| } |
| } |
| |
| return lazySingleton; |
| } |
| |
| public static void main(String[] args) { |
| for (int i = 0; i < 10; i++) { |
| new Thread(() -> { |
| System.out.println(LazySingleton.getInstance()); |
| }).start(); |
| } |
| } |
| |
| } |
| |
| # 控制台结果:添加volatile关键字,防止指令重排 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| com.xdclass.safe.LazySingleton@9ea8999 |
| 1、线程安全性问题成因 |
| 多线程环境 |
| 多个线程操作同一共享资源 |
| 对该共享资源进行了非原子性操作 |
| 2、避免线程安全性问题,打破成因中三点任意一点 |
| 1:多线程环境:将多线程改单线程(必要的代码,加锁访问) |
| 2:多个线程操作同一共享资源:不共享资源(ThreadLocal、不共享、操作无状态化、不可变) |
| 3:对该共享资源进行了非原子性操作:将非原子性操作改成原子性操作(加锁、使用JDK自带的原子性操作的类、JUC提供的相应的并发工具类) |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话