北疆行

国庆假期一家人回了趟新疆。上次回老家还是两年以前了,秋日的北疆还是那么的美,在此记录一下。

过了这个路口就到家了。

石河子

乡间小路,天永远那么蓝。

乡间小路

菜市场是最有生活气息的地方。

菜市场

菜市场

自家种的苹果熟了,好甜。

苹果熟了

又到盛夏时

盛夏,又到了西湖最美的季节。喜欢在周末的早晨来湖边锻炼。

北山路上望断桥。

西湖的荷花开了

站在白堤上望保俶塔。

西湖的荷花开了

玉古路植物园那一段。

玉古路

去西湖的路上。

龙井路

Java基础及进阶书籍推荐

以下是自己在学习Java过程中个人认为不错的一些书籍,在这里做以汇总,推荐给大家。不得不说,读书还是学习技术的捷径之一,相比一些网络博客,书本的内容会更加系统和权威,所以还是建议大家多读书,读好书。链接地址是对应的豆瓣读书地址。

Java基础

Java API Reference

《Head First Java》

《Java核心技术》

《Java编程思想》

《Java并发编程实战》

《深入Java虚拟机》

《深入分析Java Web技术内幕》

《Java设计模式》

 

代码规范

《Effective Java》

《Clean Code》

《重构——改善既有代码的设计

 

热门框架

《Spring揭秘》

《Spring In Action》

《Redis实战》

《Redis设计与实现》

《Netty权威指南》

《Lucene In Action》

 

系统架构

《大型技术网站架构——核心原理与案例分析》

《大型网站系统与Java中间件实践》

《分布式Java应用基础与实践》

《大规模分布式存储系统》

 

其它

《Java性能优化权威指南》

《TCP/IP详解》

《HTTP权威指南》

《Maven实战》

java并发编程——性能和扩展性

第一部分:对性能的思考

并发编程的最主要目的是提高程序的运行性能,线程可以使程序更加充分的利用系统的可用处理能力,从而提高系统的资源利用率。然而使用多线程时也会引入额外的开销,这些开销包括:线程之间的协调(加锁,内存同步等)、增加的的上下文切换、线程的创建和销毁和线程的调度等等。如果过度以及不恰当的使用线程,这些开销甚至会抵消由于提高吞吐量、计算能力带来的性能提升。一个设计糟糕的多线程程序其性能可能比相同功能的串行程序效率还低。

提升性能意味着用更少的资源做更多的事情。资源的范围很广,比如CPU,内存,网络带宽,IO,磁盘空间等等。当操作性能由于某种特定的资源而受到限制时,我们通常称为资源密集型操作,比如CPU密集型等等。

要想通过并发来获得良好的性能,需要努力做好两件事:要有效的利用现有的资源,以及新的处理资源出现时程序能够有效的利用新增的资源。从性能监视的视角来看,CPU需要尽可能保持忙碌状态(当然这不意味着把CPU用在一些无用的计算上)。

应用程序的性能可以通过多个指标来衡量,比如一些指标(服务时间,等待时间)用来衡量运行速度,另一些指标(生产量、吞吐量)来衡量处理能力。因此,“多快”、“多少”是性能优化的两个不同方向,它们相互独立,有时甚至是互相矛盾的。因此,在优化之前必须要先确保以下几点:

第一,首先使程序正确,然后再提高运行速度,避免不成熟的优化。有些同步加锁的方法看上去似乎可以被一些“聪明”的减少同步的方法所取代,但这往往会引发并发错误,并发错误是最难追踪和消除的,这无疑于填小坑,挖大坑。

第二,确定目标,更快的含义是什么?在什么条件下运行更快?是在高负载还是低负载?是大数据量还是小数据量?对目前的状况以及目标状况都要有清晰地认识;

第三,以测试为基准,不要猜测。

第二部分:Amdahl定律介绍

在有些问题中,可用资源越多,那么问题的解决速度越快,比如收割庄稼,人越多则收割越快,有些任务是串行的,即使增加资源也不能提高速度。程序也是一样的,它由一些串行的和并行的程序组合而成,Amdahl定律描述的是:在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程序中并行组件与串行组件所占的比重。用公式表示为:

Speedup <= 1/(F+(1-F)/N)

