Java并发编程基础

标签: JAVA concurrency | 发表时间:2014-05-20 16:40 | 作者:timon2010
出处:http://ifeve.com

1. 并发

1.1. 什么是并发?

并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力。如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善。现代的PC都有多个CPU或一个CPU中有多个核。是否能合理运用多核的能力将成为一个大规模应用程序的关键。

1.2. 进程 vs. 线程

进程是以独立于其他进程的方式运行的,进程间是互相隔离的。一个进程无法直接访问另一个进程的数据。进程的资源诸如内存和CPU时间片都是由操作系统来分配。

线程又被称为轻量级进程。每个线程有它独自的调用栈, 但是在同一进程下的线程又能互相访问它们间的共享数据。每个线程都有它独自的缓存。如果一个线程读取了某些共享数据,那么它将这些数据存放在自己的缓存中以供将来再次读取。

一个JAVA应用程序默认以一个进程的形式运行着。在一个JAVA程序中,你将协同多个不同的线程一起完成并行运算或实现异步行为。

2. 并发的改进与问题

2.1. 并发性能的上限

之所以并发号称能较快的完成某些任务是因为这些任务能被分组成多个子任务,并且这些子任务能被并行的执行。所以一个任务的实际运行效率将受限于该任务中能并行执行的部分。

一个程序理论上可达到的最高并发性能可通过以下一个被称为 Amdahl 定律来计算出:

设F为一个程序中不能被并行执行的百分比,N是处理器的数量,那么理论上该程序能获得的最高并发性能将可能是1/ (F+ ((1-F)/n)).

2.2. 并发问题

线程有独自的调用栈,但是又能互相访问共享的数据。所以这里你会遇到两个问题,可见性和访问。

可见性问题发生于如果线程A先读取了某些共享数据,之后线程B对这些数据进行了修改,那么线程A可能看不到线程B对这数据的改动。

访问问题发生于于 多个线程同时访问修改同一个共享数据。

可见性及访问问题将导致

  • 活跃性失败:由于并发访问数据导致程序无任何反应。 譬如,死锁。
  • 安全性失败:程序创建了错误的数据。

3. JAVA中的并发

3.1. 进程与线程

一个JAVA程序默认以一个线程运行在自己的进程中。JAVA语言通过Thread相关代码来支持线程。JAVA程序可通过Thread这个类来创建线程。从JAVA1.5起,在java.util.concurrent中提供了改进的并发库。

3.2. 锁和线程同步

JAVA提供了“锁” 机制来保护代码片段免于被多个线程在同一时刻运行。最简单的锁住一个方法或一个类就是在该方法或类前添加synchronized关键字

在Java中synchronized关键字保证了:

  • 在同一时刻,只有单个线程能访问这块代码
  • 每个进入这同步代码块的线程都将能看到之前持有相同锁进入的线程的所做的改动

对于线程间的可靠通讯及互斥访问 来说,同步是非常必要的

你可以在定义方法时使用synchronized关键字。这个关键字保证了同一时刻只有一个线程能运行这个方法。其他同样调用了这个方法的线程将被阻塞直到第一个线程离开这个方法。

public synchronized void critial() {
  // some thread critical stuff
  // here
}

你同样也能用synchronized关键字来保护方法中的代码块。这块代码将由一个关键对象来保护,该关键对象可以是个string或其他object。这个关键对象就被称为 lock。所有被相同lock保护的代码在同一时刻只能被单个线程执行。

举例来说,以下的数据结构将保证同时只有单个线程可以访问add和next方法

package de.vogella.pagerank.crawler;

import java.util.ArrayList;
import java.util.List;
/*** Data structure for a web crawler. Keeps track of the visited sites and keeps 
  * a list of sites which needs still to be crawled. 
  * @author Lars Vogel 
  */
<pre>public class CrawledSites {
  private List<String> crawledSites = new ArrayList<String>();
  private List<String> linkedSites = new ArrayList<String>();

  public void add(String site) {
    synchronized (this) {
      if (!crawledSites.contains(site)) {
        linkedSites.add(site);
      }
    }
  }
/** * Get next site to crawl. Can return null (if nothing to crawl) */
  public String next() {
    if (linkedSites.size() == 0) {
      return null;
    }
    synchronized (this) {
      <em>// Need to check again if size has changed</em>
      if (linkedSites.size() > 0) {
        String s = linkedSites.get(0);
        linkedSites.remove(0);
        crawledSites.add(s);
        return s;
      }
      return null;
    }
  }

}

3.3. Volatile

如果一个变量声明时使用了 volatile 关键字,那么该关键字保证了,任何读取该变量的线程都将读到最新写进该变量的值。但 volatile 关键字不会保证变量上的任何互斥访问

As of Java 5 write access to a  volatile variable will also update non-volatile variables which were modified by the same thread. This can also be used to update values within a reference variable, e.g. for a  volatile variable person. In this case you must use a temporary variable person and use the setter to initialize the variable and then assign the temporary variable to the final variable. This will then make the address changes of this variable and the values visible to other threads.

因为在JAVA 5中,对一个声明了 volatile 的变量进行写操作会导致该写操作所在该线程之前所有的对非 volatile 变量的修改进行同步更新。这也可用来更新引用类型变量。例如,有个 volatile 类型的变量person。你必须创建一个临时变量person,然后调用SETTER方法来初始化这变量然后将临时变量赋值给final变量。 这会使这个变量的地址发生改变并且此变量的值对于其他线程变为可见。

