2013年9月 ’ 的文章存档

用Ant实现Java项目的自动构建和部署

Ant是一个Apache基金会下的跨平台的构件工具,它可以实现项目的自动构建和部署等功能。在本文中,主要让读者熟悉怎样将Ant应用到Java项目中,让它简化构建和部署操作。
阅读全文

Redis事件库源码分析

由于老大在新项目中使用redis的事件库代替了libevent,我也趁着机会读了一遍redis的事件库代码,第一次读到“优美,让人愉快”的代码,加之用xmind制作的类图非常帅,所以留文纪念。
阅读全文

20 个漂亮免费的卡通字体

1.ACME Secret Agent


阅读全文

Jetty 的工作原理以及与 Tomcat 的比较

Jetty 的基本架构

Jetty 目前的是一个比较被看好的 Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器,它有一个基本数据模型,这个数据模型就是 Handler,所有可以被扩展的组件都可以作为一个 Handler,添加到 Server 中,Jetty 就是帮你管理这些 Handler。
阅读全文

如何使用git

本文不是谈论git具体命令的技术文章。

我之前发了一条关于git中如何处理中文文件名的微博,引发了一些质疑。

微博内容是这样的:

Linux使用git的时候,如果添加的文件是中文名字,会显示为转义符,虽然不影响上传的结果,但是看着很不方便。这个时候可以使用 git config –global core.quotepath false 这个命令,来禁用对于大于0×80的字符进行转义的功能,这样就可以显示出中文文件名了。

阅读全文

一步一步掌握线程机制(六)-Atomic变量和Thread局部变量

前面我们已经讲过如何让对象具有Thread安全性,让它们能够在同一时间在两个或以上的Thread中使用。Thread的安全性在多线程设计中非常重要,因为race condition是非常难以重现和修正的,我们很难发现,更加难以改正,除非将这个代码的设计推翻来过。
阅读全文

一步一步掌握线程机制(五)-等待与通知机制

在之前我们关于停止Thread的讨论中,曾经使用过设定标记done的做法,一旦done设置为true,线程就会结束,一旦为false,线程就会永远运行下去。这样做法会消耗掉许多CPU循环,是一种对内存不友好的行为。

java中的对象不仅拥有锁,而且它们本身就可以通过调用相关方法使自己成为等待者和通知者。 阅读全文

一步一步掌握线程机制(四)-同步方法和同步块

在之前例子的基础上,我们增加新的功能:根据正确与不正确的响应来显示玩家的分数。

public class ScoreLabel extends JLabel implements CharacterListener {
    private volatile int score = 0;
    private int char2type = -1;
    private CharacterSource generator = null, typist = null;

    public ScoreLabel(CharacterSource generator, CharacterSource typist) {
        this.generator = generator;
        this.typist = typist;
        if (generator != null) {
            generator.addCharacterListener(this);
        }
        if (typist != null) {
            typist.addCharacterListener(this);
        }
    }

    public ScoreLabel() {
        this(null, null);
    }

    public synchronized void resetGenerator(CharacterSource newCharactor) {
        if (generator != null) {
            generator.removeCharacterListener(this);
        }
        generator = newCharactor;
        if (generator != null) {
            generator.addCharacterListener(this);
        }
    }

    public synchronized void resetTypist(CharacterSource newTypist) {
        if (typist != null) {
            typist.removeCharacterListener(this);
            typist = newTypist;
        }
        if (typist != null) {
            typist.addCharacterListener(this);
        }
    }

    public synchronized void resetScore() {
        score = 0;
        char2type = -1;
        setScore();
    }

    private synchronized void setScore() {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                setText(Integer.toString(score));
            }
        });
    }

    @Override
    public synchronized void newCharacter(CharacterEvent ce) {
        if (ce.source == generator) {
            if (char2type != -1) {
                score--;
                setScore();
            }
            char2type = ce.character;
        } else {
            if (char2type != ce.character) {
                score--;
            } else {
                score++;
                char2type = -1;
            }
            setScore();
        }
    }
}

这里我们将newCharacter()方法用synchronized进行同步,是因为这个方法会被多个线程调用,而我们根本就不知道哪个线程会在什么时候调用这个方法。这就是race condition。
变量的volatile无法解决上面的多线程调度问题,因为这里的问题是方法调度的问题,而且更加可怕的是,需要共享的变量不少,其中有些变量是作为条件判断,这就会导致在这些条件变量没有正确的设置前,有些线程已经开始启动了。

这并不是简单的将这些变量设置为volatile就能解决的问题,因为就算这些变量的状态不对,其他线程依然能够启动。
阅读全文

一步一步掌握线程机制(三)-synchronized和volatile的使用

