Java多线程并发编程 并发三大要素

一、原子性

原子,一个不可再被分割的颗粒。原子性,指的是一个或多个不能再被分割的操作。

int i = 1; // 原子操作
i++; // 非原子操作,从主内存读取 i 到线程工作内存,进行 +1,再把 i 写到朱内存。

虽然读取和写入都是原子操作,但合起来就不属于原子操作,我们又叫这种为“复合操作”。

我们可以用synchronized 或 Lock 来把这个复合操作“变成”原子操作。

例子:

 private synchronized void increase(){
    i++;
  }

 private int i = 0;
  Lock mLock = new ReentrantLock();

  private void increase() {
    mLock.lock();
    try {
      i++;
    } finally{
      mLock.unlock();
    }
  }

这样我们就可以把这个一个方法看做一个整体,一个不可分割的整体。

除此之前,我们还可以用java.util.concurrent.atomic里的原子变量类,可以确保所有对计数器状态访问的操作都是原子的。

例子:

AtomicInteger mAtomicInteger = new AtomicInteger(0);

  private void increase(){
    mAtomicInteger.incrementAndGet();
  }

二、可见性

当多线程访问某一个(同一个)变量时,其中一条线程对此变量作出修改,其他线程可以立刻读取到最新修改后的变量。

int i = 0;
// 线程 1 执行
i++;

// 线程 2 执行
System.out.print("i=" + i);

即使是在执行完线程里的 i++ 后再执行线程 2,线程 2 的输入结果也会有 2 个种情况,一个是 0 和 1。

因为 i++ 在线程 1(CPU1)中做完了运算,并没有立刻更新到主内存当中,而线程 2(CPU2)就去主内存当中读取并打印,此时打印的就是 0。

synchronized和Lock能够保证可见性。

另外volatile关键字也可以解决这个问题(下一篇会讲到)。

三、有序性

我们都知道处理器为了拥有更好的运算效率,会自动优化、排序执行我们写的代码,但会确保执行结果不变。

例子:

int a = 0; // 语句 1
int b = 0; // 语句 2
i++; // 语句 3
b++; // 语句 4

这一段代码的执行顺序很有可能不是按上面的 1、2、3、4 来依次执行,因为 1 和 2 没有数据依赖,3 和 4 没有数据依赖, 2、1、4、3 这样来执行可以吗?完全没问题,处理器会自动帮我们排序。

在单线程看来并没有什么问题,但在多线程则很容易出现问题。

再来个例子:

// 线程 1
init();
inited = true;

// 线程 2
while(inited){
	work();
}

init(); 与 inited = true; 并没有数据的依赖,在单线程看来,如果把两句的代码调换好像也不会出现问题。

但此时处于一个多线程的环境,而处理器真的把这两句代码重新排序,那问题就出现了,若线程 1 先执行 inited = true; 此时,init() 并没有执行,线程 2 就已经开始调用 work() 方法,此时很可能造成一些奔溃或其他 BUG 的出现。

synchronized和Lock能确保原子性,能让多线程执行代码的时候依次按顺序执行,自然就具有有序性。

而volatile关键字也可以解决这个问题,volatile 关键字可以保证有序性,让处理器不会把这行代码进行优化排序。

时间: 2017-05-21

Java并发编程总结——慎用CAS详解

一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

Java 并发编程之线程挂起、恢复与终止

挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的.如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件--其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁.另外,在长时间计算期间挂起线程也可能导致问题. 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: public class DeprecatedSuspendResume

java并发编程_线程池的使用方法(详解)

一.任务和执行策略之间的隐性耦合 Executor可以将任务的提交和任务的执行策略解耦 只有任务是同类型的且执行时间差别不大,才能发挥最大性能,否则,如将一些耗时长的任务和耗时短的任务放在一个线程池,除非线程池很大,否则会造成死锁等问题 1.线程饥饿死锁 类似于:将两个任务提交给一个单线程池,且两个任务之间相互依赖,一个任务等待另一个任务,则会发生死锁:表现为池不够 定义:某个任务必须等待池中其他任务的运行结果,有可能发生饥饿死锁 2.线程池大小 注意:线程池的大小还受其他的限制,如其他资源池:

Java多线程并发编程(互斥锁Reentrant Lock)

Java 中的锁通常分为两种: 通过关键字 synchronized 获取的锁,我们称为同步锁,上一篇有介绍到:Java 多线程并发编程 Synchronized 关键字. java.util.concurrent(JUC)包里的锁,如通过继承接口 Lock 而实现的 ReentrantLock(互斥锁),继承 ReadWriteLock 实现的 ReentrantReadWriteLock(读写锁). 本篇主要介绍 ReentrantLock(互斥锁). ReentrantLock(互斥锁)

