java的多线程高并发详解

作者:清云青云 时间:2022-06-28 10:12:06 

1.JMM数据原子操作

  • read(读取)∶从主内存读取数据

  • load(载入):将主内存读取到的数据写入工作内存

  • use(使用):从工作内存读取数据来计算

  • assign(赋值):将计算好的值重新赋值到工作内存中

  • store(存储):将工作内存数据写入主内存

  • write(写入):将store过去的变量值赋值给主内存中的变量

  • lock(锁定):将主内存变量加锁,标识为线程独占状态

  • unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量

2.来看volatile关键字

(1)启动两个线程


public class VolatileDemo {

private static boolean flag = false;
   public static void main(String[] args) throws InterruptedException {
       new Thread(() -> {
           while (!flag){
           }
           System.out.println("跳出while循环了");
       }).start();

Thread.sleep(2000);
       new Thread(() -> changeFlage()).start();
   }

private static void changeFlage() {
       System.out.println("开始改变flag值之前");
       flag = true;
       System.out.println("改变flag值之后");
   }
}

没加volatile之前,第一个线程的while判断一直满足

java的多线程高并发详解

(2)给变量flag加了volatile之后


public class VolatileDemo {

private static volatile boolean flag = false;
   public static void main(String[] args) throws InterruptedException {
       new Thread(() -> {
           while (!flag){
           }
           System.out.println("跳出while循环了");
       }).start();

Thread.sleep(2000);
       new Thread(() -> changeFlage()).start();
   }

private static void changeFlage() {
       System.out.println("开始改变flag值之前");
       flag = true;
       System.out.println("改变flag值之后");
   }
}

while语句能够满足条件

java的多线程高并发详解

(3)原理解释:

开启第一个线程时,flag变量通过read从主内存中读出数据,使用load把数据加载进线程一的工作内存,通过use把flag读取到线程中;线程二也是同样的读取操作。线程二通过assign改变了flag的值,线程二工作内存中存储的flag=true,再通过store把flag写入到总线,总线再把flag通过write写入到住内存;由于两个线程读取操作的都是各种工作内存中的值,是主内存的副本,相互不通信,所以线程一一直再循环,线程一的flag为false。

加了volatile后,添加了缓存一致性协议(MESI),CPU通过总线嗅探机制感知到数据的变化而自己缓存里的值失效,此时线程一会把工作内存中存放的flag失效,从主内存中重新读取flag的值,此时满足while条件。

volatile底层通过汇编语言的lock修饰,当变量有修改立马写回主类,避免指令重排序

java的多线程高并发详解

3.并发编程三大特性

可见性,有序性、原子性

4.双锁判断机制创建单例模式


public class DoubleCheckLockSinglenon {

private static volatile DoubleCheckLockSinglenon doubleCheckLockSingleon = null;

public DoubleCheckLockSinglenon(){}

public static DoubleCheckLockSinglenon getInstance(){
       if (null == doubleCheckLockSingleon) {
           synchronized(DoubleCheckLockSinglenon.class){
               if(null == doubleCheckLockSingleon){
                   doubleCheckLockSingleon = new DoubleCheckLockSinglenon();
               }
           }
       }
       return doubleCheckLockSingleon;
   }

public static void main(String[] args) {
       System.out.println(DoubleCheckLockSinglenon.getInstance());

}

}

当线程调用getInstance方法创建的时候,先判断是否为空,为空则把对象加上锁,否则多线程的情况会创建重复,再锁里面再次判断是否为空,当new一个对象的时候,先在内存分配空间,再执行对象的init属性赋零操作,再执行初始化赋值操作。

cpu为了优化代码执行效率,会对满足as-if-serial和happens-before原则的代码进行指令重排序,as-if-serial规定线程内的执行代码顺序不影响结果输出,则会进行指令重排;

happens-before规定一些锁的顺序,同一个对象的unlock需要出现下一个lock之前等。

所以为了防止new的时候,指令重排,先进行赋值再执行赋零操作情况,需要加上volatile修饰符,加上volatile修饰后,在new操作时会创建内存屏障,高速cpu不进行指令重排序,底层是lock关键字;内存屏障分为LoadLoad(读读)、storestore(写写)、loadstore(读写)、storeload(写读),底层是c++代码写的,c++代码再调用汇编语言

5.synchronized关键字

(1)没加synchronized之前


package com.qingyun;

/**
* Synchronized关键字
*/
public class SynchronizedDemo {

public static void main(String[] args) throws InterruptedException {

Num num = new Num();
      Thread t1 = new Thread(() -> {
           for (int i = 0;i < 100000;i++) {
               num.incrent();
           }
       });
       t1.start();

for (int i = 0;i < 100000;i++) {
           num.incrent();
       }
       t1.join();
       System.out.println(num.getNum());
   }
}

package com.qingyun;

public class Num {

public int num = 0;

public void incrent() {
       num++;
   }

public int getNum(){
       return num;
   }
}

输出结果不是我们想要的,由于线程和for循环同时去调加的方法,导致最后输出的结果不是我们想要的

java的多线程高并发详解

(2)加上synchronized之后


public synchronized void incrent() {
       num++;
   }

//或者

public  void incrent() {
       synchronized(this){
           num++;
       }
   }

java的多线程高并发详解

输出的结果是我们想要的,synchronized关键字底层使用的lock,是重量级锁,互斥锁、悲观锁,jdk1.6之前的锁,线程会放到一个队列里面等待着执行