4. JAVA 内存模型

4.1. 概览

JAVA内存模型描述了线程内存与主存间的通讯关系。

JAVA内存模型定义了线程内的内存改变将怎样传递到其他线程的规则,同样也定义了线程内存与主存进行同步的场景,也描述了哪些操作属于原子操作及操作间的顺序。

4.2. 原子操作

一个原子操作是指一个执行时不会被其他操作影响到的最小单位的操作。

JAVA语言规范保证了对一个变量的读和写操作都是原子的(除了LONG和DOUBLE类型的变量)。对于LONG或DOUBLE类型的变量,只有当这些变量声明时使用了volatile关键字才是原子的

假设定义了一个INT变量I,那么在java中,I++操作不是一个原子操作。同样,这对于其他数字类型的变量也都不是一个原子操作。

I++操作先从I中读取了当前值(这是个原子操作)然后再让它加上1写回(原子操作)。但是在读和写这两个操作间,I的值有可能被改变。

从JAVA1.5起,JAVA提供了原子变量,例如AtomicInteger 或 AtomicLong 都提供了类似  getAndDecrement()getAndIncrement() 及  getAndSet()等原子方法。

4.3.同步代码块的内存更新

JAVA内存模型保证了每个进入相同锁同步块的线程都能看到之前进入的其他所有线程修改的结果。

(全文完)如果您喜欢此文请点赞,分享,评论。





您可能感兴趣的文章

相关 [java 并发 编程] 推荐:

Java并发编程基础

- - 并发编程网 - ifeve.com
并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力. 如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善. 现代的PC都有多个CPU或一个CPU中有多个核. 是否能合理运用多核的能力将成为一个大规模应用程序的关键. 进程是以独立于其他进程的方式运行的,进程间是互相隔离的.

java 并发编程 synchronized

- - Java - 编程语言 - ITeye博客
同步原语--synchronized. synchronized(class)很特别,它会让另一个线程在任何需要获取class做为monitor的地方等待.class与this做为不同的监视器可以同时使用,不存在一个线程获取了class,另一个线程就不能获取该class的一切实例.. ->线程各自获取monitor,不会有等待..

Java并发编程【1.2时代】

- - 并发编程网 - ifeve.com
         本文介绍了Java原生的多线程技术(1.2),通过详细介绍wait和notify相关的机制、基础的多线程技术以及基于这些技术的等待超时、线程间的通信技术和线程池高阶技术,最后通过一个基于线程池的简单文本web服务器—MollyServer,来阐明多线程带来好处. 通过介绍这些技术,展示了在没有使用Java并发包的时代(1.5-)是如何完成Java的多线程编程,为理解Java5提供了良好帮助.

java并发编程下变量可见行分析

- - ITeye博客
 在ubutun双核cpu下,默认不加任何jvm参数执行. 输出如下: 主线程执行完之后,子线程一直在执行,为什么子线程没有获取到主线程修改done之后的变量值呢. 我们再设置下jvm的参数为 -client,则子线程能够获取主线程修改done之后的值,正常执行完. 也就是moren ubutun下默认jvm启动是-server 服务器默认启动的,那么-server启动跟client启动有什么区别呢.

java并发编程实践学习笔记

- - zzm
    原子操作:原子为不可再分操作.    Violation :可见关键字.    Synchronized:内部隐示锁 .    ReentrantLock:显示锁 .    ReentrantReadWriteLock:读写锁 . jmm(java内存模型):. 线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递 均需要通过主存完成.

Java并发编程-生成唯一序列号

- - 编程语言 - ITeye博客
package com.league.idgenerate; /** * * ID生成器接口, 用于生成全局唯一的ID流水号 * * @author Ivan.Ma */ public interface IdGenerator {. * 生成下一个不重复的流水号. package com.league.idgenerate; /** * ID生成器的配置接口 * @author Ivan.Ma */ public interface IdGeneratorConfig {.

关于Java并发编程的总结和思考

- - ImportNew
并发其实是一种解耦合的策略,它帮助我们把做什么(目标)和什么时候做(时机)分开. 这样做可以明显改进应用程序的吞吐量(获得更多的CPU调度时间)和结构(程序有多个部分在协同工作). 做过Java Web开发的人都知道,Java Web中的Servlet程序在Servlet容器的支持下采用单实例多线程的工作模式,Servlet容器为你处理了并发问题.

Java编程规范

- - Web前端 - ITeye博客
本文档的编写从简,绝大多数内容以条款或者表格形式列出,不做过多的补充说明,代码格式规范遵循eclipse的默认编码规范要求. •    简单,易执行. 1.    名字含义要明确,做到见名知义,如: User,Role, UserManager. 2.    尽量使用英文名字作为变量名,如果要使用中文,请写上备注.

java编程风格指南

- - 行业应用 - ITeye博客
受不了的可以直接到以下网址查看. 作者:Hawstein 出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,转载请注明作者及出处.

面向GC的Java编程

- - 并发编程网 - ifeve.com
Java程序员在编码过程中通常不需要考虑内存问题,JVM经过高度优化的GC机制大部分情况下都能够很好地处理堆(Heap)的清理问题. 以至于许多Java程序员认为,我只需要关心何时创建对象,而回收对象,就交给GC来做吧. 甚至有人说,如果在编程过程中频繁考虑内存问题,是一种退化,这些事情应该交给编译器,交给虚拟机来解决.