Java ’ 标签下的文章存档

深入探索 Java 热部署

简介

在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作。对于某些大型的应用来说,每次的重启都需要花费大量的时间成本。虽然 osgi 架构的出现,让模块重启成为可能,但是如果模块之间有调用关系的话,这样的操作依然会让应用出现短暂的功能性休克。本文将探索如何在不破坏 Java 虚拟机现有行为的前提下,实现某个单一类的热部署,让系统无需重启就完成某个类的更新。 阅读全文

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

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

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

Jetty 的基本架构

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

一步一步掌握线程机制(六)-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这种方法就有它的发挥地方了。 阅读全文

一步一步掌握java的线程机制(一)-创建线程

现在将1年前写的有关线程的文章再重新看了一遍,发现过去的自己还是照本宣科,毕竟是刚学java的人,就想将java的精髓之一—线程进制掌握到手,还是有点难度。等到自己已经是编程一年级生了,还是无法将线程这个高级的概念完全贯通,所以,现在趁着自己还在校,尽量的掌握多点有关线程机制的知识。

我们以一个简单的例子开始下手:

public class SwingTypeTester extends JFrame implements CharacterSource{
    protected RandomCharacterGenerator producer;
    private CharacterDisplayCanvas displayCanvas;
    private CharacterDisplayCanvas feedbackCanvas;
    private JButton quitButton;
    private JButton startButton;
    private CharacterEventHandler handler;

    public SwingTypeTester() {
        initComponents();
    }

    private void initComponents() {
        handler = new CharacterEventHandler();
        displayCanvas = new CharacterDisplayCanvas();
        feedbackCanvas = new CharacterDisplayCanvas();
        quitButton = new JButton();
        startButton = new JButton();
        add(displayCanvas, BorderLayout.NORTH);
        add(feedbackCanvas, BorderLayout.CENTER);
        JPanel p = new JPanel();
        startButton.setLabel("Start");
        quitButton.setLabel("Quit");
        p.add(startButton);
        p.add(quitButton);

        add(p, BorderLayout.SOUTH);
        addWindowListener(new WindowAdapter(){
            public void windowClosing(WindowEvent evt){
                quit();
            }
        });

        feedbackCanvas.addKeyListener(new KeyAdapter(){
            public void keyPressed(KeyEvent ke){
                char c = ke.getKeyChar();
                if(c != KeyEvent.CHAR_UNDEFINED){
                    newCharacter((int)c);
                }
            }
        });

        startButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                 producer = new RandomCharacterGenerator();
                 displayCanvas.setCharacterSource(producer);
                 producer.start();
                 startButton.setEnabled(false);
                 feedbackCanvas.setEnabled(true);
                 feedbackCanvas.requestFocus();
            }
        });

        quitButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                 quit();
            }
        });
        pack();
    }

    private void quit(){
        System.exit(0);
    }

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

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

    public void newCharacter(int c){
        handler.fireNewCharacter(this,  c);
    }

    public void nextCharacter(){
        throw new IllegalStateException("We don't produce on demand");
    }

    public static void main(String[] args){
        new SwingTypeTester().show();
    }
}

这是一个java的Swing小例子,就是每隔一段时间就会显示一个随机的字母或者数字。具体的源码我会放在后面,现在只是对其中涉及到线程的部分进行重点讲解。 阅读全文

Java 实现 SSH 协议的客户端登录认证方式

简介: 本文首先对 SSH 协议的基础知识作以介绍,然后结合相关的 Java 代码逐步展开对登录认证方式的讨论。本文利于读者对 SSH 登录认证方式原理的理解,更有益于读者在实际项目中对 SSH 协议的应用。
背景

在开篇之前,让我们先对 SSH 协议有个宏观的大致了解,这样更有利于我们对本文的加深了解。首先要提到的就是计算机网络协议,所谓计算机网络协议,简单的说就是定义了一套标准和规则,使得不同计算机之间能够进行正常的网络通信,不至于出现在一台机器上发出的指令到另一台机器上成了不可认的乱码,SSH 就是众多协议的其中之一。经典的七层 OSI 模型(Open System Interconnection Reference Model)出现后,大大地解决了网络互联的兼容性问题,它将网络划分成服务、接口和协议三个部分,而协议就是说明本层的服务是如何实现的。SSH、Telnet 协议则主要被使用在用户层中(如图 1 深色部分所示),即应用层、表现层和会话层。

阅读全文