Speedup 表示最高加速比,F表示程序中必须被串行的部分,N表示程序处理器的数量。当N趋向于无穷大时,最高的加速比倾向于1/F。如果程序中的串行程序占比10%,那么最高的加速比接近10。

要想算出程序中串行执行的比例其实很困难。但是Amdahl定律的出现却给我们提供了一些优化的思路。比如锁分段。根据Amdahl定律,随着处理器数量的增加,锁分段的效率要比独占锁和锁分解的效率高,也更能充分利用多处理器的能力。ConcurrentHashMap之所以有较高的性能就是使用了锁分段技术。

第三部分:如何提高性能

我们知道,串行程序会降低程序的可伸缩性,而线程的上下文切换也会降低性能,而在锁上等待会同时导致以上两种问题。因此,减少锁的竞争可提高程序的伸缩性和性能。有两个因素将影响在锁上发生竞争的可能性:锁得请求频率和每次持有锁的时间。两者的乘积越小则锁的竞争越小。以下将介绍几种减少锁竞争的方法:

第一,减少锁持有的时间(快进快出)。缩小锁的范围,只在需要锁操作的程序上使用锁,将锁无关的操作移出同步代码块。

第二,降低锁的请求频率。减小锁的粒度,这可以通过锁分解和锁分段技术实现。

第三,使用带有协调机制的独占锁,这些机制允许更高的并发性。比如我们可以用现有的并发容器替换掉同步容器以及使用原子类和读写锁。因为java自带的并发容器已经实现了很好的协调机制,我们可以直接使用提高效率。

最后要说的是性能优化是一个持续的、无止境的过程。因此,在实际优化过程中要有明确的目标,在确保程序安全性和正确性的基础上循序渐进的去展开,避免盲目和过度优化。

java并发编程——锁机制

第一部分:synchronized和volatile

锁机制用来保护对象的一致性以及操作的原子性,是实现线程安全的重要手段。线程安全涉及到对象两个重要的状态:共享性和可变性。如果对象是不可变的、线程私有的那么它一定是线程安全的。所以说,只有在共享的、可变的对象上面进行操作时才需要加锁,以保障线程安全。volatile和synchronized是java 5.0之前最早协调对象共享的机制。下面我们将分别介绍他们:

#synchronized

synchronized用来形容方法或者代码块,是java提供的最早的锁机制,支持重入锁。关于synchronized的详细解析文章有很多,这里列举几个注意事项:

第一,使用synchronized关键字时一定要尽可能的缩小范围,尽可能的在方法块里需要锁的地方使用,而不是直接用来修饰整个方法。这需要我们对方法里面的操作进行分析,哪些需要加锁,哪些不需要加锁,只在需要锁的地方加锁,这样即可以提高程序的效率,同时开放调用方法也减少了线程安全的隐患。

第二,synchronized提供的锁机制是粗粒度的,当有线程访问当前对象的synchronized方法或代码块时,其他线程只能等待当前操作结束才可以访问。这听上去似乎存在一定的性能问题,但java 6.0以后synchronized在并发环境下性能得到了大幅提升,因此建议尽可能的使用synchronized,除非synchronized满足不了业务需求。而且synchronized使用时无需释放锁,而且JVM还提供了专门的优化支持,因此即使synchronized是古老的锁,但是它依然适用于绝大多数场景。

#volatile

volatile用来修饰变量保证其可见性。可见性是一种复杂的属性,volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,在读取volatile变量时返回的一定是最新写入的值。volatile不是线程安全的,他不能替代锁,它只在特定的场景下使用,使用时要非常小心。以下场景可以使用volatile:

第一,对变量的写入不依懒于变量当前的值,或者你能确保只有单个线程更新变量的值;

第二,该变量不会与其它状态变量一起纳入不变性条件中;

第三,在访问变量时不需要加锁。

第二部分:ReentrantLock

ReentrantLock是一个可重入的互斥锁,继承自Lock接口。如果说synchronized是隐式锁的话,那么ReentrentLock就是显式锁。锁的申请、使用、释放都必须显式的申明。Lock接口提供了以下方法:

