驯服Java线程(二)

原创 2003年03月08日 15:03:00

Java 线程的支持不是平台独立的<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

非常不幸,作为Java语言所保证的平台独立性最重要的组成部分-------Java线程,并非是平台独立的。这增加了实现不依赖于平台的线程系统的难度。在实现的时候,不得不考虑每个平台的细微区别,以确保你的程序在每个平台都保持一致。其实,写一个独立于平台的程序,还是有可能的,但必须非常小心。不过你可以放心,这个令人失望的事实,并不是Java的问题。(“AceFramework 就是一个非常好的,也非常复杂的平台独立线程系统http://www.cs.wustl.edu/~schmidt/ACE.html)。所以,在我继续讲下去之前,我不得不线讨论一下,由于平台的多样性,而导致的JVM的不一致性。

线程和进程(Threads and Processes)

第一个关键的系统级概念,究竟什么是线程或者说究竟什么是进程?她们其实就是操作系统内部的一种数据结构。

进程数据结构掌握着所有与内存相关的东西:全局地址空间、文件句柄等等诸如此类的东西。当一个进程放弃执行(准确的说是放弃占有CPU),而被操作系统交换到硬盘上,使别的进程有机会运行的时候,在那个进程里的所有数据也将被写到硬盘上,甚至包括整个系统的核心(core memory)。可以这么说,当你想到进程(process),就应该想到内存(memory (进程 == 内存)。如上所述,切换进程的代价非常大,总有那么一大堆的内存要移来移去。你必须用秒这个单位来计量进程切换(上下文切换),对于用户来说秒意味着明显的等待和硬盘灯的狂闪(对于作者的我,就意味着IBM龙腾3代的烂掉,5555555)。言归正传,对于Java而言,JVM就几乎相当于一个进程(process),因为只有进程才能拥有堆内存(heap,也就是我们平时用new操作符,分出来的内存空间)。

那么线程是什么呢?你可以把它看成“一段代码的执行”---- 也就是一系列由JVM执行的二进制指令。这里面没有对象(Object)甚至没有方法(Method)的概念。指令执行的序列可以重叠,并且并行的执行。后面,我会更加详细的论述这个问题。但是请记住,线程是有序的指令,而不是方法(method)。

线程的数据结构,与进程相反,仅仅只包括执行这些指令的信息。它包含当前的运行上下文(context):如寄存器(register)的内容、当前指令的在运行引擎的指令流中的位置、保存方法(methods)本地参数和变量的运行时堆栈。如果发生线程切换,OS只需把寄存器的值压进栈,然后把线程包含的数据结构放到某个类是列表(LIST)的地方;把另一个线程的数据从列表中取出,并且用栈里的值重新设置寄存器。切换线程更加有效率,时间单位是毫秒。对于Java而言,一个线程可以看作是JVM的一个状态。

运行时堆栈(也就是前面说的存储本地变量和参数的地方)是线程数据结构一部分。这是因为多个线程,每一个都有自己的运行时堆栈,也就是说存储在这里面的数据是绝对线程安全(后面将会详细解释这个概念)的。因为可以肯定一个线程是无法修改另一个线程的系统级的数据结构的。也可以这么说一个不访问堆内存的(只读写堆栈内存)方法,是线程安全的(Thread Safe)。

线程安全和同步

线程安全,是指一个方法(method)可以在多线程的环境下安全的有效的访问进程级的数据(这些数据是与其他线程共享的)。事实上,线程安全是个很难达到的目标。

线程安全的核心概念就是同步,它保证多个线程:

l         同时开始执行,并行运行

l         不同时访问相同的对象实例

l         不同时执行同一段代码

我将会在后面的章节,一一细诉这些问题。但现在还是让我们来看看同步的一种经典的

实现方法——信号量。信号量是任何可以让两个线程为了同步它们的操作而相互通信的对象。Java也是通过信号量来实现线程间通信的。

不要被微软的文档所暗示的信号量仅仅是Dijksta提出的计数型信号量所迷惑。信号量其实包含任何可以用来同步的对象。

如果没有synchronized关键字,就无法用JAVA实现信号量,但是仅仅只依靠它也不足够。我将会在后面为大家演示一种用Java实现的信号量。

同步的代价很高哟!

       同步(或者说信号量,随你喜欢啦)的一个很让人头痛的问题就是代价。考虑一下,下面的代码:

Listing 1.2:

import java.util.*;

import java.text.NumberFormat;

 

class Synch

{

       private static long[ ]              locking_time   = new long[100];

       private static long[ ]              not_locking_time = new long[100];

    private static final long       ITERATIONS = 10000000;

 

    synchronized long locking     (long a, long b){return a + b;}

    long              not_locking (long a, long b){return a + b;}

 

       private void test( int id )

       {

        long start = System.currentTimeMillis();

 

        for(long i = ITERATIONS; --i >= 0 ;)

              {     locking(i,i);

              }

 

        locking_time[id] = System.currentTimeMillis() - start;

        start                      = System.currentTimeMillis();

 

        for(long i = ITERATIONS; --i >= 0 ;)

        {     not_locking(i,i);

              }

 

              not_locking_time[id] = System.currentTimeMillis() - start;

       }

 

       static void print_results( int id )

       {

 

              NumberFormat compositor = NumberFormat.getInstance();

              compositor.setMaximumFractionDigits( 2 );

 

              double time_in_synchronization = locking_time[id] - not_locking_time[id];

 

        System.out.println( "Pass " + id + ": Time lost: "

                            + compositor.format( time_in_synchronization                         )

                            + " ms. "

                            + compositor.format( ((double)locking_time[id]/not_locking_time[id])*100.0 )

                            + "% increase"

                            );

       }

 

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

    {

              final Synch tester = new Synch();

              tester.test(0); print_results(0);

              tester.test(1); print_results(1);

              tester.test(2); print_results(2);

              tester.test(3); print_results(3);

              tester.test(4); print_results(4);

              tester.test(5); print_results(5);

              tester.test(6); print_results(6);

             

              final Object start_gate = new Object();

 

              Thread t1 = new Thread()

              {     public void run()

                     {     try{ synchronized(start_gate) {     start_gate.wait(); } }

                            catch( InterruptedException e ){}

 

                            tester.test(7);

                     }

              };

              Thread t2 = new Thread()

              {     public void run()

                     {     try{ synchronized(start_gate) {     start_gate.wait(); } }

                            catch( InterruptedException e ){}

 

                            tester.test(8);

                     }

              };

 

              Thread.currentThread().setPriority( Thread.MIN_PRIORITY );

 

              t1.start();

              t2.start();

 

              synchronized(start_gate){ start_gate.notifyAll(); }

 

              t1.join();

              t2.join();

 

              print_results( 7 );

              print_results( 8 );

    }

}

 

    这是一个简单的基准测试程序,她清楚的向大家揭示了同步的代价是多么的大。test(…)方法调用2个方法10000000次。其中一个是同步的,另一个则否。下面是在我的机器上输出的结果(CPU: P4 2.4G(B); Memory: 1GB; OS: windows 2000 server(sp3); JDK: Ver1.4.01 and HotSpot 1.4.01-b01:

C:/>java -verbose:gc Synch

Pass 0: Time lost: 251 ms. 727.5% increase

Pass 1: Time lost: 250 ms. 725% increase

Pass 2: Time lost: 251 ms. 602% increase

Pass 3: Time lost: 250 ms. 725% increase

Pass 4: Time lost: 261 ms. 752.5% increase

Pass 5: Time lost: 260 ms. 750% increase

Pass 6: Time lost: 261 ms. 752.5% increase

Pass 7: Time lost: 1,953 ms. 1,248.82% increase

Pass 8: Time lost: 3,475 ms. 8,787.5% increase

 

这里为了使HotSpot JVM充分的发挥其威力,test( )方法被多次反复调用。一旦这段程序被彻底优化以后,也就是大约在Pass 6时,同步的代价达到最大。Pass 7 Pass 8与前面的区别在于,我new了两个新的线程来并行执行test方法,两个线程竞争执行(后面是适当的地方,我会解释什么是“竞争”,如果你已经等不及了,买本大学的操作系统课本看看吧! J),这使结果更加接近真实。同步的代价是如此之高的,应该尽量避免无谓的同步代价。

现在是时候我们更深入的讨论一下同步的代价了。HotSpot JVM一般会使用一到两个方法来实现同步,这主要取决于是否存在线程的竞争。当没有竞争的时候,计算机的汇编指令顺序的执行,这些指令的执行是不被打断。指令试图测试一个比特(bit),然后设置各种二进制位来表示测试的结果,如果这个bit没有被设置,指令就设置它。这可以说是非常原始的信号量,因为当两个线程同步的企图设置一个bit的值时,只有一个线程可以成功,两个线程都会检查结果,看看是不是自己设成功了。

如果bit已经被设置(这里说的是有线程竞争的情况下),失败的JVM(线程)不得不离开操作系统的核心进程等待这个bit位被清零。这样来回的在系统核心中切换是非常耗时的。在NT系统下,需要600次机械指令循环来进入一次系统内核,这还仅仅是进入所耗费的时间还不包括做操作的时间。

 

    是不是觉得很无聊了,呵呵!今天似乎都是些不顶用的东西。但这是必须的,为了使你能够读懂后面的内容。下一篇,我将会谈到一些更有趣的话题,例如如何避免同步,如果大家不反对,我还想讲一些设计模式的东西。下回见!

JavaScript笔记整理——驯服线程和定时器

定时器提供了一种让一段代码在一定毫秒之后,再异步执行的能力。由于Js是单线程的(同一时间只能执行一处Js代码)。定时器提供了一种跳出这种限制的方法,以一种不太直观的方式来执行代码。需要注意的是,定时器...
  • u014695532
  • u014695532
  • 2016年06月24日 16:23
  • 621

读《驯服烂代码——在编程操练中悟道》

读《驯服烂代码——在编程操练中悟道》 第2章 按图索骥地编写代码 第4章 调试一下 第5章 用TDD重做编程操练题目 第6章 消除假数据所带来的重复代码 第8章 嗅出代码腐臭和新的测试点 第9章 测试...
  • q547550831
  • q547550831
  • 2016年06月26日 19:59
  • 2849

Java多线程知识小抄集(一)

本文主要整理博主遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1. in...
  • u013256816
  • u013256816
  • 2016年05月05日 18:23
  • 13529

Java实现二值化处理图像

由于需求,在做图像处理这块,大概也学习了小半年,本文利用Java通过设置一个阈值来读一图像进行二值化处理。 import java.awt.Color; import java.awt.image.B...
  • xiaoxun2802
  • xiaoxun2802
  • 2017年02月25日 09:31
  • 1374

京东一二面问题整理

京东一面: 自我介绍, Spring 生成bean (单列)线程安全性 答:Spring 生成的Bean 如果是单例模式 其中的变量不是线程安全的、可以用ThreadLocal来实现线程安全,或者使用...
  • baidu_34464794
  • baidu_34464794
  • 2017年04月17日 15:03
  • 1194

阿里二面准备(Java 研发)

感觉有机会进行二面(原谅我没来由的自信,~~),准备一下。参考了牛客网上 30 多个面经帖,这是目前我能找到的几乎所有的问题。私以为如果能全部掌握,基本就能收割 offer 了。时间有限的话,针对自己...
  • u012516166
  • u012516166
  • 2017年07月27日 13:16
  • 828

JAVA灰度化、二值化图片如此简单方便

JAVA灰度化、二值化图片如此简单方便 分类: java 算法2011-12-30 19:07 5589人阅读 评论(7) 收藏 举报 java图片灰度化图片二直化 ...
  • JJRFJYFJYFJDFJRUJDJD
  • JJRFJYFJYFJDFJRUJDJD
  • 2015年01月26日 14:17
  • 1749

JAVA简单二值化图像处理

package downloadimg; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.Buf...
  • Cytosine
  • Cytosine
  • 2017年02月06日 16:32
  • 367

java图像处理——图像读取,二值化转bitset

最近要处理一些新闻中的广告图片,其中比较多的是含二维码的图片。简单写了3种逻辑处理了下(同源不同内容,同图片;含完整二维码的图片;残缺二维码广告图片),基本达到了实际需求。有同样需求的可以参考下,言归...
  • u010910436
  • u010910436
  • 2016年07月28日 18:22
  • 928

Java线程(二)线程池

系统启动一个新线程成本很高,使用线程池能很好的提高性能。尤其当需要创建大量的生存周期很短的线程时,更应该考虑线程池。 线程池在系统启动时会创建大量空闲的线程,将一个Runnable对象或者Calla...
  • qq_28713311
  • qq_28713311
  • 2016年08月31日 10:16
  • 449
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:驯服Java线程(二)
举报原因:
原因补充:

(最多只允许输入30个字)