当前位置: 首页 > news >正文

Java多线程感悟二

写在前面

这篇是Java多线程感悟的第二篇博客,主要讲述的JAVA层面对并发的一些支持。第一篇博客地址为:http://zhangfengzhe.blog.51cto.com/8855103/1607712  下一篇博客将介绍线程池和一些同步工具类。


目录

9.  并发内存模型及并发问题概述

10. volatile和synchronized原理分析

11. ThreadLocal原理及其在Struts/Spring中的应用

12. Atomic

13. Lock


并发内存模型及并发问题概述

首先看一个图:


wKioL1TUH5Cz58H9AAC-L63M7JY507.jpg


在多核CPU的情况下,每一个CPU都有自己的缓存cache,当多个CPU对同一份内存的数据进行操作时,显然就有可能导致缓存不一致的问题。


然后,我们再来看看多线程的工作模型:


wKiom1TUISaB7uG2AAENAumeHqk464.jpg


从上面的模型图,可以得到如下结论:


第一,同一个进程内部的线程通信(数据交换)是通过内存来实现的


第二,每个线程在进行操作时,都会先从主内存COPY一份到自己的工作内存中,当完成计算后,会在某个时候将工作内存中的数据刷新到主内存中。显然如果我们不提供一种机制保证各个线程的load/save操作的次序,那么就会导致各种问题。


需要注意的是:

Java线程之间的通信由Java内存模型(JMM:Java Memory Model)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。另外,Java为了获得最优性能,在不修改程序语义和单线程执行结果的前提下,允许编译器对指令进行重排,允许CPU决定指令的执行顺序,当然如果在多线程环境下就有可能因为指令重排产生问题。


总结:

在多线程环境下,当我们考虑并发问题时,需要注意如下几点:

原子性:保证一个线程的几个操作要么一起执行成功,要么一起不被执行,不允许其他线程打断。

可见性:当一个线程对共享变量进行了操作,什么时候对其他线程可见。

排序性:确保程序的执行顺序按照代码的先后顺序。



volatile和synchronized原理分析

volatile和synchronized是JAVA在语法层面提供对并发支持的2个关键字。


synchronized锁住的是什么?


只有明白锁住了什么,才能根据业务情况去构造一个对象,锁住它,来达到同步的目的,以及去优化synchronized的锁粒度!


synchronized(obj){

...

}


需要注意的是锁住的是一个对象(一个普通对象或者一份class),并不仅仅是这一个synchronized的{}区域。也就是说synchronized(obj)和任何其他synchronized(obj)互斥。需要注意的是子类对象,父类对象,类class他们是3个不同对象,是3把不同的“锁”。


synchronized背后都干了些什么?


第一,同一时刻,只有一个线程能拿到“钥匙”进入临界区域

第二,进入临界区域时,该线程的工作缓存失效,强制从主内存中读取最新值

第三,退出临界区域时,该线程的工作缓存强制刷新到主内存

第四,当这个线程OVER,其他某个线程拿到“钥匙”后,重复上面3个步骤


实际上,通过上面的分析,synchronized保证了:

原子性:因为任意时刻只有一个线程才能执行这段代码

可见性:因为线程在进入、退出临界区域时,都会强制和主内存交互,这样当前线程可以看到上一个线程操作后的变化

有序性:由于临界区域其实是一个单线程的执行环境,自然就不存在这个问题



volatile

对于普通共享变量,工作内存和主内存之间什么时候交互由于是不确定,因此会导致可见性问题。volatile这个关键字是专门用来保证Java线程中的可见性的。实际上,我们可以这样认为多个线程之间对volatile的变量的读写操作,是直接在主内存中进行的,工作缓存中的是失效的。同时,JMM还保证了volatile变量前后操作的一定的“有序性”,但是不能保证原子性。因此volatile提供了synchronized的一部分功能,带来的开销小于一段代码的同步锁机制,但是在业务场景下,往往需要操作的原子性,所以volatile的应用场景有限。比如一个典型的volatile应用场景如下:


wKioL1TUN_rwz1S2AADyZEVYONY383.jpg


由于读线程不需要加锁可以并发执行,这样通过volatile减少synchronized的代码区域开销。



ThreadLocal原理及其在Struts/Spring中的应用

要想彻底弄懂ThreadLocal,还得看看它的源码!


对于ThreadLocal,我们用的最多的方法就是:get()/set(value)/remove()这3个操作。那么先看看set(value)方法的源码:


wKiom1TUYhXCB1viAAB-6--R7pE656.jpg


说明:

在set的时候,取出当前线程,并通过当前线程获得一个ThreadLocalMap,如果存在那么将ThreadLocal作为KEY,用户提供的值为VALUE设置进去。


追踪下getMap(Thread)和createMap(Thread,T)方法:


wKioL1TVakbS7gUcAAAyeH9XK-I690.jpg


返回了一个线程的成员变量threadLocals,查看下Thread的源码发现:

1
     ThreadLocal.ThreadLocalMap threadLocals =  null ;


ThreadLocalMap本是定义在ThreadLocal类中的内部类,但是却是Thread的一个成员变量!


其实到这里,我们就可以得出结论:


往一个ThreadLocal变量里面存东西,就相当于往当前线程的一个MAP成员变量里面存东西,KEY是ThreadLocal对象,VALUE就是你要放的东西。这样的话,在一个线程的任何地方都可以取出来,并且是绝对安全的,因为它是一个线程本身的属性,并非多个线程共享。