public interface Lock{
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time,TimeUnit unit) throws InterruptedException;
    void unLock();
    Condition newCondition();
}

ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。那既然如此,为什么还要创建一个类似的锁机制呢?内置锁虽然好用,但是缺乏一些灵活性,而ReentrantLock则可以弥补这些不足。

第一,轮询锁与定时锁。tryLock方法实现了可定时的与可轮询的锁实现。与synchronized相比它有更完善的错误恢复机制。内置锁中死锁是一类严重的错误,只能重启程序。而ReentrantLock可以使用可定时或者轮询的锁,它会释放已获得的锁,然后再尝试获得所有的锁。在实现具有时间限制的操作时,定时锁也非常也用。如果操作在给定时间内不能给出结果那么就会使程序提前结束。

第二,可中断锁获取操作。lockInterruptibly方法能够在获得锁的同时保持对中断的响应。而且由于它包含在Lock中,因此无需创建其他类型的不可中断阻塞机制。

第三部分:ReadWriteLock

ReentrantReadWriteLock实现了一种标准的互斥读写锁,继承自ReadWriteLock。ReadWriteLock接口包含以下方法:

public interface ReadWriteLock{
    Lock readLock();
    Lock WriteLock();
}

ReentrantReadWriteLock我们可以这样理解:当执行读操作的时候可以多个线程并发访问;当执行写操作的时候,只可以同时被一个线程访问。所以它使用的场景是读操作多而写操作少的并发场景。此外,ReentrantReadWriteLock还可以设置是否为公平锁,是公平锁的话则可以按照排队的顺序获取锁,非公平锁的话则是随机获得。

第四部分:锁的公平性

上一节讲到了ReentrantReadWriteLock实现了公平锁和非公平锁两种模式。在公平锁模式下线程按照请求的顺序来获得锁,而非公平模式下则可以插队。我们的期望是所有的锁都是公平的,毕竟插队是一种不好的行为。但实际上非公平锁比公平锁有着更高的并发效率。假设线程A持有一个锁,并且线程B也请求这个锁,由于该锁被线程A占有,所以B线程挂起,当A使用结束时释放锁,此时唤醒B,B需要重新申请获得锁。如果同时线程C也请求这个锁,并且C很可能在B获得锁之前已经获得、使用并释放了锁。这样就实现了双赢,B线程获得的锁没有延迟同时线程C也得到了执行,这提高了程序的吞吐率。

总结:与内置锁相比,显式锁提供了一些扩展功能,在处理锁的不可用方面有着更高的灵活性,并且对队列有着更好的控制。但是显式锁无法替换synchronized,只有在synchronized无法满足需求时才会使用它。读写锁允许多个读线程并发的访问被保护对象,当访问以读取操作为主的数据结构时,能提高程序的伸缩性。

java并发编程——线程池及Executor接口介绍

第一部分:概述

早期的应用程序大多是单线程串行执行的,虽然程序的任务边界清晰有序,但是执行的效率却很低,尤其是执行花费时间较长的操作,会导致大量的等待和堆积。为了提高程序的执行效率和吞吐量,我们很自然的会想到多线程,即为每个任务都新建一个独立的线程,这样就极大地提高了程序的执行效率。但事实上多线程也会带来很多问题。比如大量的创建线程,这本身就会消耗很多的资源,尤其是内存,当创建的线程数量超过服务器能够承受的极限时,内存溢出是在所难免的;比如还有其他稳定性问题,以及多线程造成的程序调用和管理的混乱。所以,综上所述,我们需要一个介于两者之间的工具,既可以创建大量的线程来提高程序的并发性和吞吐量,同时又可以有序的管理这些线程,能够可控。而Executor接口和线程池技术就是在这种背景下应运而生的。下面分别将这两种常用的并发技术做以介绍和总结。

第二部分:Executor和ExecutorService接口介绍

java.util.coucurrent包下面为我们提供了丰富的并发工具,Executor和ExecutorService接口就是其中比较重要的两个。

#Executor接口介绍

public interface Executor{
    void execute(Runnable command);
}

以上是Executor接口的代码,可以看出这个借口非常简单,就只有一个execute()方法。但他却为强大的异步任务执行提供了基础,它支持不同类型的任务执行策略。Executor框架基于生产者消费者模型,它提供了一个标准的方法将任务的提交和任务的执行过程解耦,并用Runnable来表示任务。下面来看一个基于Executor的简单服务器实现:

public class ExecutorWebServer {
    private static final int LIMIT = 50;
    private static final Executor exe = Executors.newFixedThreadPool(LIMIT);

    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
    ServerSocket server = new ServerSocket(80);
    while(true){
        Socket socket = server.accept();
        Runnable task = new Runnable(){
            public void run(){
                doSomeThing(socket);
            }
        };
        exe.execute(task);
    }
    }
}

通过上面列子我们就很好的把任务的提交和任务的执行分开来,这就是Executor框架最大的优势。

#ExecutorService接口介绍

上面我们讲了如何创建一个Executor,但是并没有讲如何关闭它。既然Executor是为应用程序服务的,因而他们应该是可关闭的,并将关闭操作中受影响的任务状态反馈给应用程序。为了解决执行服务的生命周期问题,ExecutorService扩展了Executor接口,增加了管理生命周期的方法:

public interface ExecutorService Extends Executor{
    void shutdown();
    boolean isShutDown();
    boolean isTerminated();
    ……
}

ExecutorService的生命周期分为三种,运行,关闭和已终止。下面看这个简单的实例:

class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
   throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
   for (;;) {
     pool.execute(new Handler(serverSocket.accept()));
   }
     } catch (IOException ex) {
   pool.shutdown();
     }
   }
 }

 class Handler implements Runnable {
   private final Socket socket;
   Handler(Socket socket) { this.socket = socket; }
   public void run() {
     // read and service request on socket
   }
}

第三部分:线程池介绍及使用

#几种类型的线程池

可以通过Executors类的几个静态方法来创建线程池。包含以下几类线程池:
newFixedThreadPool创建固定长度的线程池。每提交一个任务时就新建一个线程,直到达到线程池的最大数量。

newCachedThreadPool创建一个可缓存的线程池,线程的数量不受限制,但是在以前线程可用时将重用他们。

newSingleThreadExecutor创建一个使用单个 worker 线程的 ,以无界队列方式来运行该线程。

newScheduleThreadPool创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。类似于一个定时器。

#设置线程池大小

线程池的理想大小取决于被提交任务的类型以及所部署系统的特性。在代码中最好不要固定线程池的大小,而要通过某种灵活机制来配置。线程池大小的配置只要避免“过大”和”过小“两种极端情况即可。线程池过大会导致大量的线程竞争CPU和内存,最终导致资源耗尽;线程池过小时又会导致资源浪费,所以设置一个合适的线程池大小非常重要。通常有一个简单的设置线程池大小的公式供我们参考使用:

N(Threads) = N(cpu) U(cpu) (1 + w/c)

N(cpu)代表cpu的个数,U(cpu)代表cpu利用率, w/c表示等待时间与计算时间的比值

#配置线程池

ThreadPoolExecutor为Executor和ExecutorService接口提供了基本实现。下面我们来看一下ThreadPoolExecutor类的构造函数:该类一共有四个构造函数,其中最基础的一个构造函数如下:

ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue)
corePoolSize表示的是核心池的大小,第二个参数表示线程池最大的线程数,第三个参数表示存活时间,第四个参数表示给定单元粒度的时间段,第五个参数表示的是工作队列。

#扩展线程池

ThreadPoolExecutor是可以扩展的,它提供了几个可以在子类中改写的方法:beforeExecutor,afterExecutor,terminated,这些方法可用于扩展ThreadPoolExecutor的行为。

java并发编程——并发容器和并发工具介绍

java.util.concurrent包下面为我们提供了丰富的类和接口供我们开发出支持高并发、线程安全的程序。下面将从三个方面对这些基础构建类做以介绍和总结。

同步容器类,介绍Vector,HashTable和Collections.SynchronizedXXX();

并发容器类,介绍ConcurrentHashMap,CopyOnWrite容器以及阻塞队列。

并发工具类,介绍CountLatch,FututeTask,Semaphore和CyclicBarrier。

第一部分:同步容器类

同步容器类包括:Vector和HashTable,这两个类是早期JDK的一部分。此外还包括了JDK1.2之后提供的同步封装器方法Collections.synchronizedXxx()。

#同步容器类的问题

