所谓的并发,一般是指基于多处理器硬件环境的,允许系统在同一时刻执行多件不同的任务逻辑,在单处理器硬件环境下,一般是按照时间片轮转的调度方式,实现宏观意义上的并发,而事实上,在同一个时间点上,仍然只有一件任务在运行,我习惯把这种并发看成“伪并发”,以下所讲的并发临界资源管理,是基于多CPU硬件环境的。即在同一时刻,正在运行的不同CPU可能会访问一些共用的资源,而临界资源管理需要做的就是保证,这些资源的读写操作不能陷入死锁以及,各线程获得的资源都是最新的。

在提到并发之前,我得提到一个基础问题,那就是,程序在运行时,为了加快系统的运算速度,临时数据一般是放在CPU缓存(Cache)中的,而不同的线程占有一段不同的Cache,所以这里就涉及到一个问题,即对共享资源而言,如何保证所有线程读写的同步。这就是并发所要解决的临界资源管理问题的来由。java并发解决这个问题都是基于以下原理:保证每个线程对共享资源的写操作都是独占式的,且将发生改变的共享资源写回到主存(memory)中,基于缓存一致性规则,系统所有Cache中的相同共享资源值将全部过期失效,其他线程的读操作被迫从主存中去取值了。(硬件实现,就不细说了)

一. Java多线程的实现

Java中从语言上实现多线程是通过实例化Thread类,调用实例start()方法,执行其run()方法所定义的任务来实现的。 得到我们自己的Thread实例,一般有两种方式,一是让自己的任务类继承Thread父类,并覆写其run方法,最后实例化这个任务类,调用start()方法,启动线程。

另一种方法是任务类继承Runnable接口,并实现其run()方法,通过Thread myThread = new Thread(myRunnable),传入该Runnable实例作为构造器参数,从而获得Thread实例,调用start()方法,启动线程。这种方式较为常用。

以上提到的两种方式,都需要显示获得Thread类的实例,并针对每单个Thread进行操作与管理。但在所需构建的线程较多时,这种方式便显得较为繁琐,因为对系统资源的占用与释放问题都交给了我们的设计者,所以Java中也提供了类似线程池的管理方式来管理这些线程。这些管理工具在java.util.concurrent包中。通过ExecutorService实例,进一步封装了Thread管理细节,通过调用其execute()方法,执行线程,并通过shutdown()方式,释放掉其中所有的线程所占用的资源。其通用代码如下:(推荐)

ExecutorService es = Executors.newCachedThreadPool();//获取线程池

for(int i=0;i<5;i++){

es.execute(new Accessor(i)); //执行线程

}

TimeUnit.SECONDS.sleep(5);

es.shutdown(); //释放所有资源

其获取线程池的方式有三种,分别是

Executors.newCachedThreadPool():创建的线程池大小等于实际线程数 Executors.newFixedThreadPool(int num):创建固定大小的线程池 Executors.newSingleThreadExecutor():固定线程池大小为1

二. 临界资源管理

这部分涉及到几个常用的关键词,一个是synchronized,一个是volatile,一个是Atomatic*一系列原子类,以及ThreadLocal(线程本地存储)。

1. volatile

这个关键字用来修饰变量,其功能和影响只需要记住一句话,它保证的是,被其所修饰的变量值,在每次发生改变之后,必定即时将改动后的值刷新写入主存中。

其局限性也在于此,他只能保证变量的同步,而不是功能性的同步。例如对被volatile修饰的变量i,执行操作i++;这个操作并不线程安全,因为这个操作不是原子性的,它可以拆分成两步,一步是读i值,第二步是做加法;volatile只能保证第一步读到的值一定是当时最新的,但在第二步之前,该线程可能被暂时挂起,进而去执行其他的线程,如果其他线程在此时修改了i的值,那么第二步算出来的值就不是那么合理了。所以功能性的同步,就是synchronized这个关键字的事情了。

2. synchronized

这是Java并发保证同步最常用到的一个关键字。利用该关键字来保证同步是通过加锁机制实现的,也可以说是一种隐式加锁方式,之所以这么说,是因为你可以使用java.util.concurrent.locks类库中提供的Lock类显示地对代码块进行加锁以实现线程同步。

Java中的每一个对象都可以作为锁,这里的锁是针对加锁和解锁两种操作来说的,即同一时刻至多只有一个线程能够访问作为锁的对象。根据synchronized的用途:synchronized可以用于修饰普通方法、静态方法以及代码块,锁的表现形式有以下三种:

对于普通同步方法,锁是当前实例对象;对于静态同步方法,锁是当前类的Class对象;对于同步方法块,锁是synchronized括号里参数指明的对象。

关于synchronized需要说明的有两点:

(1)对于某个特定对象来说,其所有synchronized方法共享同一个锁,即当一个类中同时包含多个Synchronized修饰的成员方法时,该类的一个实例对象,同一时刻只能访问其中一个成员方法,对同步代码块,这一规则同样成立。

(2)synchronized关键字不属于方法特征签名的组成部分,所以可以在覆盖方法的时候加上去。

3. 原子类

需要首先说明的是,原子操作是指不能被线程调度机制中断的操作。原子性可以用于除了long和double之外的所有基本类型之上的简单操作,即对于读取和写入除long和double之外的基本类型变量的值的操作,是原子操作。但是由于long和double为64bit数据,而JVM对64位的读取和写入是当做两个分离的32位操作来执行,这就可能产生一个在读取和写入操作中间发生上下文切换的隐患,导致不正确结果的可能性,这也被称为字撕裂。所以Java SE5引入了诸如AtomicInteger、AtomicLong以及AtomicReference等特殊的原子性变量,并提供了其对应的读写方法,从而保证其读写操作的原子性。具体参考手册,其类库为java.util.concurrent.atomic。