可以看下createMap(Thread,T)来验证上面的结论:

1
2
3
     void  createMap(Thread t, T firstValue) {
         t.threadLocals =  new  ThreadLocalMap( this , firstValue);
     }


正是由于ThreadLocal的特性,使得其在Struts/Spring中得到应用!


当一个请求到达web容器,一般而言,web容器会从线程池中取出一个空闲线程,那这个请求的数据比如request,是如何和这个线程建立关系的?struts2会将请求的数据做一下封装,然后放入到ThreadLocal中,所以一个线程中的请求数据是绝对安全的!


而在Spring中,ThreadLocal更是无处不在!


wKiom1TVcCPDX4CIAAC-IbgxKgU436.jpg

在DAO层,我们并没有在显式的给DAO方法传递Connection,它是怎么取到Connection的?

为什么在Spring的一个线程中我们取得的是同一个Connection?

......




Atomic

Atomic,英文的意思是“原子性的”,JDK在java.util.concurrent.atomic包中给我们提供了一组原子操作类,直接看一个例子,就能明白。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package  test14;
import  java.util.concurrent.atomic.AtomicInteger;
public  class  IntegerTest {
public  static  void  main(String[] args)  throws  InterruptedException {
AddTask task =  new  AddTask( 1 );
Thread[] threads =  new  Thread[ 10 ];
for ( int  i =  0  ; i <  10  ; i++){
threads[i] =  new  Thread(task);
threads[i].start();
}
for (Thread t : threads){
t.join();
}
System.out.println( "最终结果为:" );
task.display();
}
}
class  AddTask  implements  Runnable{
private  int  i =  0 ;
//private AtomicInteger atomic ;
public  AddTask( int  i){
this .i = i;
//this.atomic = new AtomicInteger(i);
}
@Override
public  void  run() {
try  {
Thread.sleep( 1000 );
catch  (InterruptedException e) {
e.printStackTrace();
}
i = i +  1 ;
//atomic.incrementAndGet();
}
void  display(){
System.out.println( "i = "  + i);
//System.out.println("atomicInteger = " + atomic);
}
}


当变量为普通int型时,由于i = i + 1这个操作并不是原子性,导致并发问题,往往结果<= 11,如果使用AtomicInteger时,就会始终得到11了。


在java.util.concurrent.atomic包下,提供了Integer/Long/Boolean类型的原子操作类,还提供了数组/引用类型的原子操作类。下面,以AtomicInteger为例,简单分析下原子操作类的实现原理。


注意在AtomicInteger类中的成员变量:

1
private  volatile  int  value;


注意用了volatile修饰,在并发时,其他线程可见!


我们分析一个方法就可以了,以incrementAndGet()为例:


wKiom1TVfoWzVeNjAAB1Q_ck9wg022.jpg


注意get()返回的就是那个成员变量value,实际上利用compareAndSet进行对比和修改,如果current和当前value进行比对,如果一致,说明老值一样,并没有其他线程修改过,那么可以将老值设置为next,否则死循环,尝试修改!其实这就是所谓的CAS机制。



Lock

我们不仅仅可以通过synchronized关键字来实现锁的目的,还可以通过java.util.concurrent.locks.Lock来达到目的。


比如我们经常这样写:

1
2
3
4
5
6
7
lock.lock();
try {
//xxx业务操作
} finally {
//务必释放锁
lock.unlock();
}


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1612581,如需转载请自行联系原作者


相关文章:

  • linux下乱码问题的终极解决方法
  • 【原】ios下比较完美的单例模式,已验证
  • 希望我们的婚姻,纯粹因为爱情
  • Rabbimq 安装配置详解
  • BGP路径选择次序
  • iOS类方法实例方法 与 self
  • 英语每日听写练习 Day 19
  • Puppet利用Nginx多端口实现负载均衡
  • mysql内存过高解决办法
  • JQuery EasyUI的datagrid的使用方式总结
  • Docker 容器抓包说明
  • 工作日志
  • 现代程序设计 作业 2
  • 金融安全资讯精选 2017年第十四期:十大顶级终端安全提供商报告,Uber承认数据泄露,微软“11月周二补丁日”发布53个漏洞补丁...
  • Content Assist not available at the current location
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • JavaScript DOM 10 - 滚动
  • JavaScript实现分页效果
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • leetcode388. Longest Absolute File Path
  • Redis 懒删除(lazy free)简史
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 每天一个设计模式之命令模式
  • 前端代码风格自动化系列(二)之Commitlint
  • 数据科学 第 3 章 11 字符串处理
  • 物联网链路协议
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • Java性能优化之JVM GC(垃圾回收机制)
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • #include<初见C语言之指针(5)>
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (function(){})()的分步解析
  • (ros//EnvironmentVariables)ros环境变量
  • (附源码)springboot教学评价 毕业设计 641310
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (三十五)大数据实战——Superset可视化平台搭建
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • (转)Oracle 9i 数据库设计指引全集(1)
  • ***通过什么方式***网吧
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .form文件_SSM框架文件上传篇
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 设计模式初探
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .net6使用Sejil可视化日志
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • @GlobalLock注解作用与原理解析
  • @Query中countQuery的介绍
  • [C++]运行时,如何确保一个对象是只读的