我们都知道Vector是线程安全的容器,但当我们对Vector经行复合操作时往往会得到意想之外的结果。比如迭代操作:

for(int i=0;i<vector.size();i++){
    doSomething(vector.get(i));
}

例如两个线程A和B,A线程对容器进行迭代操作的时候B线程可能对容器进行了删除操作,这样就会导致A线程的迭代操作抛出IndexOutOfBoundsException。而这显然不是我们想得到的结果,所以,为了解决不可靠迭代的问题,我们只能牺牲伸缩性为容器加锁,代码如下:

​synchronized(vector){
    for(int i=0;i<vector.size();i++){
    doSomething(vector.get(i));
    }
}


加锁防止了迭代期间对容器的修改,但这同时也降低了容器的并发性。当容器很大时,其他线程要一直等待迭代结束,因此性能不高。

#迭代器与CoucurrentModificationException

上面的例子我们举了Vector这种“古老”的容器。但是实际上很多“现代”的容器类也并没有避免上面所提到的问题。比如:for-each循环和Iterator。当我们调用Iterator类的hasNext和next方法对容器进行迭代时,若其他线程并发修改该容器,那么此时容器表现出的行为是“及时失败”的,即抛出CoucurrentModificationException。因此,为了避免以上问题,我们又不得不为容器加锁,或者对容器进行“克隆”,在副本上进行操作,但这样做的性能显然是不高的。

#影藏的迭代器

这里要特别提到的是有些类的方法会隐藏的调用容器的迭代器,这往往是很容易被我们忽视的。看下面列子:

private final Set<Integer> set = new HashSet<Integer>();

public synchronized void add(Integer i){set.add(i);}

public void addMore(){
    for(int i=0;i<10;i++)
    add(i);
    System.out.println("this is set:"+set);
}

以上代码能看出对容器的迭代操作吗?乍看没有。但实际上最后一段“this is set:”+set的代码影藏的调用了set的toString方法,而toString方法又会影藏的调用容器的迭代方法,这样该类的addMore方法同样可能会抛出CoucurrentModificationException异常。更严重的问题是该类并不是线程安全的,我们可以使用SynchronizedSet来包装set类,对同步代码进行封装,这样就避免了问题。此外我们还需要特别注意:容器的equals、hashCode方法都会隐式的进行迭代操作,当一个容器作为另一个容器的健值或者元素时就会出现这种情况。同样,containsAll,removeAll等方法以及把容器当做参数的构造函数,都会进行迭代操作。所有这些间接迭代的操作都可能导致程序抛出CoucurrentModificationException。

第二部分:并发容器类

java.util.concurrent包下面为我们提供了丰富的高并发容器类。通过并发容器来替换同步容器,可以极大地提高伸缩性和降低风险。这些容器按照不同的用途分为以下几类:

#ConcurrentHashMap

Java 5.0增加的ConcurrentHashMap几乎是最常用的并发容器了。与·HashTable相比,ConcurrentHashMap不仅线程安全,而且还支持高并发、高吞吐。ConcurrentHashMap的底层实现使用了分段锁技术,而不是独占锁,它允许多个线程可以同时访问和修改容器,而不会抛出CoucurrentModificationException异常,极大地提高了效率。在这里要说明的是ConcurrentHashMap返回的迭代器是弱一致性的,而不是及时失败的。另外size、isEmpty等需要在整个容器上执行的方法其返回值也是弱一致性的,是近似值而非准确值。所以,在实际使用中要对此做权衡。与同步容器和加锁机制相比,ConcurrentHashMap优势明显,是我们优先考虑的容器。

#CopyOnWriteArrayList

CopyOnWrite容器用于替代同步的List,它提供了更好的并发性,并且在使用时不需要加锁或者拷贝容器。CopyOnWriteArrayList的主要应用就是迭代容器操作多而修改少的场景,迭代时也不会抛出CoucurrentModificationException异常。CopyOnWrite容器的底层实现是在迭代操作前都会复制一个底层数组,这保证了多线程下的并发性和一致性,但是当底层数组的数据量比较大的时候,就需要效率问题了。

#阻塞队列和生产者—消费者模型