Java 多线程并发编程_动力节点Java学院整理

一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

java并发编程之cas详解

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值.这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术. CAS的使用场景 在程序和算法中一个经常出现的模式就是"check and act"模式.先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作.下面是一个

Java 并发编程之ThreadLocal详解及实例

Java 理解 ThreadLocal 摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制

实例讲解Java并发编程之ThreadLocal类

ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当前执行线程在调用set时设置的最新值.可以将ThreadLocal<T>视为 包含了Map<Thread,T>对象,保存了特定于该线程的值. 概括起来说,对于多线程资源共享的问题,同步机制采用了"以时间换空间"的方式,而ThreadLocal采用了"以空间

Java并发编程之Semaphore(信号量)详解及实例

Java并发编程之Semaphore(信号量)详解及实例 概述 通常情况下,可能有多个线程同时访问数目很少的资源,如客户端建立了若干个线程同时访问同一数据库,这势必会造成服务端资源被耗尽的地步,那么怎样能够有效的来控制不可预知的接入量呢?及在同一时刻只能获得指定数目的数据库连接,在JDK1.5 java.util.concurrent 包中引入了Semaphore(信号量),信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器,通常用于限制可以访问某些资源(物

Java并发编程之Condition源码分析(推荐)

Condition介绍 上篇文章讲了ReentrantLock的加锁和释放锁的使用,这篇文章是对ReentrantLock的补充.ReentrantLock#newCondition()可以创建Condition,在ReentrantLock加锁过程中可以利用Condition阻塞当前线程并临时释放锁,待另外线程获取到锁并在逻辑后通知阻塞线程"激活".Condition常用在基于异步通信的同步机制实现中,比如dubbo中的请求和获取应答结果的实现. 常用方法 Condition中主要的

Java并发编程-volatile可见性详解

前言 要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么用? volatile主要对所修饰的变量提供两个功能 可见性 防止指令重排序 <br>本篇博客主要对volatile可见性进行探讨,以后发表关于指令重排序的博文. 什么是可见性? 把JAVA内存模型(JMM)展示得很详细了,简单概括一下 1.每个Thread有一个属于自己的工作内存(可以理解为每个

python 编程之twisted详解及简单实例

python 编程之twisted详解 前言: 我不擅长写socket代码.一是用c写起来比较麻烦,二是自己平时也没有这方面的需求.等到自己真正想了解的时候,才发现自己在这方面确实有需要改进的地方.最近由于项目的原因需要写一些Python代码,才发现在python下面开发socket是一件多么爽的事情. 对于大多数socket来说,用户其实只要关注三个事件就可以了.这分别是创建.删除.和收发数据.python中的twisted库正好可以帮助我们完成这么一个目标,实用起来也不麻烦.下面的代码来自t

浅谈Java并发编程之Lock锁和条件变量

简单使用Lock锁 Java 5中引入了新的锁机制--java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接口有3个实现它的类:ReentrantLock.ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁.读锁和写锁.lock必须被显式地创建.锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例

深入分析Java并发编程之CAS

在Java并发编程的世界里,synchronized 和 Lock 是控制多线程并发环境下对共享资源同步访问的两大手段.其中 Lock 是 JDK 层面的锁机制,是轻量级锁,底层使用大量的自旋+CAS操作实现的. 学习并发推荐<Java并发编程的艺术> 那什么是CAS呢?CAS,compare and swap,即比较并交换,什么是比较并交换呢?在Lock锁的理念中,采用的是一种乐观锁的形式,即多线程去修改共享资源时,不是在修改之前就加锁,而是乐观的认为没有别的线程和自己争锁,就是通过CAS的

JAVA线程sleep()和wait()详解及实例

JAVA线程sleep()和wait()详解及实例 sleep 1.sleep是Thread的一个静态(static)方法.使得Runnable实现的线程也可以使用sleep方法.而且避免了线程之前相互调用sleep()方法,引发死锁. 2.sleep()执行时需要赋予一个沉睡时间.在沉睡期间(阻塞线程期间),CPU会放弃这个线程,执行其他任务.当沉睡时间到了之后,该线程会自动苏醒,不过此时线程不会立刻被执行,而是要等CPU分配资源,和其他线程进行竞争. 3.此外如果这个线程之前获取了一个机锁,