线程安全 如何实现线程安全

什么是线程安全

多线程执行某段代码,不对这段代码进行同步处理、线程间的协调,程序运行的结果仍与预期一致,这就是线程安全。

多线程编程的三个核心概念

  • 原子性: 同数据库事务的原子性,一些操作要么全部成功,要么全部失败,经典的例子就是银行转账。
  • 可见性:多线程并发访问共享变量时,某个线程对共享变量的更新,其他线程能立即看到这个更新。

现代的计算机都有几层缓存,一个变量在多个线程*享,每个线程都会对这个变量进行缓存,某个线程内部更新该变量会立即更新缓存,但不会立即更新主内存,这使得其他线程无法立即观察到这个更新变化。

  • 顺序性: 指程序顺序执行代码中的操作。

编译器、处理器会对代码的执行顺序进行优化,以提高处理速度。因此程序实际的执行顺序可能与代码中的不一样,但编译器、处理器保证结果是一样的。

如何保证原子性

使用synchronized、ReentrantLock作用于一段代码,同一时刻只能有一个线程能进入这段代码,保证了原子性、可见性、顺序性。使用synchronized、ReentrantLock的性能较差,因为一个线程占有锁,其他线程会阻塞,线程的挂起和唤醒会降低处理速度。

使用AtmoicInteger、AtmoicLong、AtmoicReference,底层使用CAS(compare and swap)。

如何保证可见性

使用synchronized、ReentrantLock作用于一段代码,同一时刻只能有一个线程能进入这段代码,保证了原子性、可见性、顺序性。使用synchronized、ReentrantLock的性能较差,因为一个线程占有锁,其他线程会阻塞,线程的挂起和唤醒会降低处理速度。

java使用volatile关键字来保证可见性。被volatile修饰的变量,当被某个线程更新时,会立即更新主内存,并将该变量的所有缓存设置为无效。其他线程想要获取该变量必须访问主内存。

如何保证顺序性

编译器、处理器会对代码的执行顺序进行优化,以提高处理速度。因此程序实际的执行顺序可能与代码中的不一样,但编译器、处理器保证结果是一样的。在单线程程序下结果是正确的,但在多线程下有可能不正确。

使用volatile可以一定程度上保证顺序性。
使用synchronized、ReentrantLock作用于一段代码,同一时刻只能有一个线程能进入这段代码,保证了原子性、可见性、顺序性。使用synchronized、ReentrantLock的性能较差,因为一个线程占有锁,其他线程会阻塞,线程的挂起和唤醒会降低处理速度。

如何实现线程安全

  • 互斥同步: 使用synchronized、ReentrantLock作用于一段代码,同一时刻只能有一个线程能进入这段代码,保证了原子性、可见性、顺序性。
  • 非阻塞同步: CAS(compare and swap)是非阻塞同步的计算机指令, 它有三个操作数,内存位置、旧的预期值、新值,当内存位置的值与旧的预期值相等时才将新值存入内存位置。对于ABA问题,可使用AtomicStampedReference,通过引入版本号解决。
  • 无同步: 使用ThreadLocal将共享变量的可见性限制在线程内部,每个线程维护一个共享变量的副本,线程间相互隔离,不再争用数据。
上一篇:多线程001--synchronized和lock有哪些区别


下一篇:StJavaDay12