4. ThreadLocal

引用自《Java编程编程思想》——“防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。因此,如果你有5个线程都要使用变量X所表示的对象,那么线程本地存储会生成5个用于X的不同的存储块。”,而创建和管理线程本地存储可以用java.lang.ThreadLocal类来实现。

其提供的示例代码如下:

/**

* Created by Song on 2016/10/15.

*/

public class ThreadLocalVariableHolder {

private static ThreadLocal<Integer> values = new ThreadLocal<Integer>(){

private Random rand = new Random(47);

@Override

protected synchronized Integer initialValue() {

return rand.nextInt(10000);

}

};

public static void increment(){

values.set(values.get()+1);

}

public static int get(){return values.get();}

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

ExecutorService es = Executors.newSingleThreadExecutor();

for(int i=0;i<5;i++){

es.execute(new Accessor(i));

}

TimeUnit.SECONDS.sleep(5);

es.shutdown();

}

}

class Accessor implements Runnable{

private final int id;

public Accessor(int id){this.id=id;}

public void run() {

while (!Thread.currentThread().isInterrupted()){                     ThreadLocalVariableHolder.increment();

System.out.println(this); Thread.yield();

}

}

public String toString(){

return "#"+id+": "+ThreadLocalVariableHolder.get();

}

}

转载于:https://juejin.im/post/5cedfda1f265da1ba84a729e

java并发临界资源管理相关推荐

  1. 忘掉 Java 并发,先听完这个故事。。。

    最近在给别人讲解 Java 并发编程面试考点时,为了解释锁对象这个概念,想了一个形象的故事. 后来慢慢发现这个故事似乎能讲解 Java 并发编程中好多核心概念,于是完善起来形成了这篇文章. 大家先忘记 ...

  2. 忘掉Java并发,先听完这个故事...

    点击上方"朱小厮的博客",选择"设为星标" 回复"666"获取公众号专属资料 来源:http://uee.me/b27Xt 最近在给别人讲解 ...

  3. Java并发容器(一) CocurrentHashMap的应用及实现

    转载自 http://blog.csdn.net/qq_24451605/article/details/51125946 CocurrentHashMap的优势 首先常用三种HashMap包括Has ...

  4. Java并发编程实战笔记

    如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误.有三种方式可以修复这个问题: i.不在线程之间共享该状态变量 ii.将状态变量修改为不可变的变量 iii.在访问状态变 ...

  5. Java 并发编程概念深入理解

    why-为什么要有多线程? 单线程情况下: 在有IO操作的情况下,线程是在阻塞的,cpu什么事情也不干,直到IO操作完成 如果没有IO操作且是单核cpu,可以是单线程 多线程的情况下: 有IO操作的情 ...

  6. java并发编程实战学习(3)--基础构建模块

    转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...

  7. Java并发相关知识(多线程、锁、容器、工具)

    目录 一.基础知识 线程之间如何通信? Java内存模型 内存屏障 顺序一致性 CAS实现原理 原子操作 volatile synchronized 实现原理 什么是锁 原子操作类说明 高性能原子类 ...

  8. Java并发编程 - 第三章 Java内存模型

    前言: Java 线程之间的通信对程序员完全透明,内存可见性问题很容易困扰 Java 程序员,本章将揭开 Java 内存模型神秘的面纱. 一.Java 内存模型的基础 1.1 并发编程模型的两个关键问 ...

  9. Java并发编程的艺术(推荐指数:☆☆☆☆☆☆)

    文章目录 Java并发编程的艺术(推荐指数:☆☆☆☆☆☆) 并发编程的挑战 Java并发机制的底层实现原理 Volatile的应用 实现原理 synchronized的实现原理与应用 对象头 锁详解 ...

最新文章

  1. Vue.js响应式原理
  2. 数字电路知识点杂谈(自用,其他人看不懂)
  3. ArcGIS锁定显示比例
  4. 判断一个数是否为质数
  5. MVC中处理表单提交的方式(Ajax+Jquery)
  6. Hibernate拦截器字段加密解密
  7. 大众点评全球吃货地图 五一海外游神器
  8. GA算法(遗传算法) ——以求解achley,rastrigin函数为例
  9. Python编写中国象棋棋盘(可视化页面)
  10. mysql5.7卸载服务_Mysql5.7.28安装配置、卸载—CentOS7.6生产环境下的微服务部署(四)...
  11. 白话 RESTful,OpenApi(OAS),Swagger
  12. 校友会小程序开发笔记十八:为浏览记录(我的足迹)模块的设计与实现
  13. UWP中的Direct2D
  14. android获取ro._Android 简单的设备信息获取
  15. C++ define用法
  16. CSS hack常用方案(摘选)
  17. 2022G1工业锅炉司炉考试题库及模拟考试
  18. jzoj3208. 【JSOI2013】编程作业(kmp)
  19. 二进制转BCD码模块
  20. 知名投资人大卫 · 考恩:量子计算值得我“豪赌”

热门文章

  1. [置顶] J2EE (八) 策略模式+装饰模式+反射(java)
  2. 无聊了就上来转转,看看大家发的东东也挺好玩的啊
  3. Archiva 2.2.3 安装运行的时候出现协议版本错误
  4. 老男孩shell实战读书笔记 (6-10章节)
  5. Hadoop集群启动、初体验
  6. JDBC连接数据库:单线程、多线程、批处理插入数据的对比
  7. Java什么时候提高境界支持async/await写法啊?
  8. java i++与++i与与之间的区别在哪里
  9. Topology and Geometry in OpenCascade-Face
  10. Android 短信开发学习