Java 5.0之后新增加了Queue(队列)和BlockingQueue(阻塞队列)。Queue的底层实现其实就是一个LinkedList。队列是典型的FIFO先进先出的实现。阻塞队列提供了很多现成的方法可以满足我们实现生产者—消费者模型。

生产者—消费者模型简单理解就是一个缓冲容器,协调生产者和消费者之间的关系。生产者生产数据扔到容器里,消费者直接从容器里消费数据,大家不需要关心彼此,只需要和容器打交道,这样就实现了生产者和消费者的解耦。

队列分为有界队列和无界队列,无界队列会因为数据的累计造成内存溢出,使用时要小心。阻塞队列有很多种实现,最常用的有ArrayBlockingQueue和LinkedBlockingQueue。阻塞队列提供了阻塞的take和put方法,如果队列已满,那么put方法将等待队列有空间时在执行插入操作;如果队列为空,那么take方法将一直阻塞直到有元素可取。有界队列是一个强大的资源管理器,它能抑制产生过多的工作项,使程序更加健壮。

#其他并发容器类

除了以上较常用的并发容器外,Java还为我们提供了一些个性化的容器类以满足我们的需求。BlockingDeque,PriorityBlockingQueue,DelayQueue和SynchronousQueue。

Deque是一种双端队列,它支持在两端进行插入和删除元素,Deque继承自Queue。BlockingDeque就是支持阻塞的双端队列,常用的实现有LinkedBlockingDeque。双端队列最典型的应用是工作密取,每个消费者都有各自的双端队列,它适用于既是生产者又是消费者的场景。

PriorityBlockingQueue是一个可以按照优先级排序的阻塞队列,当你希望按照某种顺序来排序时非常有用。

DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。

SynchronousQueue实际上不能算作一个队列,他不提供元素的存储功能,它只是维护一组线程,这些线程只是 等待将元素加入或者移除队列。SynchronousQueue相当于扮演一个直接交付的角色,因此它的put和get方法是一直阻塞的,有更少的延迟。

第三部分:并发工具类

如果遇到以上并发容器类无法解决的问题,大多数情况下我们可以使用并发工具类来解决问题。所有的同步工具类都包含共同的属性:“他们封装了一些状态,这些状态将决定执行并发工具类的线程是继续执行还是等待,此外还提供了一些方法对状态进行操作,以及另外一些用于高效的等待并发工具类进入到预期状态。”并发工具类主要包括以下几类:

#CountDownLatch

中文翻译为“闭锁”,Java API上是这样描述的:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。我们可以这样理解:CountDownLatch就相当于一扇门,当N个人都到齐之后才可以打开这扇门。门上面有一个计数器,用于记录打开门还需要的人数,比如需要五人,初始化计数器就显示五人,来了两人以后,计数器就变成了三,当五个人都到齐之后计数器变为零,此时门就打开了,所有人都可以进入了。看以下示例,创建一定数量的线程,用它们并发的执行某个指定任务。

public class CountDownLatchTask {
    public long timeTask(int nThreads,final Runnable task) 
            throws InterruptedException{
        final CountDownLatch startTask = new CountDownLatch(1);
        final CountDownLatch endTask = new CountDownLatch(nThreads);

        for(int i=0;i<nThreads;i++){
            Thread t = new Thread(){
                public void run(){
                    try{
                        startTask.await();
                        try{
                            task.run();
                        }finally{
                            endTask.countDown();
                        }
                    }catch(InterruptedException e){

                    }
                }
            };
            t.start();
        }

        long start = System.currentTimeMillis();
        startTask.countDown();
        endTask.await();
        long end = System.currentTimeMillis();
        return end-start;
    }
}

#FutureTask

FutureTask表示一个可以取消的异步计算。FutureTask表示的计算是通过Callable实现的,Callable与Runnable的区别就是Callable可以返回值或者抛出异常。所以我们可以用它来表示一些时间较长的计算,这些计算可以在使用计算结果之前启动,从而减少等待的时间。

#Semaphore

信号量用来控制同时访问某个资源的数量,或者同时执行某个操作的数量。所以信号量的典型应用就是用来实现某种资源池,或者对容器施加边界。看下面代码实例,为一个Set设置边界:

public class SemaphoreSet<T> {
    private final Set<T> set;
    private final Semaphore sem;