现在开始进入线程编程中最重要的话题—数据同步,它是线程编程的核心,也是难点,就算我们理解了数据同步的基本原理,但是我们也无法保证能够写出正确的同步代码,但基本原理是必须掌握的。

要想理解数据同步的基本原理,首先就要明白,为什么我们要数据同步?

public class CharacterDisplayCanvas extends JComponent implements
        CharacterListener {
    protected FontMetrics fm;
    protected char[] tmpChar = new char[1];
    protected int fontHeight;

    public CharacterDisplayCanvas() {
        setFont(new Font("Monospaced", Font.BOLD, 18));
        fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
        fontHeight = fm.getHeight();
    }

    public CharacterDisplayCanvas(CharacterSource cs) {
        this();
        setCharacterSource(cs);
    }

    public void setCharacterSource(CharacterSource cs) {
        cs.addCharacterListener(this);
    }

    public synchronized void newCharacter(CharacterEvent ce) {
        tmpChar[0] = (char) ce.character;
        repaint();
    }

    public Dimension preferredSize() {
        return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
    }

    protected synchronized void paintComponent(Graphics gc) {
        Dimension d = getSize();
        gc.clearRect(0, 0, d.width, d.height);
        if (tmpChar[0] == 0) {
            return;
        }
        int charWidth = fm.charWidth((int) tmpChar[0]);
        gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
    }
}

仔细查看上面的代码,我们就会发现,有两个方法的前面多了一个新的关键字:synchronized。让我们看看这两个方法为什么要添加这个关键字。
newCharacter()用于显示新字母,而paintComponent()负责调整和重画canvas。这两个方法存在着race condition,也就是竞争,因为它们访问的是同一份数据,最重要的是它们是由不同的线程所调用的,这就导致我们无法保证它们的调用是按照正确的顺序来进行,可能在newCharacter()方法未被调用前paintComponent()方法就已经重新绘制canvas。

之所以产生竞争,除了这两个方法访问的是同一份数据之外,还和它们是非automic有关。我们在初中的时候都学过,原子曾经被认为是最小单元,不可分的,哪怕现在已经证明这是不正确的,但原子不可分的概念在计算机这里保留了下来。 一个程序如果被认为是automic,那么就表示它是无法被中断的,不会有中间状态。使用synchronized,就能保证该方法无法被中断,那么其他线程就无法在该方法没有完成前调用它。
阅读全文

一步一步掌握java的线程机制(二)-Thread的生命周期

之前讲到Thread的创建,那是Thread生命周期的第一步,其后就是通过start()方法来启动Thread,它会执行一些内部的管理工作然后调用Thread的run()方法,此时该Thread就是alive(活跃)的,而且我们还可以通过isAlive()方法来确定该线程是否启动还是终结。

一旦启动Thread后,我们就只能执行一个方法:run(),而run()方法就是负责执行Thread的任务,所以终结Thread的方法很简单,就是终结run()方法。仔细查看文档,我们会发现里面有一个方法:stop(),似乎可以用来停止Thread,但是这个方法已经被废除了,因为它存在着内部的竞争。

我们经常需要一个不断执行的Thread,然后在某个特定的条件下才会终结它,方法有很多,但最常用的有设定标记和中断Thread两种方式。

我们将之前例子中的Thread改写一下:

public class RandomCharacterGenerator extends Thread implements CharacterSource {
    static char[] chars;
    static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
    static {
        chars = charArray.toCharArray();
    }

    private volatile boolean done = false;

    Random random;
    CharacterEventHandler handler;

    public RandomCharacterGenerator() {
        random = new Random();
        handler = new CharacterEventHandler();
    }

    public int getPauseTime() {
        return (int) (Math.max(1000, 5000 * random.nextDouble()));
    }

    @Override
    public void addCharacterListener(CharacterListener cl) {
        handler.addCharacterListener(cl);
    }

    @Override
    public void removeCharacterListener(CharacterListener cl) {
        handler.removeCharacterListener(cl);
    }

    @Override
    public void nextCharacter() {
        handler.fireNewCharacter(this,
                (int) chars[random.nextInt(chars.length)]);
    }

    public void run() {
        while(!done){
            nextCharacter();
            try {
                Thread.sleep(getPauseTime());
            } catch (InterruptedException ie) {
                return;
            }
        }
    }

    public void setDone(){
        done = true;
    }
}

现在我们多了一个标记:done,这样我们就可以在代码中通过调用setDone()来决定什么时候停止该Thread。这里使用了volatile关键字,它主要是为了同步。这点会放在同步这里讲。
设定标记的最大问题就是我们必须等待标记的状态,这样就会造成延迟。当然,这种延迟是无法避免的,但必须想办法缩短到最小。于是,中断Thread这种方法就有它的发挥地方了。 阅读全文