`

Java多线程发展简史(4)

阅读更多
JDK 6.0

JDK 6.0对锁做了一些优化,比如锁自旋、锁消除、锁合并、轻量级锁、所偏向等。在这里不一一介绍,但是给一个例子以有感性认识:

import java.util.Vector;  
   
public class LockElimination {  
    public String getStr() {  
        Vector v = new Vector();  
        v.add(3);  
        v.add(4);  
        return v.toString();  
    }  
       
    public static void main(String[] args) {  
        System.out.println(new LockElimination().getStr());  
    }  
} 

在这个例子中,对vector的加锁完全是没有必要的,这样的锁是可以被优化消除的。

CyclicBarrier是JDK 6.0新增的一个用于流程控制的类,这个类可以保证多个任务在并行执行都完成的情况下,再统一执行下一步操作:



上面这个例子就模拟了,两个子任务(分别执行2000毫秒和4000毫秒)完成以后,再执行一个总任务(2000毫秒)并打印完成。

还有一个类似的类是CountDownLatch(使用倒数计数的方式),这样的类出现标志着,JDK对并发的设计已经逐步由微观转向宏观了,开始逐步重视并发程序流程,甚至是框架上的设计,这样的思路我们会在下文的JDK 7.0中继续看到。

import java.util.concurrent.BrokenBarrierException;  
import java.util.concurrent.CyclicBarrier;  
   
public class BarrierUsage extends Thread {  
    private static CyclicBarrier barrier = new CyclicBarrier(2, new Thread() {  
        public void run() {  
            try {  
                Thread.sleep(2000);  
            } catch (InterruptedException e) {  
            }  
            System.out.println("finish");  
        };  
    });  
   
    private final int sleepMilSecs;  
   
    public BarrierUsage(int sleepMilSecs) {  
        this.sleepMilSecs = sleepMilSecs;  
    }  
   
    @Override 
    public void run() {  
        try {  
            Thread.sleep(sleepMilSecs);  
            System.out.println(sleepMilSecs + " secs slept");  
            barrier.await();  
        } catch (InterruptedException e) {  
        } catch (BrokenBarrierException e) {  
        }  
    }  
   
    public static void main(String[] args) {  
        new BarrierUsage(2000).start();  
        new BarrierUsage(4000).start();  
    }  
} 

JDK 7.0

2011年的JDK 7.0进一步完善了并发流程控制的功能,比如fork-join框架:



把任务分解成不同子任务完成;比如Phaser这个类,整合了CyclicBarrier和CountDownLatch两个类的功能,但是提供了动态修改依赖目标的能力;还有NIO2的新开放特性。这里不详细介绍了。

Java的未来

在多线程编程方面,Java的未来会怎样?

JDK 8.0按计划将在2013年夏天发布,Java从动态语言那里学了很多过来,比如闭包等等,在多线程方面会怎样呢?郁于JLS所限,无法有大的突破,还是有另辟蹊径的办法?纵观整个Java发展的历程,都在努力修正多线程模型实现上的种种弊端,尽可能在保留虚拟机优化特性的基础上给使用者屏蔽细节。

在来回想一下Java最基础的线程模型,其他语言是怎样实现的呢?

比如C#,任何类的任何方法,都可以成为线程的执行方法:

using System;  
using System.Threading;  
   
public class AnyClass {  
    public void DoSth() {  
         Console.WriteLine("working");  
    }  
}  
   
class ThreadTest{  
    public static void Main() {  
        AnyClass anyClass = new AnyClass();  
        ThreadStart threadDelegate = new ThreadStart(anyClass.DoSth);  
        Thread myThread = new Thread(threadDelegate);  
           
        myThread.Start();  
    }  
} 

上面的AnyClass的DoSth方法,就模拟线程执行打印了一句话。

再来看一门小众语言Io,在语法糖的帮助下,实现更加简单:

thread := Object clone  
thread start := method("working" println)  
thread @@start 

因为Io是基于原型的语言(如果你有兴趣的话,可以在我的blog里找到Io介绍),通过这样的@符号,就实现了新启一个线程运行的功能。

再来看看JDK 5.0的ReentrantLock类,它完全实现了synchronized语义上的全部功能,并且还能具备诸如条件锁、锁超时、公平锁等等更优越的特性(特别值得一提的是tryLock的功能也实现了,就是说可以判定假如这个时间获取锁是否能够成功),甚至在并发量居高不下时,性能还更加优越……我不禁要问,用一个Java实现的锁类去从功能上代替一个已有的同步关键字,这岂不是Java自己在抽自己嘴巴?

import java.util.concurrent.locks.ReentrantLock;  
   
public class ReentrantLockUsage implements Runnable {  
   
    private static ReentrantLock lock = new ReentrantLock();  
   
    @Override 
    public void run() {  
        lock.lock();  
   
        try {  
            System.out.println("do something 1");  
            Thread.sleep(2000);  
        } catch (InterruptedException e) {  
        } finally {  
            lock.unlock(); // Why put it in finally block?  
        }  
   
        System.out.println("finish 1");  
    }  
   
    public static void main(String[] args) {  
        new Thread(new ReentrantLockUsage()).start();  
        lock.lock();  
   
        try {  
            System.out.println("do something 2");  
            Thread.sleep(2000);  
        } catch (InterruptedException e) {  
        } finally {  
            lock.unlock();  
        }  
   
        System.out.println("finish 2");  
    }  
} 

其实这个问题渊源已久,JLS在最初把Java锁机制(包括synchronized关键字)定得太死,以至于无法在上面做进一步的修正和优化,无奈只好另外重新建一个类来做这些未竟的事情。如果让Jame Gosling重新回到二十多年前,他也许会选择不同的实现。

关于协程(coroutine)。很多语言都内置了对协程的实现(协程的含义请自行查阅维基百科),听起来似乎是一个崭新的名字,但是其实这个概念一点都不新,JavaScript引擎对单个页面的解析就是通过协程的方式在一个线程内完成的。协程的实现困难有两点,一个是异常的处理,一个是出入线程时现场(包括堆栈)的保存和设置。有一些开源库已经有了Java上协程的实现,如果你感兴趣的话,不妨关注Kilim和Coroutine for Java。

最后,让我们来回顾一下Java多线程发展的历史。从Java诞生到如今有二十年了,可未来会怎样,又谁知道呢?



补充2012-09-18:

jinnianshilongnian回复:

一、我觉得非原子性的++操作这句话有点模糊,如下所示:

1、nonAtomicCounter++; 不是原子的原因是因为它是静态/实例变量,需要 读/操作/写对象成员变量。可以加把锁保证读/操作/写原子性

synchronized(atomicCounter) {

nonAtomicCounter++;

}


2、如果nonAtomicCounter++; 是局部变量 仅有一条指令 iinc i,1;但局部变量又不会线程不安全;

3、nonAtomicCounter如果是long(64位)的在32位机器即使是局部变量也是线程不安全的(四火补充:在64位机器上也不是线程安全的);

4、Atomic×××等类通过Unsafe的compareAndSwap××× 即CAS完成的。

应该是使用成员变量的++时。


二、对于文中这段话:

但是,上面的情况是对boolValue使用volatile修饰保证其可见性的情况下出现的,如果不对boolValue使用 volatile修饰,运行时就一次不会出现(起码在我的电脑上)打印“WTF!”的情形,换句话说,这反而是不太正常的,我无法猜测JVM做了什么操作,基本上唯一可以确定的是,没有用volatile修饰的时候,boolValue在获取的时候,并不能总取到最真实的值。

这个应该是工作内存 和 主内存 同步的问题。 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。

但[boolValue == !boolValue] 和 check/swap 操作并不是原子操作。

也可以通过 在check/swap的两个boolValue加锁来保证同步

synchronized(this) { 

      boolValue = !boolValue; 

}
三、对于DCL问题那段代码,网上也有文章说即使使用volatile也不能保证DCL的安全性:

http://www.ibm.com/developerworks/java/library/j-dcl/index.html

四、说明:

你给的ibm那个文章链接也说到,“The memory model allows what is known as "out-of-order writes" and is a prime reason why this idiom fails.” 所以根因在于out of order writes,引起的问题是“partially initialized”,但是文章里面提到使用volatile不能解决问题的原因在于一些JVM的实现并不能保证顺序一致性,换句话说,对于 happens-before守则并没有严格遵守,且不说他的说法是否有根据,我谈论这个问题的时候一定是在JLS明确规定以下进行的。至于虚拟机实现上的问题,我不得而知。

FYI:

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

另外对于element前面如果不加volatile/final的话,也不能保证解决DCL问题,这里四火做一个说明:

在于instance的可见性由volatile保证了,可是element的name并没有任何语义上的保证,这里可以使用 volatile,但是对于不可变对象其实也可以使用在这里语义弱一些的final,也是可以解决问题的,JSR133对这两个关键字的语义做了强化,我上面给的链接里面也提到了,“the values assigned to the final fields in the constructor will be visible to all other threads without synchronization”。

感谢讨论。

原文链接:http://www.raychase.net/?p=698

ref:http://developer.51cto.com/art/201209/357617_3.htm
  • 大小: 21.1 KB
  • 大小: 201.2 KB
  • 大小: 44.7 KB
分享到:
评论

相关推荐

    狂神说Java-多线程课程全部代码.rar

    狂神说Java-多线程课程全部代码.rar

    java核心技术第八版源代码(全)

    1.4 Java发展简史 1.5 关于Java的常见误解 第2章 Java程序设计环境 第3章 Java基本的程序设计程序 第4章 对象与类 第5章 继承 第6章 接口与内部类 第7章 图形程序设计 第8章 事件处理 第9章 Swing用户界面组件 第10...

    [java.核心技术.第八版].Core.Java..8th.Edition源代码 示例代码

    1.4 Java发展简史 1.5 关于Java的常见误解 第2章 Java 程序设计环境 第3章 Java基本的程序设计程序 第4章 对象与类 第5章 继承 第6章 接口与内部类 第7章 图形程序设计 第8章 事件处理 第9章 Swing用户界面组件 第...

    JAVA核心技术第一卷基础知识(原书第九版)

    1.4 Java发展简史 1.5 关于Java的常见误解 第2章 Java程序设计环境 2.1 安装Java开发工具箱 2.1.1 下载JDK 2.1.2 设置执行路径 2.1.3 安装库源文件和文档 2.1.4 安装本书中的示例 2.1.5 导航Java目录 2.2 选择开发...

    Java核心技术 卷I(原书第8版).Part1 pdf

    1.4 Java发展简史 1.5 关于Java的常见误解 第2章 Java程序设计环境 第3章 Java基本的程序设计程序 第4章 对象与类 第5章 继承 第6章 接口与内部类 第7章 图形程序设计 第8章 事件处理 第9章 Swing用户界面组件 第10...

    Java核心技术 卷I(原书第8版).part2 PDF

    1.4 Java发展简史 1.5 关于Java的常见误解 第2章 Java程序设计环境 第3章 Java基本的程序设计程序 第4章 对象与类 第5章 继承 第6章 接口与内部类 第7章 图形程序设计 第8章 事件处理 第9章 Swing用户界面组件 第10...

    Core Java. Volume I. Fundamentals, 8th Edition JAVA核心技术1基础知识

    1.4 Java发展简史 1.5 关于Java的常见误解 第2章 Java程序设计环境 第3章 Java基本的程序设计程序 第4章 对象与类 第5章 继承 第6章 接口与内部类 第7章 图形程序设计 第8章 事件处理 第9章 Swing用户界面组件 第10...

    JAVA基础课程讲义

    JAVA发展简史 12 JAVA为什么能够流行? 13 JAVA各版本的含义 13 JAVA技术体系架构 14 JAVA的特性和优势 14 JAVA应用程序的运行机制 15 JVM(JAVA VIRTUAL MACHINE) 16 Java运行时环境JRE(Java Runtime Environment) ...

    Java并发编程实战

    1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 ...

    java基础案例与开发详解案例源码全

    1.1 Java语言发展简史2 1.2 认识Java语言3 1.2.1 Java语言特性3 1.2.2 JavaApplet4 1.2.3 丰富的类库4 1.2.4 Java的竞争对手5 1.2.5 Java在应用领域的优势7 1.3 Java平台的体系结构7 1.3.1 JavaSE标准版8 1.3.2 ...

    疯狂JAVA讲义

    1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6 1.3.2 Java程序的...

    Java2核心技术.part5

    1. 4 Java发展简史 1.5关于Java的常见误解 第2章Java程序设计环境 2.1安装Java开发工具箱 2.1.1下载JDK 2.1.2设置执行路径 2.1.3安装库源代码和文档 2.1.4安装本书中的示例 2.1.5导航Java目录 ...

    Java2核心技术.part4

    1. 4 Java发展简史 1.5关于Java的常见误解 第2章Java程序设计环境 2.1安装Java开发工具箱 2.1.1下载JDK 2.1.2设置执行路径 2.1.3安装库源代码和文档 2.1.4安装本书中的示例 2.1.5导航Java目录 ...

    Java语言基础下载

    面向对象语言的发展简史 26 内容总结 29 独立实践 30 第三章:面向对象的程序设计 31 学习目标 31 类和对象的描述 32 声明类 32 声明属性 33 声明成员方法 34 源文件的布局 36 包的声明 36 包与目录的布局 38 内容...

    Java 并发编程实战

    1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 ...

    基于SMTP邮件截获与还原技术研究.pdf

    2.4 Java的多线程技术 24 2.4.1 线程与进程的区别 24 2.4.2 多线程的调度 26 2.4.3 线程的状态 26 2.4.4 线程的同步机制 27 2.4.5 系统多线程的实现 28 第三章 监控系统的整体设计 29 3.1邮件监视系统的基本功能 29 ...

    Java2核心技术.part3

    1. 4 Java发展简史 1.5关于Java的常见误解 第2章Java程序设计环境 2.1安装Java开发工具箱 2.1.1下载JDK 2.1.2设置执行路径 2.1.3安装库源代码和文档 2.1.4安装本书中的示例 2.1.5导航Java目录 ...

    Java2核心技术.part1

    1. 4 Java发展简史 1.5关于Java的常见误解 第2章Java程序设计环境 2.1安装Java开发工具箱 2.1.1下载JDK 2.1.2设置执行路径 2.1.3安装库源代码和文档 2.1.4安装本书中的示例 2.1.5导航Java目录 2.2选择...

Global site tag (gtag.js) - Google Analytics