Java线程同步的四种方式详解(建议收藏)

Java线程同步的四种方式详解(建议收藏)-mikechen

Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen

什么是线程同步

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

Java线程同步的四种方式详解(建议收藏)-mikechen

比如多个线程都在操作同一数据,都打算修改商品库存,这样就会导致数据不一致的问题。

线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

 

线程同步的几种方式

Java线程同步的四种方式详解(建议收藏)-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. }

 

synchronized 与 Lock 的对比

ReentrantLock是显示锁,手动开启和关闭锁,别忘记关闭锁;

synchronized 是隐式锁,出了作用域自动释放;

ReentrantLock只有代码块锁,synchronized 有代码块锁和方法锁;

使用 ReentrantLock锁,JVM 将花费较少的时间来调度线程,线程更好,并且具有更好的扩展性(提供更多的子类);

优先使用顺序:

ReentrantLock> synchronized 同步代码块> 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. }

 

mikechen

mikechen睿哥,10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

评论交流
    说说你的看法
欢迎您,新朋友,感谢参与互动!