ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java线程同步的四种方式详解(建议收藏) [打印本页]

作者: 前进之路    时间: 2022-9-28 09:29
标题: Java线程同步的四种方式详解(建议收藏)
​ 
Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen
目录
什么是线程同步

当使用多个线程来访问同一个数据时,将会导致数据不准确,相互之间产生冲突,非常容易出现线程安全问题,如下图所示:

比如多个线程都在操作同一数据,都打算修改商品库存,这样就会导致数据不一致的问题。
线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
 
线程同步的几种方式


1、使用synchronized关键字

这种方式比较灵活,修饰一个代码块,被修饰的代码块称为同步语句块。
其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象,如下格式:
  1. synchronized(对象) {                    
  2.    //得到对象的锁,才能操作同步代码
  3.     需要被同步代码;
  4. }
复制代码
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
具体的示例如下:
  1. public class SynchronizedThread {
  2.     class Bank {
  3.         private int account = 200;
  4.         public int getAccount() {
  5.             return account;
  6.         }
  7.         /**
  8.          * 用同步方法实现
  9.          *
  10.          * @param money
  11.          */
  12.         public synchronized void save(int money) {
  13.             account += money;
  14.         }
  15.         /**
  16.          * 用同步代码块实现
  17.          *
  18.          * @param money
  19.          */
  20.         public void save1(int money) {
  21.             synchronized (this) {
  22.                 account += money;
  23.             }
  24.         }
  25.     }
  26.     class NewThread implements Runnable {
  27.         private Bank bank;
  28.         public NewThread(Bank bank) {
  29.             this.bank = bank;
  30.         }
  31.         @Override
  32.         public void run() {
  33.             for (int i = 0; i < 10; i++) {
  34.                 // bank.save1(10);
  35.                 bank.save(10);
  36.                 System.out.println(i + "账户余额为:" + bank.getAccount());
  37.             }
  38.         }
  39.     }
  40.     /**
  41.      * 建立线程,调用内部类
  42.      */
  43.     public void useThread() {
  44.         Bank bank = new Bank();
  45.         NewThread new_thread = new NewThread(bank);
  46.         System.out.println("线程1");
  47.         Thread thread1 = new Thread(new_thread);
  48.         thread1.start();
  49.         System.out.println("线程2");
  50.         Thread thread2 = new Thread(new_thread);
  51.         thread2.start();
  52.     }
  53.     public static void main(String[] args) {
  54.         SynchronizedThread st = new SynchronizedThread();
  55.         st.useThread();
  56.     }
  57. }
复制代码
 
2.使用ReentrantLock

ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法具有相同的基本行为和语义,并且扩展了其能力。
  1. private int account = 100;
  2.             //需要声明这个锁
  3.             private Lock lock = new ReentrantLock();
  4.             public int getAccount() {
  5.                 return account;
  6.             }
  7.             //这里不再需要synchronized
  8.             public void save(int money) {
  9.                 lock.lock();
  10.                 try{
  11.                     account += money;
  12.                 }finally{
  13.                     lock.unlock();
  14.                 }
  15.             }
  16.         }
复制代码
 
synchronized 与 Lock 的对比
ReentrantLock是显示锁,手动开启和关闭锁,别忘记关闭锁;
synchronized 是隐式锁,出了作用域自动释放;
ReentrantLock只有代码块锁,synchronized 有代码块锁和方法锁;
使用 ReentrantLock锁,JVM 将花费较少的时间来调度线程,线程更好,并且具有更好的扩展性(提供更多的子类);
优先使用顺序:
ReentrantLocksynchronized 同步代码块> synchronized 同步方法
 
3.使用原子变量实现线程同步

为了完成线程同步,我们将使用原子变量(Atomic***开头的)来实现。
比如典型代表:AtomicInteger类存在于java.util.concurrent.atomic中,该类表示支持原子操作的整数,采用getAndIncrement方法以原子方法将当前的值递加。
具体示例如下:
  1. private AtomicInteger account = new AtomicInteger(100);
  2.         public AtomicInteger getAccount() {
  3.             return account;
  4.         }
  5.         public void save(int money) {
  6.             account.addAndGet(money);
  7.         }
复制代码
4.ThreadLocal实现线程同步

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响,从而实现线程同步。
具体代码示例如下:
  1. //只改Bank类,其余代码与上同
  2.         public class Bank{
  3.             // 创建一个线程本地变量 ThreadLocal
  4.             private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
  5.                 @Override
  6.                 //返回当前线程的"初始值"
  7.                 protected Integer initialValue(){
  8.                     return 100;
  9.                 }
  10.             };
  11.             public void save(int money){
  12.                 //设置线程副本中的值
  13.                 account.set(account.get()+money);
  14.             }
  15.             public int getAccount(){
  16.                 //返回线程副本中的值
  17.                 return account.get();
  18.             }
  19.         }
复制代码
  1.  
复制代码
以上
作者简介

陈睿|mikechen,10年+大厂架构经验,《BAT架构技术500期》系列文章作者,专注于互联网架构技术。
阅读mikechen的互联网架构更多技术文章合集
Java并发|JVM|MySQL|Spring|Redis|分布式|高并发

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4