6.AtomicIntger原子操作

(1)给原子加1的操作,可以使用AtomicInteger实现,与synchronized相比,性能大大提升


public class Num {

// public int num = 0;
   AtomicInteger atomicInteger = new AtomicInteger();

public  void incrent() {
      atomicInteger.incrementAndGet(); //原子加1
   }

public int getNum(){
       return atomicInteger.get();
   }
}

AtomicInteger源码有一个value字段,使用volatile修饰,volatile底层使用lock修饰,保证多线程并发结果的正确


private volatile int value;

(2)atomicInteger.incrementAndGet()方法做的事情:先获取到value的值,给值加1,再使用旧的值和atomicInteger进行比较,相等了把newValue设置进去,由于使用多线程可能值会不相等的情况,所以使用while进行循环比对,相等了执行完才推出


while(true) {
   int oldValue = atomicInteger.get();
   int newValue = oldValue+1;
   if(atomicInteger.compareAndSet(oldValue,newValue)){
      break;
   }
}

(3)atomicInteger.compareAndSet比对完值后才设置新值的方式即为CAS:无锁、乐观锁、轻量级锁,synchroznied存在线程阻塞、上行文切换、操作系统调度比较费时;CAS一直循环比对执行,效率要高

(4)compareAndSetInt底层使用native修饰,底层是c++代码,实现了原子性问题,在汇编语言使用代码lock cmpxchqq保证了原子性,是缓存行锁

(5)ABA问题:线程一那到一个变量,线程二执行比较快,也拿到这个变量,把变量的值进行修改,再快速修改回原来的值,这样变量的值有过一次变化,线程一再去执行compareAndSet的时候,虽然值还是之前的没变,但是已经发生过变化了,出现ABA问题

(6)解决ABA问题就是给变量加版本,每次操作变量版本加1,JDK带版本的锁有AtomicStampedReference,这样就算变量被其它线程修改过再回复原值,版本号也是不一致的。

7.锁优化

(1)重量级锁会把等待的线程放到队列中,重量级锁锁定的是monitor,存在上下问切换的资源占用;轻量级锁若是线程太多,会存在自旋,耗费cpu

(2)jdk1.6之后,锁升级为无状态-》偏向锁(锁id指定)-》轻量级锁(自旋膨胀)-》重量级锁(队列存储)

(3)创建一个对象,此时对象为无状态,当启动了一个线程时,再创建一个对象时,启用偏向锁,偏向锁执行完之后不会释放锁;当再启用一个线程时,有两个线程来挣抢对象时,立马又偏向锁升级为轻量级锁;当再创建一个线程的来挣抢对象锁时,由轻量级锁升级为重量级锁

(4)分段CAS,底层有一个base记录变量值,当有多个线程类访问此变量是,base的值会分为多个cell,组成数组,每个cell对应一到多个线程的cas处理,避免了线程的自旋空转,这样还是轻量级锁,返回数据的时候,底层调用的是所有cell数组和base的加和


public class Num {

LongAdder longAdder = new LongAdder();

public  void incrent() {

longAdder.increment();
   }

public long getNum(){
      return longAdder.longValue();
   }
}

public long longValue() {
       return sum();
   }

public long sum() {
       Cell[] as = cells; Cell a;
       long sum = base;
       if (as != null) {
           for (int i = 0; i < as.length; ++i) {
               if ((a = as[i]) != null)
                   sum += a.value;
           }
       }
       return sum;
   }

来源:https://blog.csdn.net/ZHANGLIZENG/article/details/115710833

标签:java,多线程,高并发
0
投稿

猜你喜欢

  • Android studio报: java.lang.ExceptionInInitializerError 错误

    2022-08-14 14:21:30
  • 安卓11适配攻略抢先看

    2022-05-22 22:39:05
  • SpringBoot2.0解决Long型数据转换成json格式时丢失精度问题

    2022-10-31 16:56:24
  • 详解Android通知栏沉浸式/透明化完整解决方案

    2023-09-06 03:59:11
  • Android实现购物车添加商品特效

    2021-06-25 22:59:45
  • Spring Boot与RabbitMQ结合实现延迟队列的示例

    2021-08-31 02:02:01
  • Spring Boot两种全局配置和两种注解的操作方法

    2022-06-07 01:14:42
  • SpringBoot整合Log4j2及配置步骤

    2023-10-27 10:50:16
  • Struts2中Action中是否需要实现Execute方法

    2021-10-30 06:57:23
  • spring profile 多环境配置管理详解

    2023-01-23 17:53:58
  • android主线程和子线程之间消息传递详解

    2021-11-06 05:06:23
  • Java实现文件切割拼接的实现代码

    2022-04-15 08:10:03
  • 如何使用HttpClient发送java对象到服务器

    2022-10-29 06:45:51
  • Java并发编程中的生产者与消费者模型简述

    2023-02-16 20:33:18
  • RocketMQ消息生产者是如何选择Broker示例详解

    2023-11-10 21:45:49
  • 如何实现Spring Event(异步事件)

    2023-08-23 05:06:47
  • Unity 使用TexturePacker打包图集的操作方法

    2021-08-30 18:48:17
  • ADO.NET实体数据模型详细介绍

    2023-10-16 12:15:41
  • 探讨Java中函数是值传递还是引用传递问题

    2021-07-25 19:34:44
  • 在Spring中自动装配Bean的属性

    2022-01-29 07:01:28
  • asp之家 软件编程 m.aspxhome.com