月度归档:2014 年三月

java线程重排序问题

以前在看《java并发编程实战》的时候,看到重排序的时候始终不能很好理解,做了很多demo也未能实现书中所述效果,见CSDN上的提问

按作者的意思是,当线程A运行的时候,在没有同步或者使用锁的情况下,其他线程看到的线程A内的代码运行顺序可能被打乱。即后面的代码可能会先运行。

代码:

</pre>
public class NoVisibility {
private static boolean ready;
private static int     number;

private static class ReaderThread extends Thread {
public void run() {
while (!ready) {
Thread.yield();
}
System.out.println(number);
}
}

public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
<pre>

ReaderThread线程可能会先看到主线程中ready=true。但是经过多次实验均未看到此效果。 今天偶然看到关于系统架构的文章,觉得可能问题出在系统的架构差异导致的。

从系统架构来看,目前的商用服务器大体可以分为三类,即对称多处理器结构 (SMP : Symmetric Multi-Processor) ,非一致存储访问结构 (NUMA : Non-Uniform Memory Access) ,以及海量并行处理结构 (MPP : Massive Parallel Processing)  本人DEMO运行环境为intel I3处理器. 在SMP系统架构中,所有的CPU共享全部资源,如总线,内存和I/O系统等,操作系统或管理数据库的复本只有一个,这种系统有一个最大的特点就是共享所有资源。多个CPU之间没有区别,平等地访问内存、外设、一个操作系统。操作系统管理着一个队列,每个处理器依次处理队列中的进程。此外SMP架构还存在内存屏障,保障指令的顺序执行。

本人运行环境是在intel I3处理器下运行的,估计此款CPU采用的就是SMP架构。

而在NUMA架构中,不同CPU之间具有独立的本地内存、 I/O 槽口等,CPU之间的通讯是通过互联模块 ( 如称为 Crossbar Switch) 进行连接和信息交互,因此每个 CPU 可以访问整个系统的内存 ( 这是 NUMA 系统与 MPP 系统的重要差别 ),这将导致在不同CPU通讯的时候,读取数据的CPU没有读到写CPU的实际数据。

另外除了CPU对指令的乱序执行外,编译器在编译的时候也可能会对代码进行重排序。CPU在乱序执行中对指令的执行顺序是不会打乱的,而只是可能不会等到前面的指令执行完在执行后面的指令,如同并发执行,相比于CPU的乱序,编译器的乱序才是真正对指令顺序做了调整。但是编译器的乱序也必须保证程序上下文的因果关系不发生改变。

本文只做笔记,关于重排序更深的理解还需对JVM内存模型,系统架构等做更深入研究

参考: SMP、NUMA、MPP体系结构介绍

中央处理器

为什么要指令重排序和屏障的作用

JAVA学习笔记–4.多线程编程 part1.背景知识和内存模型

 

 

hsqldb

hsqldb是纯java数据库,启动数据库只需引入hsqldb的jar包即可,非常方便,居家旅行必备,非常适合开发一些小型应用。

hsql运行模式有三种:

1,在内存中运行,数据不写入磁盘。

启用内存模式只需在connection的url设置为:jdbc:hsqldb:mem:dbname即可,如:

DriverManager.getConnection("jdbc:hsqldb:mem:mydb","sa","")

2, In-Process (Standalone)模式:

在此模式下数据库与应用运行在同一个jvm中,可以随应用一同启动,可以将数据写入磁盘。但是只能在一个进程中运行,不能通过其他工具同时连接。

启用此模式设置connection的url为:jdbc:hsqldb:file:dbaddr”,dbaddr为数据库的地址,在初次连接时,会自动建立数据库相关文件,默认登录用户名为sa。

 con =DriverManager.getConnection("jdbc:hsqldb:file:D:/hsqldb/testdb","sa","");

此模式数据写入时,也是在内存中,当一定时间周期后,或者在关闭应用时,调用“shutdown”命令,数据才会写入磁盘,如:

statement.executeUpdate("shutdown")

或者在connection的url后面添加“;shutdown=true”:

 con =DriverManager.getConnection("jdbc:hsqldb:file:D:/hsqldb/testdb;shutdown=true
","sa","");

另外可以设置 WRITE_DELAY参数来实时写入数据(默认是20):

 statement.execute("SET WRITE_DELAY 0 MILLIS");

在Standalone模式下,jvm会在每隔WRITE_DELAY设置的时间后将内存中的数据写入到硬盘文件一次。设置shutdown应该是在应用关闭时候将内存数据写入硬盘script的文件,如果未运行shutdown或者设置”shutdown=true”参数,而直接关闭jvm的情况下可能会导致数据最后写入硬盘后的数据不能及时写入文件。而设置WRITE_DELAY是将延迟写入时间设置为0,所以数据实时写入了,但是如果写入频繁的情况下,可能IO开销会很高。该结论是否正确,未做深入研究。
3,server模式,另起一个服务,可以通过其他工具链接。

附:hsqldb用户指南

定长线程池newFixedThreadPool

在多线程“生产者-消费者”模式中,为了避免线程陷入竞争而耗尽资源,通常可以通过Executors.newFixedThreadPool(num)限制线程的数量来解决问题,但是如果“生产者”数量持续大于“消费者”数量问题就容易产生了:

    public static void main(String[] args) throws InterruptedException {
        int num = 10000000;
        ExecutorService es = Executors.newFixedThreadPool(2);
        for (int i = 0; i < num; i++) {
            es.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }
                }
            });
        }
        es.shutdown();
        System.out.println("main end...");
    }

这里创建了一千万个线程,限制线程池大小为2;为了让”消费者”消费速度小于生产速度更明显,让创建的线程休息1秒。本人运行环境机器是4G内存,jvm堆空间默认大小,运行后抛出OutOfMemoryError,这是因为线程创建线程后,但是由于消费速度小于生产速度,线程池满后线程虽然没有立即执行,而是放在了等待队列中,当持续消费速度小于生产速度时,等待队列则持续增长,最终把堆空间撑爆了。

解决这个问题可以配合信号变量使用,使线程池满后,生产者暂停生产,陷入阻塞状态:

public static void main(String[] args) throws InterruptedException {
        int num = 10000000;
        int threadNum = 2;
        ExecutorService es = Executors.newFixedThreadPool(threadNum);
        final Semaphore semaphore = new Semaphore(threadNum);    //创建与线程池大小相当的信号变量
        for (int i = 0; i < num; i++) {
            semaphore.acquire(); //线程池满后主线程将暂时阻塞
            es.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }finally{
                        semaphore.release(); //线程运行完后信号释放,主线程恢复运行
                    }
                }
            });
        }
        es.shutdown();
        System.out.println("main end...");
    }

如此,堆空间将不再担心资源枯竭,只是主线程会受到阻塞,只需另起一个线程代替主线程即可。

收益计算

买房这个问题估计很多人都在纠结,到底买还是不买对于不是土豪的朋友估计都要困扰很久。

下面就拿南昌房价来说,目前据我所知,南昌一手房均价在9K左右,买个111平的房子价格约一百万;按照最低首付30%的30年期商业贷款计算,首付在30万,其中不包括其他杂七杂八的费用,如果是等额本息还款,按目前首套房6.55%的利率计算,每月还款在4447元左右(不含物业管理费),总还款额约为1,901,106元。

假设不买房,将所有的钱拿去做基本理财投资,很多P2P理财产品基本都能维持在6%以上的年收益,而且是循环计算收益的。

下面我写了一个计算受益的脚本:收益计算,可通过打开此链接来进行收益的计算。

回到正题,假如说按6%的年化收益进行计算,把买房的首付30万投资,30年后可计算得到本息为1,723,047元左右;把每月还款4447元每月定期投资,30年后得到的本息约为4,354,608元。30年后总收入约为1,723,047+4,354,608=6,077,655元。变成六百多万了!30年后南昌的房子能升到6万每平米吗,这很难说,但是如果投资6%的受益,能拿到600万了,我想只要会简单投资理财6%的年受益应该不是很困难,况且定期存银行都有3%。

或许很多人会想,每个月还房贷自己能住里边,那如果自己租房子,在南昌租个1000元的房子应该也还算过得去了。我们就按每月就4447-1000=3447来计算,30年后能得到3,375,384元,加上30万的本息1,723,047,总共是3,375,384+1,723,047=5,098,431,也有五百来万了,南昌房价30年后能否有五万每平米也是个未知数啊,当然可能是有也肯能没有(废话了)。

以上仅供参考,献给当今的我们80后这一代年轻的屌丝小伙伴们,不买房的话就如上面房子的面积111,光棍一条啊;买房的话就如上面房子的月供4447,那是死一条,唉。。。多么痛的领悟!

本文纯客观评价,无任何主观因素,仅供参考而已···

附:收益计算