    public SemaphoreSet(int bounds){
        set = Collections.synchronizedSet(new HashSet<T>());
        sem = new Semaphore(bounds);
    }

    public boolean add(T o) throws InterruptedException{
        sem.acquire();
        boolean isSuccess = false;
        try{
            isSuccess = set.add(o);
            return isSuccess;
        }finally{
            if(!isSuccess)
                sem.release();
        }
    }

    public boolean remove(T o) throws InterruptedException{
        sem.acquire();
        boolean isSuccess = false;
        try{
            isSuccess = set.remove(o);
            return isSuccess;
        }finally{
            if(!isSuccess)
                sem.release();
        }
    }
}

#CyclicBarrier

栅栏类似于闭锁。它们的区别是:闭锁是一次性对象,一旦达到终止状态就不能被重置,而栅栏类似于水坝,当达到某个水位线以后开闸放水,放水完毕后又可以被重置。闭锁与栅栏的另一个区别体现在:所有线程必须都达到栅栏位置,才能继续执行。闭锁等待事件,而栅栏等待线程。

综上所述,通过对同步容器、并发容器和并发工具的介绍,大家大致了解了每种容器的使用场景以及各自的局限。并发容器和并发工具的出现极大地提高了程序的吞吐量和并发性,是大型网站开发过程中的必不可少的工具。

写在前面

首先得mark一下,注册github三年多时间,今天是第一次使用github pages写东西。花了大半个下午的时间总算把它部署上去了,目前用起来还不错,比wordpress简单方便。最近一直在找个适合开发人员写东西的地方,可以记录下工作和生活中的成长和思考。一来方便以后归纳总结,二来可以和别人分享一些解决问题的方法,好记性不如烂笔头,多写写还是有好处。记得2010年购买了一个独立域名在wordpress上更新过大概两年时间的博客,有十几篇文章,后来域名和服务器到期没有及时续费,数据都被清空,然后就不了了之了。

我选择了基于Node.js的开源hexo框架搭建pages,很好用,开源的主题也很多,自己做一些简单的修改基本就可以用起来了。在这里给github,给所有开源作者32个赞。

Hello World!

Lucene学习资料汇总

以下是自己在学习Lucene过程中收藏的一些学习资料,内容质量都很高,对于学习Lucene的朋友来说无疑是雪中送炭,在这里列出地址与大家分享,同时也感谢原作者们的贡献。

1、Lucene学习总结之一:全文检索的基本原理(理解全文检索的经典之作):

http://forfuture1978.iteye.com/blog/546771

2、Lucene学习总结(强烈推荐,此系列一共33篇文章,原理讲解透彻,学习Lucene的不二之选):

http://forfuture1978.iteye.com/category/89151

3、车东大牛关于Lucene的博客文章:

http://www.chedong.com/tech/lucene.html

4、Lucene实例教程(质量不错的文章,一共4篇):

http://blog.csdn.net/ch656409110/article/category/1570275

5、扩展Lucene的索引文件存储(红薯写的,很实用):

http://www.oschina.net/question/12_4979

6、Lucene详解(概念解释清晰,可以当做API文档):

http://www.open-open.com/lib/view/open1331275900374.html

7、Lucene索引查看工具Luke:

http://www.oschina.net/p/luke

http://www.oschina.net/question/222929_90569

8、Lucene核心API在线文档(4.0.0版本):

http://lucene.apache.org/core/4_0_0/core/overview-summary.html

9、IKAnalyzer作者的开源访谈实录:

http://www.oschina.net/question/28_61577

10、Lucene4.0的新特征:

http://www.oschina.net/question/12_60240

11、开源中国全文检索设计思路:

http://www.oschina.net/question/12_71591

夜行

昨晚从文一路一路骑车30多公里一直到滨江。回来已经一点多,精疲力尽。人,长久的生活在一种状态中实在太可怕了,时间会消磨人的意志力和激情,直到有一天你对一切都失去了兴趣。生活,需要改变。也许偶尔的疯狂,会告诉你曾经的自己依然还在。吉庆的斋月已经到来,每个人心中都有一轮明月。让我们用坚忍和敬畏的心去赶走对未知的恐惧,迎接新的开始。
Ramadan Mubarak.