iyihua

  • 首页

  • 标签

  • 分类

  • 归档

Golang Tour学习记录

发表于 2018-12-13 | 更新于 2019-05-22 | 分类于 Go

Go Tour: Golang tour学习中的一些记录,帮组理解和记忆

  • 设置GOPATH,可以改变go工作空间

  • 工作空间中需要一个src目录,需要一个bin目录

  • 导入package时,使用pkg的时候,只需要导入路径中的最后一段即可引用该包

  • go build or go install,注意目录

  • package中大写的定义是对外开放的,小写的则外面不可用

  • 类型:string boolean int(int int8 int32 int 64) uint(…) complex(complex64 complex 128)

  • if else, for, switch, 没有while,while使用for代替

  • = 是赋值, := 声明并赋值

  • structs, array, slice, map, pointer

  • Function, Method

func 可以作为参数,有func闭包

Method则是定义于struct上的方法,如果是定义于struct值上的方法,不会改变struct值本身;如果是定义于struct指针上的方法,可以改变struct的值。

给struct定方法有2种方式,一种是把struct作为方法的receiver,另一种是把struct作为方法的参数。

两者有一个重要的差别:

functions with a pointer argument must take a pointer:

1
2
3
var v Vertex
ScaleFunc(v, 5) // Compile error!
ScaleFunc(&v, 5) // OK

while methods with pointer receivers take either a value or a pointer as the receiver when they are called:

1
2
3
4
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK

当一个函数有一个指针参数,那么传入的参数必须是指针;
而当一个函数有一个指针的receiver,那么调用这个方法时,用指针或者值都是可以的。

  • interface

type只要实现了interface的方法,即为interface的实现,无需显式的定义。

在底层interface values可以被看做是含value和type的一个元祖:

1
(value, type)

an interface value that holds a nil concrete value is itself non-nil.
就是没有空指针问题

  • Errors

function call的时候,就看err这个返回值,如果是nil,意味着成功,非空意味着执行报错了。

  • Reader

Reader可以嵌套

  • Image

  • Goroutines

  • Channels

Channels are a typed conduit through which you can send and receive values with the channel operator, <-.

1
2
3
4
ch <- v    // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.
(The data flows in the direction of the arrow.)

Like maps and slices, channels must be created before use:

1
ch := make(chan int)

  • Range and Close

channel能Range,也能close,只有sender能close channel,receiver不能。
一般不需要close channel,除非你需要显式的通知receiver channel已关闭。

  • Select

它会监听case中的channel,当channel发生变时,再触发一次选择

  • sync.Mutex

互斥锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mux.Unlock()
return c.v[key]
}

git-git常用操作命令记录

发表于 2018-10-08 | 更新于 2019-05-22 | 分类于 git

一些日常使用的git命令记录

  • 强制更新远程仓库的更新到本地

    1
    git fetch --all && git reset --hard origin/master && git pull
  • 创建、检出和删除分支

创建并切换分支

1
git branch -b dev

检出分支

1
git checkout dev

删除分支

1
git branch -d dev

  • 合并dev分支到master
    1
    2
    git checkout master
    git merge dev

[git]如何把本地仓库中使用https连接方式的库改为使用ssh

发表于 2018-10-08 | 更新于 2019-05-22 | 分类于 git

有时候,我们在检出github或者gitlab的repository时,原先是使用https的方式检出的。现在想把原先这个使用https方式检出的库改为使用ssh连接,要如何做呢?

很简单,使用一条命令即可:

1
2

git remote set-url origin git@github.com:username/repo-name-here.git

[git]如何在一台电脑设置使用多个git的SshKey

发表于 2018-10-08 | 更新于 2019-05-22 | 分类于 git

有时候,我们在同一台机器,会需要检出连接多个git仓库的代码,比如个人的代码库github和公司的代码库gitlab,如果都想生成ssh key来免除输入用户名密码的繁琐,那么就需要针对github和gitlab都生成ssh key。

但是,这两者如果邮箱不同的话,在生成第二个key的时候会覆盖第一个的key,会导致一个用不了。如何解决这个问题呢?

  1. 首先,在使用命令生成ssh key的时候,使用命令
    1
    $ ssh-keygen -t rsa -C "your_email@example.com"

的时候,在第一步要求输入key存放地址的时候,不能默认回车,您需要输入一个不同的存放地址,例如

1
/c/Users/xxx/.ssh/id_rsa_github

后面的一直回车即可,就可以看到在.ssh目录生成了2个新文件:

1
2
id_rsa_github.pub
id_rsa_github

另外一个同理,只要名称路径不一样即可。

  1. 在.ssh目录添加一个文件名称为’config’的文件

编辑该文件,把github和gitlab的对应配置分别写进去,例如:

1
2
3
4
5
6
7
8
9
Host github.com
HostName github.com
User youusername
IdentityFile ~/.ssh/id_rsa_github

Host code.aliyun.com
HostName code.aliyun.com
User youusername
IdentityFile ~/.ssh/id_rsa

我的Emacs-spacemacs使用配置记录

发表于 2018-09-13 | 更新于 2019-05-22 | 分类于 emacs

此文章为我的Emacs/spacemacs配置和使用记录。持续更新中…

其中记录分为2类,如果是spacemacs专用配置的,会记录于spacemacs类目下,否则默认是记录于Emacs类别下。

1. emacs

1.1 日常高频操作

  • 打开一个新的系统窗口

    1
    M-x make-frame <Return>
  • 指定一个文件出现在下方的窗格中,同时光标也跳到了那里

输入 C-x 4 C-f,紧跟着输入一个文件名,再用 结束。

  • 将光标转移到其他的窗格。

输入 C-x o(“o”指的是“其它(other)”),

  • 滚动下方的窗格。

    1
    C-M-v
  • 复制/剪切和粘贴

    1
    2
    3
    C-w 剪切
    M-w 复制
    C-y 粘贴
  • 搜索和替换

C-s 向后搜索,然后输入需要搜索的文字
M-% 查找和替换,输入M-%后,先输入要被替换的文字,Ret,再输入要替换的新文字

  • 复制一行

    1
    2
    3
    4
    Ctrl-a 光标到行首
    Ctrl-Shift-Space 设置标记
    Ctrl-e 光标到行尾。如此这一行就被选为激活的区域了
    Alt-w 复制当前激活的区域
  • 修改配置文件后,重新加载配置

C-x C-e ;; current line
M-x eval-region ;; region
M-x eval-buffer ;; whole buffer
M-x load-file ~/.emacs.d/init.el

1.2 常用配置

  • 定义快捷键快速打开配置文件
    (defun open-my-init-file()
    (interactive)
    (find-file”C:/Users/iyihua/AppData/Roaming/.emacs”))
    (global-set-key(kbd ““) ‘open-my-init-file)

  • 设置在emacs中打开git bash
    ;;set git base as emacs shell
    (prefer-coding-system ‘utf-8)
    (defun iyihua/bash()
    (interactive)
    (let ((explicit-shell-file-name “D:/tools/git/bin/bash”))
    (call-interactively ‘shell)))
    (prefer-coding-system ‘utf-8)
    (defun iyihua/git-bash()
    (interactive)
    (let ((explicit-shell-file-name “D:/tools/git/git-bash”))
    (call-interactively ‘shell)))

  • 添加packages源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
https://mirror.tuna.tsinghua.edu.cn/help/elpa/

Installing
To use the MELPA repository, you'll need an Emacs with package.el. Emacs 24 has package.el bundled with it, and there's also a version you can use with Emacs 23.

Enable installation of packages from MELPA by adding an entry to package-archives after (require 'package) and before the call to package-initialize in your init.el or .emacs file:

(require 'package)
(let* ((no-ssl (and (memq system-type '(windows-nt ms-dos))
(not (gnutls-available-p))))
(proto (if no-ssl "http" "https")))
;; Comment/uncomment these two lines to enable/disable MELPA and MELPA Stable as desired
(add-to-list 'package-archives (cons "melpa" (concat proto "://melpa.org/packages/")) t)
;;(add-to-list 'package-archives (cons "melpa-stable" (concat proto "://stable.melpa.org/packages/")) t)
(when (< emacs-major-version 24)
;; For important compatibility libraries like cl-lib
(add-to-list 'package-archives '("gnu" . (concat proto "://elpa.gnu.org/packages/")))))
(package-initialize)
To use the stable package repository instead of the default “bleeding-edge” repository, use this instead of "melpa":

(add-to-list 'package-archives
'("melpa-stable" . "https://stable.melpa.org/packages/") t)
  • 设置自动保存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ;;; packages.el start
    (require 'auto-save)
    (defconst iyihua-packages
    '(auto-save)
    )

    (defun iyihua/init-auto-save()
    (use-package auto-save
    :init
    (auto-save-enable)
    (setq auto-save-slient t)))

    ;;; packages.el ends here
  • 安装主题

    1
    2
    3
    4
    5
    6
    7
    Install manually
    Add the emacs theme files to ~/.emacs.d/themes.

    To load a theme add the following to your init.el

    (add-to-list 'custom-theme-load-path "~/.emacs.d/themes")
    (load-theme 'dracula t)
  • 设置默认字体

前提是系统已下载安装对应字体

1
2
(custom-set-faces
'(default ((t (:family "Source Code Pro" :foundry "outline" :slant normal :weight normal :height 98 :width normal)))))
  • 显示隐藏menu bar
1
2
3
;;show/hide menu bar
(menu-bar-mode -1)
(global-set-key [f9] 'toggle-menu-bar-mode-from-frame)
  • 解决在windows下在有中文的时候特别卡顿的问题
1
2
3
4
5
6
7

(when
(eq system-type 'windows-nt)
(setq gc-cons-threshold (* 512 1024 1024))
(setq gc-cons-percentage 0.5)
(run-with-idle-timer 5 t #'garbage-collect)
(setq garbage-collection-messages t))

2. spacemacs

java并发编程的15个关键知识点和面试点

发表于 2018-09-10 | 更新于 2019-05-22 | 分类于 java

<java并发编程关键知识点/面试点>

15个常见的面试问题和关键点,我列举了出来,并且就每个问题罗列出来分析解答。其中不少资料是来自英文资料,内容非常的丰富详细,其中每一点都能单独成长文,适合慢慢看。

1. 如何保证多线程的顺序执行(T2在T1后执行,T3在T2后执行)?

  • 可以用 Thread 类的 join 方法实现这一效果。

2. Lock接口,相对于同步代码块(synchronized block)有什么优势?

  • 多线程和并发编程中使用 lock 接口的最大优势是它为读和写提供两个单独的锁,可以让你构建高性能数据结构,比如 ConcurrentHashMap 和条件阻塞。

2.1 如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。

  • 由于Lock具有写锁和读锁,读锁可以实现不互斥的读操作,比同步代码块具有更好的并发性能,所以可以用Lock实现高性能缓存。
  • readLock(),支持可重入读,可以共享读锁,支持多线程的并发读取;
  • writeLock(),互斥的写入,单一写入。
  • 代码例如如下(思路:java.util.concurrent.locks包下面ReadWriteLock接口,该接口下面的实现类ReentrantReadWriteLock维护了两个锁读锁和解锁,可用该类实现这个功能):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    import java.util.Date;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;

    /**
    * 你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
    * @author user
    *
    */
    public class Test2 {

    public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
    new Thread(new Runnable() {

    @Override
    public void run() {
    MyData.read();
    }
    }).start();
    }
    for (int i = 0; i < 3; i++) {
    new Thread(new Runnable() {

    @Override
    public void run() {
    MyData.write("a");
    }
    }).start();
    }
    }
    }

    class MyData{
    //数据
    private static String data = "0";
    //读写锁
    private static ReadWriteLock rw = new ReentrantReadWriteLock();
    //读数据
    public static void read(){
    rw.readLock().lock();
    System.out.println(Thread.currentThread()+"读取一次数据:"+data+"时间:"+new Date());
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    rw.readLock().unlock();
    }
    }
    //写数据
    public static void write(String data){
    rw.writeLock().lock();
    System.out.println(Thread.currentThread()+"对数据进行修改一次:"+data+"时间:"+new Date());
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    rw.writeLock().unlock();
    }
    }
    }

3. Java 中 wait 和 sleep 方法有什么区别?

  • (扩展阅读:http://www.java67.com/2015/12/producer-consumer-solution-using-blocking-queue-java.html)
  • 两者主要的区别就是等待释放锁和监视器。sleep方法在等待时不会释放任何锁或监视器。wait 方法多用于线程间通信,而 sleep 只是在执行时暂停。
  • wait可以被notify/notifyAll唤醒,而sleep则不能。
  • wait is called from synchronized context only while sleep can be called without synchronized block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
     In order to call the wait (), notify () or notifyAll () methods in Java, we must have obtained the lock for the object on which we're calling the method.

    we call wait (), notify () or notifyAll method in Java from synchronized method or synchronized block in Java to avoid:

    1) IllegalMonitorStateException in Java which will occur if we don't call wait (), notify () or notifyAll () method from synchronized context.

    2) Any potential race condition between wait and notify method in Java.

    Read more: https://javarevisited.blogspot.com/2011/05/wait-notify-and-notifyall-in-java.html#ixzz5Q61F8eI1
  • The wait() method is called on an Object on which the synchronized block is locked, while sleep is called on the Thread.

4. 如何在 Java 中实现一个阻塞队列?

1.wait()和notify()方式

阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列。

线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素。

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BlockingQueue {

private List queue = new LinkedList();
private int limit = 10;

public BlockingQueue(int limit){
this.limit = limit;
}


public synchronized void enqueue(Object item)
throws InterruptedException {
while(this.queue.size() == this.limit) {
wait();
}
if(this.queue.size() == 0) {
notifyAll();
}
this.queue.add(item);
}


public synchronized Object dequeue()
throws InterruptedException{
while(this.queue.size() == 0){
wait();
}
if(this.queue.size() == this.limit){
notifyAll();
}

return this.queue.remove(0);
}

}

必须注意到,在enqueue和dequeue方法内部,只有队列的大小等于上限(limit)或者下限(0)时,才调用notifyAll方法。如果队列的大小既不等于上限,也不等于下限,任何线程调用enqueue或者dequeue方法时,都不会阻塞,都能够正常的往队列中添加或者移除元素。

2.并发类实现。eg:使用ArrayBlockingQueue实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class BlockingQueueTest {

public static void main(String[] args) {
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3); //缓冲区允许放3个数据

for(int i = 0; i < 2; i ++) {
new Thread() { //开启两个线程不停的往缓冲区存数据

@Override
public void run() {
while(true) {
try {
Thread.sleep((long) (Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "准备放数据"
+ (queue.size() == 3?"..队列已满,正在等待":"..."));
queue.put(1);
System.out.println(Thread.currentThread().getName() + "存入数据,"
+ "队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}.start();
}

new Thread() { //开启一个线程不停的从缓冲区取数据

@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "准备取数据"
+ (queue.size() == 0?"..队列已空,正在等待":"..."));
queue.take();
System.out.println(Thread.currentThread().getName() + "取出数据,"
+ "队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}

}

5. 如何在 Java 中编写代码解决生产者消费者问题?

5.1 用 Java 中 BlockingQueue 的解决方案(由于代码简单,并且不需要处理同步代码块,这个方案是最推荐的方式)

  • why BlockingQueue? blocking queue data structure not only provides storage but also provides flow control and thread-safety, which makes the code really simple.

  • Producer-Consumer Example in Java using BlockingQueue

Here is our sample Java program to solve the classical producer consumer problem using BlockingQueue in Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
* Producer Consumer Problem solution using BlockingQueue in Java.
* BlockingQueue not only provide a data structure to store data
* but also gives you flow control, require for inter thread communication.
*
* @author Javin Paul
*/
public class ProducerConsumerSolution {

public static void main(String[] args) {
BlockingQueue<Integer> sharedQ = new LinkedBlockingQueue<Integer>();

Producer p = new Producer(sharedQ);
Consumer c = new Consumer(sharedQ);

p.start();
c.start();
}
}

class Producer extends Thread {
private BlockingQueue<Integer> sharedQueue;

public Producer(BlockingQueue<Integer> aQueue) {
super("PRODUCER");
this.sharedQueue = aQueue;
}

public void run() {
// no synchronization needed
for (int i = 0; i < 10; i++) {
try {
System.out.println(getName() + " produced " + i);
sharedQueue.put(i);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}
}

class Consumer extends Thread {
private BlockingQueue<Integer> sharedQueue;

public Consumer(BlockingQueue<Integer> aQueue) {
super("CONSUMER");
this.sharedQueue = aQueue;
}

public void run() {
try {
while (true) {
Integer item = sharedQueue.take();
System.out.println(getName() + " consumed " + item);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Output
PRODUCER produced 0
CONSUMER consumed 0
PRODUCER produced 1
CONSUMER consumed 1
PRODUCER produced 2
CONSUMER consumed 2
PRODUCER produced 3
CONSUMER consumed 3
PRODUCER produced 4
CONSUMER consumed 4
PRODUCER produced 5
CONSUMER consumed 5
PRODUCER produced 6
CONSUMER consumed 6
PRODUCER produced 7
CONSUMER consumed 7
PRODUCER produced 8
CONSUMER consumed 8
PRODUCER produced 9
CONSUMER consumed 9

Read more: http://www.java67.com/2015/12/producer-consumer-solution-using-blocking-queue-java.html#ixzz5Q65io9os

5.2 用wait和nofity的方式

  • Java program to solve Producer Consumer Problem in Java

How to solve Producer Consumer Problem in Java with ExampleHere is complete Java program to solve producer consumer problem in Java programming language. In this program we have used wait and notify method from java.lang.Object class instead of using BlockingQueue for flow control.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Java program to solve Producer Consumer problem using wait and notify
* method in Java. Producer Consumer is also a popular concurrency design pattern.
*
* @author Javin Paul
*/
public class ProducerConsumerSolution {

public static void main(String args[]) {
Vector sharedQueue = new Vector();
int size = 4;
Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
prodThread.start();
consThread.start();
}
}

class Producer implements Runnable {

private final Vector sharedQueue;
private final int SIZE;

public Producer(Vector sharedQueue, int size) {
this.sharedQueue = sharedQueue;
this.SIZE = size;
}

@Override
public void run() {
for (int i = 0; i < 7; i++) {
System.out.println("Produced: " + i);
try {
produce(i);
} catch (InterruptedException ex) {
Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
}

}
}

private void produce(int i) throws InterruptedException {

//wait if queue is full
while (sharedQueue.size() == SIZE) {
synchronized (sharedQueue) {
System.out.println("Queue is full " + Thread.currentThread().getName()
+ " is waiting , size: " + sharedQueue.size());

sharedQueue.wait();
}
}

//producing element and notify consumers
synchronized (sharedQueue) {
sharedQueue.add(i);
sharedQueue.notifyAll();
}
}
}

class Consumer implements Runnable {

private final Vector sharedQueue;
private final int SIZE;

public Consumer(Vector sharedQueue, int size) {
this.sharedQueue = sharedQueue;
this.SIZE = size;
}

@Override
public void run() {
while (true) {
try {
System.out.println("Consumed: " + consume());
Thread.sleep(50);
} catch (InterruptedException ex) {
Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
}

}
}

private int consume() throws InterruptedException {
//wait if queue is empty
while (sharedQueue.isEmpty()) {
synchronized (sharedQueue) {
System.out.println("Queue is empty " + Thread.currentThread().getName()
+ " is waiting , size: " + sharedQueue.size());

sharedQueue.wait();
}
}

//Otherwise consume element and notify waiting producer
synchronized (sharedQueue) {
sharedQueue.notifyAll();
return (Integer) sharedQueue.remove(0);
}
}
}

Output:
Produced: 0
Queue is empty Consumer is waiting , size: 0
Produced: 1
Consumed: 0
Produced: 2
Produced: 3
Produced: 4
Produced: 5
Queue is full Producer is waiting , size: 4
Consumed: 1
Produced: 6
Queue is full Producer is waiting , size: 4
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Queue is empty Consumer is waiting , size: 0

Read more: http://www.java67.com/2012/12/producer-consumer-problem-with-wait-and-notify-example.html#ixzz5Q68twJon

5.3 Counting Semaphore Example in Java (Binary Semaphore)

Java 5 Semaphore Example codea Counting semaphore with one permit is known as binary semaphore because it has only two state permit available or permit unavailable. Binary semaphore can be used to implement mutual exclusion or critical section where only one thread is allowed to execute. Thread will wait on acquire() until Thread inside critical section release permit by calling release() on semaphore.

here is a simple example of counting semaphore in Java where we are using binary semaphore to provide mutual exclusive access on critical section of code in java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.concurrent.Semaphore;

public class SemaphoreTest {

Semaphore binary = new Semaphore(1);

public static void main(String args[]) {
final SemaphoreTest test = new SemaphoreTest();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();

new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();

}

private void mutualExclusion() {
try {
binary.acquire();

//mutual exclusive region
System.out.println(Thread.currentThread().getName() + " inside mutual exclusive region");
Thread.sleep(1000);

} catch (InterruptedException i.e.) {
ie.printStackTrace();
} finally {
binary.release();
System.out.println(Thread.currentThread().getName() + " outside of mutual exclusive region");
}
}

}

Output:
Thread-0 inside mutual exclusive region
Thread-0 outside of mutual exclusive region
Thread-1 inside mutual exclusive region
Thread-1 outside of mutual exclusive region

Read more: https://javarevisited.blogspot.com/2012/05/counting-semaphore-example-in-java-5.html#ixzz5Q6BDechd

6. 写一段死锁代码。你在 Java 中如何解决死锁?

6.1 Write a Java program which will result in deadlock?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Java program to create a deadlock by imposing circular wait.
*
* @author WINDOWS 8
*
*/
public class DeadLockDemo {

/*
* This method request two locks, first String and then Integer
*/
public void method1() {
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");

synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");
}
}
}

/*
* This method also requests same two lock but in exactly
* Opposite order i.e. first Integer and then String.
* This creates potential deadlock, if one thread holds String lock
* and other holds Integer lock and they wait for each other, forever.
*/
public void method2() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");

synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
}

6.2 How to avoid deadlock in Java?

If you have looked above code carefully then you may have figured out that real reason for deadlock is not multiple threads but the way they are requesting a lock, if you provide an ordered access then the problem will be resolved.
死锁的原因不是多线程,而是请求锁的方式不对。如果为锁的请求提供了顺序的访问,那么问题就解决了。

fixed version, which avoids deadlock by avoiding circular wait with no preemption, one of the four conditions which need for deadlock.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DeadLockFixed {

/**
* Both method are now requesting lock in same order, first Integer and then String.
* You could have also done reverse e.g. first String and then Integer,
* both will solve the problem, as long as both method are requesting lock
* in consistent order.
*/
public void method1() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");

synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}

public void method2() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");

synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
}

Read more: https://javarevisited.blogspot.com/2018/08/how-to-avoid-deadlock-in-java-threads.html#ixzz5Q6H9gcNy

7. 什么是原子操作?Java 中有哪些原子操作?

  • 什么是原子操作?
    所谓原子操作,就是”不可中断的一个或一系列操作” , 在确认一个操作是原子的情况下,多线程环境里面,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁,甚至借助于原子操作,我们可以实现互斥锁。 很多操作系统都为int类型提供了+-赋值的原子操作版本,比如 NT 提供了 InterlockedExchange 等API, Linux/UNIX也提供了atomic_set 等函数。

  • 关于java中的原子性?
    原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。对于读取和写入出long double之外的基本类型变量这样的操作,可以保证它们会被当作不可分(原子)的操作来操作。 因为JVM的版本和其它的问题,其它的很多操作就不好说了,比如说++操作在C++中是原子操作,但在Java中就不好说了。 另外,Java提供了AtomicInteger等原子类。再就是用原子性来控制并发比较麻烦,也容易出问题。

1)除long和double之外的基本类型的赋值操作
2)所有引用reference的赋值操作
3)java.concurrent.Atomic.* 包中所有类的一切操作
count++不是原子操作,是3个原子操作组合
1.读取主存中的count值,赋值给一个局部成员变量tmp
2.tmp+1
3.将tmp赋值给count
可能会出现线程1运行到第2步的时候,tmp值为1;这时CPU调度切换到线程2执行完毕,count值为1;切换到线程1,继续执行第3步,count被赋值为1————结果就是两个线程执行完毕,count的值只加了1;
还有一点要注意,如果使用AtomicInteger.set(AtomicInteger.get() + 1),会和上述情况一样有并发问题,要使用AtomicInteger.getAndIncrement()才可以避免并发问题

(https://javarevisited.blogspot.com/2011/04/synchronization-in-java-synchronized.html)

  • What is Synchronization in Java

Synchronization in Java is possible by using Java keywords “synchronized” and “volatile”.

Concurrent access of shared objects in Java introduces to kind of errors: thread interference and memory consistency errors and to avoid these errors you need to properly synchronize your Java object to allow mutual exclusive access of critical section to two threads.

  • Why do we need Synchronization in Java?

If your code is executing in a multi-threaded environment, you need synchronization for objects, which are shared among multiple threads, to avoid any corruption of state or any kind of unexpected behavior. Synchronization in Java will only be needed if shared object is mutable. if your shared object is either read-only or immutable object, then you don’t need synchronization, despite running multiple threads. Same is true with what threads are doing with an object if all the threads are only reading value then you don’t require synchronization in Java. JVM guarantees that Java synchronized code will only be executed by one thread at a time.

In Summary, Java synchronized Keyword provides following functionality essential for concurrent programming:

1) The synchronized keyword in Java provides locking, which ensures mutually exclusive access to the shared resource and prevents data race.

2) synchronized keyword also prevent reordering of code statement by the compiler which can cause a subtle concurrent issue if we don’t use synchronized or volatile keyword.

3) synchronized keyword involve locking and unlocking. before entering into synchronized method or block thread needs to acquire the lock, at this point it reads data from main memory than cache and when it release the lock, it flushes write operation into main memory which eliminates memory inconsistency errors.

  • Example of Synchronized Method in Java
1
public class Counter{ private static int count = 0; public static synchronized int getCount(){ return count; } public synchoronized setCount(int count){ this.count = count; } }

In this example of Java, the synchronization code is not properly synchronized because both getCount() and setCount() are not getting locked on the same object and can run in parallel which may result in the incorrect count. Here getCount() will lock in Counter.class object while setCount() will lock on current object (this). To make this code properly synchronized in Java you need to either make both method static or nonstatic or use java synchronized block instead of java synchronized method. By the way, this is one of the common mistake Java developers make while synchronizing their code.

  • Example of Synchronized Block in Java

This is a classic example of double checked locking in Singleton.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton{

private static volatile Singleton _instance;

public static Singleton getInstance(){
if(_instance == null){
synchronized(Singleton.class){
if(_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
  • Important points of synchronized keyword in Java
  1. Synchronized keyword in Java is used to provide mutually exclusive access to a shared resource with multiple threads in Java. Synchronization in Java guarantees that no two threads can execute a synchronized method which requires the same lock simultaneously or concurrently.
  2. You can use java synchronized keyword only on synchronized method or synchronized block.
  3. Whenever a thread enters into java synchronized method or blocks it acquires a lock and whenever it leaves java synchronized method or block it releases the lock. The lock is released even if thread leaves synchronized method after completion or due to any Error or Exception.

  4. Java Thread acquires an object level lock when it enters into an instance synchronized java method and acquires a class level lock when it enters into static synchronized java method.

  5. Java synchronized keyword is re-entrant in nature it means if a java synchronized method calls another synchronized method which requires the same lock then the current thread which is holding lock can enter into that method without acquiring the lock.

  6. Java Synchronization will throw NullPointerException if object used in java synchronized block is null e.g. synchronized (myInstance) will throw java.lang.NullPointerException if myInstance is null.

  7. One Major disadvantage of Java synchronized keyword is that it doesn’t allow concurrent read, which can potentially limit scalability. By using the concept of lock stripping and using different locks for reading and writing, you can overcome this limitation of synchronized in Java. You will be glad to know that java.util.concurrent.locks.ReentrantReadWriteLock provides ready-made implementation of ReadWriteLock in Java.

  8. One more limitation of java synchronized keyword is that it can only be used to control access to a shared object within the same JVM. If you have more than one JVM and need to synchronize access to a shared file system or database, the Java synchronized keyword is not at all sufficient. You need to implement a kind of global lock for that.

  9. Java synchronized keyword incurs a performance cost. A synchronized method in Java is very slow and can degrade performance. So use synchronization in java when it absolutely requires and consider using java synchronized block for synchronizing critical section only.

  10. Java synchronized block is better than java synchronized method in Java because by using synchronized block you can only lock critical section of code and avoid locking the whole method which can possibly degrade performance. A good example of java synchronization around this concept is getting Instance() method Singleton class. See here.

  11. It’s possible that both static synchronized and non-static synchronized method can run simultaneously or concurrently because they lock on the different object.

  12. From java 5 after a change in Java memory model reads and writes are atomic for all variables declared using the volatile keyword (including long and double variables) and simple atomic variable access is more efficient instead of accessing these variables via synchronized java code. But it requires more care and attention from the programmer to avoid memory consistency errors.

  13. Java synchronized code could result in deadlock or starvation while accessing by multiple threads if synchronization is not implemented correctly. To know how to avoid deadlock in java see here.

  14. According to the Java language specification you can not use Java synchronized keyword with constructor it’s illegal and result in compilation error. So you can not synchronize constructor in Java which seems logical because other threads cannot see the object being created until the thread creating it has finished it.

  15. You cannot apply java synchronized keyword with variables and can not use java volatile keyword with the method.

  16. Java.util.concurrent.locks extends capability provided by java synchronized keyword for writing more sophisticated programs since they offer more capabilities e.g. Reentrancy and interruptible locks.

  17. Java synchronized keyword also synchronizes memory. In fact, java synchronized synchronizes the whole of thread memory with main memory.

  18. Important method related to synchronization in Java are wait(), notify() and notifyAll() which is defined in Object class. Do you know, why they are defined in java.lang.object class instead of java.lang.Thread? You can find some reasons, which make sense.

  19. Do not synchronize on the non-final field on synchronized block in Java. because the reference of the non-final field may change anytime and then different thread might synchronizing on different objects i.e. no synchronization at all. an example of synchronizing on the non-final field:

1
2
3
4
private String lock = new String("lock");
synchronized(lock){
System.out.println("locking on :" + lock);
}

any if you write synchronized code like above in java you may get a warning “Synchronization on the non-final field” in IDE like Netbeans and InteliJ

  1. It’s not recommended to use String object as a lock in java synchronized block because a string is an immutable object and literal string and interned string gets stored in String pool. so by any chance if any other part of the code or any third party library used same String as there lock then they both will be locked on the same object despite being completely unrelated which could result in unexpected behavior and bad performance. instead of String object its advised to use new Object() for Synchronization in Java on synchronized block.

    1
    2
    3
    4
    5
    6
    7
    8
    private static final String LOCK = "lock";   //not recommended
    private static final Object OBJ_LOCK = new Object(); //better

    public void process() {
    synchronized(LOCK) {
    ........
    }
    }
  2. From Java library, Calendar and SimpleDateFormat classes are not thread-safe and requires external synchronization in Java to be used in the multi-threaded environment.

  • some limitation of synchronized keyword in Java which is addressed by explicit locking using new concurrent package and Lock interface:
  1. synchronized keyword doesn’t allow separate locks for reading and writing. as we know that multiple threads can read without affecting thread-safety of class, synchronized keyword suffer performance due to contention in case of multiple readers and one or few writer.

    1. if one thread is waiting for lock then there is no way to timeout, the thread can wait indefinitely for the lock.
  2. on a similar note if the thread is waiting for the lock to acquired there is no way to interrupt the thread.

All these limitations of synchronized keyword are addressed and resolved by using ReadWriteLock and ReentrantLock in Java 5.

  • list of Java Synchronization facts and best practices:

1) synchronized keyword in internally implemented using two-byte code instructions MonitorEnter and MonitorExit, this is generated by the compiler. The compiler also ensures that there must be a MonitorExit for every MonitorEnter in different code path e.g. normal execution and abrupt execution, because of Exception.

2) java.util.concurrent package different locking mechanism than provided by synchronized keyword, they mostly used ReentrantLock, which internally use CAS operations, volatile variables and atomic variables to get better performance.

3) With synchronized keyword, you have to leave the lock, once you exist a synchronized method or block, there is no way you can take the lock to another method. java.util.concurrent.locks.ReentrantLock solves this problem by providing control of acquiring and releasing the lock, which means you can acquire the lock in method A and can release in method B if they both needs to be locked in same object lock. Though this could be risky as the compiler will neither check nor warn you about any accidental leak of locks. Which means, this can potentially block other threads, which are waiting for the same lock.

4) Prefer ReentrantLock over synchronized keyword, it provides more control on lock acquisition, lock release, and better performance compared to synchronized keyword.

5) Any thread trying to acquire a lock using synchronized method will block indefinitely until the lock is available. Instead this, tryLock() method of java.util.concurrent.locks.ReentrantLock will not block if the lock is not available.
Having said that, I must say, lots of good information.

你需要同步原子操作吗?

原子操作是需要同步控制的。

可以通过synchronized这个关键字来同步原子操作;
更简便的方法是,对所操作的属性用原子类来定义即可:

1
private AtomicInteger a = new AtomicInteger(1);

8. Java 中 volatile 关键字是什么?你如何使用它?它和 Java 中的同步方法有什么区别?

8.1 what is volatile?

volatile has semantics for memory visibility. Basically, the value of a volatile field becomes visible to all readers (other threads in particular) after a write operation completes on it. Without volatile, readers could see some non-updated value.

简言之,就是volatile定义的变量是多线程环境下直接内存可见的变量。

8.2 How you use volatile?

  • volatile is very useful to stop threads.

Not that you should be writing your own threads, Java 1.6 has a lot of nice thread pools. But if you are sure you need a thread, you’ll need to know how to stop it.

The pattern I use for threads is:

1
2
3
4
5
6
7
8
9
10
11
12
public class Foo extends Thread {
private volatile boolean close = false;
public void run() {
while(!close) {
// do work
}
}
public void close() {
close = true;
// interrupt here if needed
}
}

Notice how there’s no need for synchronization

  • I use a volatile variable to control whether some code continues a loop.

The loop tests the volatile value and continues if it is true. The condition can be set to false by calling a “stop” method. The loop sees false and terminates when it tests the value after the stop method completes execution.

  • Important point about volatile:

Synchronization in Java is possible by using Java keywords synchronized and volatile and locks.
In Java, we can not have synchronized variable. Using synchronized keyword with a variable is illegal and will result in compilation error. Instead of using the synchronized variable in Java, you can use the java volatile variable, which will instruct JVM threads to read the value of volatile variable from main memory and don’t cache it locally.
If a variable is not shared between multiple threads then there is no need to use the volatile keyword.

  • Lazy initialization
    A singleton implementation may use lazy initialization, where the instance is created when the static method is first invoked. If the static method might be called from multiple threads simultaneously, measures may need to be taken to prevent race conditions that could result in the creation of multiple instances of the class. The following is a thread-safe sample implementation, using lazy initialization with double-checked locking, written in Java.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public final class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
    if (instance == null) {
    synchronized(Singleton.class) {
    if (instance == null) {
    instance = new Singleton();
    }
    }
    }

    return instance;
    }
    }

简言之,
volatile定义的变量可以作为在多线程环境下停止一个县城的标记;
其次,volatile不安尖子也被用于在内存中的多线程间共享内容;一个应用例子就是懒加载的单例模式,如果没有volatile关键字标记单例的实例,就会有并发问题。

8.2 它和 Java 中的同步方法有什么区别?

Declaring a variable as volatile means that modifying its value immediately affects the actual memory storage for the variable. The compiler cannot optimize away any references made to the variable. This guarantees that when one thread modifies the variable, all other threads see the new value immediately. (This is not guaranteed for non-volatile variables.)

Declaring an atomic variable guarantees that operations made on the variable occur in an atomic fashion, i.e., that all of the substeps of the operation are completed within the thread they are executed and are not interrupted by other threads. For example, an increment-and-test operation requires the variable to be incremented and then compared to another value; an atomic operation guarantees that both of these steps will be completed as if they were a single indivisible/uninterruptible operation.

Synchronizing all accesses to a variable allows only a single thread at a time to access the variable, and forces all other threads to wait for that accessing thread to release its access to the variable.

Synchronized access is similar to atomic access, but the atomic operations are generally implemented at a lower level of programming. Also, it is entirely possible to synchronize only some accesses to a variable and allow other accesses to be unsynchronized (e.g., synchronize all writes to a variable but none of the reads from it).

Atomicity, synchronization, and volatility are independent attributes, but are typically used in combination to enforce proper thread cooperation for accessing variables.

volatile:

volatile is a keyword. volatile forces all threads to get latest value of the variable from main memory instead of cache. No locking is required to access volatile variables. All threads can access volatile variable value at same time.

Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable.

This means that changes to a volatile variable are always visible to other threads. What’s more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

When to use: One thread modifies the data and other threads have to read latest value of data. Other threads will take some action but they won’t update data.

AtomicXXX:

AtomicXXX classes support lock-free thread-safe programming on single variables. These AtomicXXX classes (like AtomicInteger) resolves memory inconsistency errors / side effects of modification of volatile variables, which have been accessed in multiple threads.

When to use: Multiple threads can read and modify data.

synchronized:

synchronized is keyword used to guard a method or code block. By making method as synchronized has two effects:

First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

When to use: Multiple threads can read and modify data. Your business logic not only update the data but also executes atomic operations

AtomicXXX is equivalent of volatile + synchronized even though the implementation is different. AmtomicXXX extends volatile variables + compareAndSet methods but does not use synchronization.

简言之,
主要区别是,volatile是定义于变量的,不能修饰于方法。而synchronized则用于修饰方法或者代码块,不能用于修饰变量。

9. 什么是竞态条件?你如何发现并解决竞态条件?

(https://javarevisited.blogspot.com/2012/02/what-is-race-condition-in.html)

9.1 What is

Race condition in Java is a type of concurrency bug or issue which is introduced in your program because parallel execution of your program by multiple threads at same time, Since Java is a multi-threaded programming language hence risk of Race condition is higher in Java which demands clear understanding of what causes a race condition and how to avoid that. Anyway Race conditions are just one of hazards or risk presented by use of multi-threading in Java just like deadlock in Java. Race conditions occurs when two thread operate on same object without proper synchronization and there operation interleaves on each other. Classical example of Race condition is incrementing a counter since increment is not an atomic operation and can be further divided into three steps like read, update and write. if two threads tries to increment count at same time and if they read same value because of interleaving of read operation of one thread to update operation of another thread, one count will be lost when one thread overwrite increment done by other thread. atomic operations are not subject to race conditions because those operation cannot be interleaved.

9.2 How to find Race Conditions in Java

only sure shot way to find race condition is reviewing code manually or using code review tools which can alert you on potential race conditions based on code pattern and use of synchronization in Java. I solely rely on code review and yet to find a suitable tool for exposing race condition in java.

  • Code Example of Race Condition in Java
    Based on my experience in Java synchronization and where we use synchronized keyword I found that two code patterns namely “check and act” and “read modify write” can suffer race condition if not synchronized properly. both cases rely on natural assumption that a single line of code will be atomic and execute in one shot which is wrong e.g. ++ is not atomic.
  • “Check and Act” race condition pattern
    classical example of “check and act” race condition in Java is getInstance() method of Singleton Class, remember that was one questions which we have discussed on 10 Interview questions on Singleton pattern in Java as “How to write thread-safe Singleton in Java”. getInstace() method first check for whether instance is null and than initialized the instance and return to caller. Whole purpose of Singleton is that getInstance should always return same instance of Singleton. if you call getInstance() method from two thread simultaneously its possible that while one thread is initializing singleton after null check, another thread sees value of _instance reference variable as null (quite possible in java) especially if your object takes longer time to initialize and enters into critical section which eventually results in getInstance() returning two separate instance of Singleton. This may not happen always because a fraction of delay may result in value of _instance updated in main memory. here is a code example
    1
    2
    3
    4
    5
    public Singleton getInstance(){
    if(_instance == null){ //race condition if two threads sees _instance= null
    _instance = new Singleton();
    }
    }

an easy way to fix “check and act” race conditions is to synchronized keyword and enforce locking which will make this operation atomic and guarantees that block or method will only be executed by one thread and result of operation will be visible to all threads once synchronized blocks completed or thread exited form synchronized block.

read-modify-update race conditions
This is another code pattern in Java which cause race condition, classical example is the non thread safe counter we discussed in how to write thread safe class in Java. this is also a very popular multi-threading question where they ask you to find bugs on concurrent code. read-modify-update pattern also comes due to improper synchronization of non-atomic operations or combination of two individual atomic operations which is not atomic together e.g. put if absent scenario. consider below code

1
2
3
if(!hashtable.contains(key)){
hashtable.put(key,value);
}

here we only insert object into hashtable if its not already there. point is both contains() and put() are atomic but still this code can result in race condition since both operation together is not atomic. consider thread T1 checks for conditions and goes inside if block now CPU is switched from T1 to thread T2 which also checks condition and goes inside if block. now we have two thread inside if block which result in either T1 overwriting T2 value or vice-versa based on which thread has CPU for execution. In order to fix this race condition in Java you need to wrap this code inside synchronized block which makes them atomic together because no thread can go inside synchronized block if one thread is already there.

These are just some of examples of race conditions in Java, there will be numerous based on your business logic and code. best approach to find Race conditions is code review but its hard because thinking concurrently is not natural and we still assume code to run sequentially. Problem can become worse if JVM reorders code in absent of proper synchronization to gain performance benefit and this usually happens on production under heavily load, which is worst. I also suggest doing load testing in production like environment which many time helps to expose race conditions in java. Please share if you have faced any race condition in java projects.

10. 在 Java 中你如何转储线程(thread dump)?如何分析它?

在 UNIX 中,你可以使用 kill -3 ,然后线程转储日志会打印在屏幕上,可以使用 CTRL+Break 查看。

你如何分析转储日志?线程转储日志对于分析死锁情况非常有用。

10.2 如何分析转储日志

以下为一篇详细的介绍分析thread dump的文章:

How to Analyze Java Thread Dumps

When there is an obstacle, or when a Java based Web application is running much slower than expected, we need to use thread dumps. If thread dumps feel like very complicated to you, this article may help you very much. Here I will explain what threads are in Java, their types, how they are created, how to manage them, how you can dump threads from a running application, and finally how you can analyze them and determine the bottleneck or blocking threads. This article is a result of long experience in Java application debugging.

Java and Thread

A web server uses tens to hundreds of threads to process a large number of concurrent users. If two or more threads utilize the same resources, a contention between the threads is inevitable, and sometimes deadlock occurs.

Thread contention is a status in which one thread is waiting for a lock, held by another thread, to be lifted. Different threads frequently access shared resources on a web application. For example, to record a log, the thread trying to record the log must obtain a lock and access the shared resources.

Deadlock is a special type of thread contention, in which two or more threads are waiting for the other threads to complete their tasks in order to complete their own tasks.

Different issues can arise from thread contention. To analyze such issues, you need to use the thread dump. A thread dump will give you the information on the exact status of each thread.

Background Information for Java Threads

  • Thread Synchronization
    A thread can be processed with other threads at the same time. In order to ensure compatibility when multiple threads are trying to use shared resources, one thread at a time should be allowed to access the shared resources by using thread synchronization.

Thread synchronization on Java can be done using monitor. Every Java object has a single monitor. The monitor can be owned by only one thread. For a thread to own a monitor that is owned by a different thread, it needs to wait in the wait queue until the other thread releases its monitor.

  • Thread Status

In order to analyze a thread dump, you need to know the status of threads. The statuses of threads are stated on java.lang.Thread.State.

NEW: The thread is created but has not been processed yet.
RUNNABLE: The thread is occupying the CPU and processing a task. (It may be in WAITING status due to the OS’s resource distribution.)
BLOCKED: The thread is waiting for a different thread to release its lock in order to get the monitor lock.
WAITING: The thread is waiting by using a wait, join or park method.
TIMED_WAITING: The thread is waiting by using a sleep, wait, join or park method. (The difference from WAITING is that the maximum waiting time is specified by the method parameter, and WAITING can be relieved by time as well as external changes.)

  • Thread Types

Java threads can be divided into two:

daemon threads;
and non-daemon threads.
Daemon threads stop working when there are no other non-daemon threads. Even if you do not create any threads, the Java application will create several threads by default. Most of them are daemon threads, mainly for processing tasks such as garbage collection or JMX.

A thread running the ‘static void main(String[] args)’ method is created as a non-daemon thread, and when this thread stops working, all other daemon threads will stop as well. (The thread running this main method is called the VM thread in HotSpot VM.)

Getting a Thread Dump

We will introduce the three most commonly used methods. Note that there are many other ways to get a thread dump. A thread dump can only show the thread status at the time of measurement, so in order to see the change in thread status, it is recommended to extract them from 5 to 10 times with 5-second intervals.

  • Getting a Thread Dump Using jstack

In JDK 1.6 and higher, it is possible to get a thread dump on MS Windows using jstack.

Use PID via jps to check the PID of the currently running Java application process.

1
2
3
4
[user@linux ~]$ jps -v
25780 RemoteTestRunner -Dfile.encoding=UTF-8
25590 sub.rmi.registry.RegistryImpl 2999 -Dapplication.home=/home1/user/java/jdk.1.6.0_24 -Xms8m
26300 sun.tools.jps.Jps -mlvV -Dapplication.home=/home1/user/java/jdk.1.6.0_24 -Xms8m

Use the extracted PID as the parameter of jstack to obtain a thread dump.

1
[user@linux ~]$ jstack -f 5824

  • A Thread Dump Using jVisualVM

Generate a thread dump by using a program such as jVisualVM.

The task on the left indicates the list of currently running processes. Click on the process for which you want the information, and select the thread tab to check the thread information in real time. Click the Thread Dump button on the top right corner to get the thread dump file.

  • Generating in a Linux Terminal

Obtain the process pid by using ps -ef command to check the pid of the currently running Java process.

1
2
3
4
[user@linux ~]$ ps - ef | grep java
user 2477 1 0 Dec23 ? 00:10:45 ...
user 25780 25361 0 15:02 pts/3 00:00:02 ./jstatd -J -Djava.security.policy=jstatd.all.policy -p 2999
user 26335 25361 0 15:49 pts/3 00:00:00 grep java

Use the extracted pid as the parameter of kill –SIGQUIT(3) to obtain a thread dump.

Thread Information from the Thread Dump File

1
2
3
4
5
6
7
8
9
10
11
12
"pool-1-thread-13" prio=6 tid=0x000000000729a000 nid=0x2fb4 runnable [0x0000000007f0f000] java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
- locked <0x0000000780b7e688> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:167)
at java.io.BufferedReader.fill(BufferedReader.java:136)
at java.io.BufferedReader.readLine(BufferedReader.java:299)
- locked <0x0000000780b7e688> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:362)
  • Thread name: When using Java.lang.Thread class to generate a thread, the thread will be named Thread-(Number), whereas when using java.util.concurrent.ThreadFactory class, it will be named pool-(number)-thread-(number).
  • Priority: Represents the priority of the threads.
  • Thread ID: Represents the unique ID for the threads. (Some useful information, including the CPU usage or memory usage of the thread, can be obtained by using thread ID.)
  • Thread status: Represents the status of the threads.
  • Thread callstack: Represents the call stack information of the threads.

Thread Dump Patterns by Type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"BLOCKED_TEST pool-1-thread-1" prio=6 tid=0x0000000006904800 nid=0x28f4 runnable [0x000000000785f000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:282)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
- locked <0x0000000780a31778> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:432)
- locked <0x0000000780a04118> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:85)
- locked <0x0000000780a040c0> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:168)
at java.io.PrintStream.newLine(PrintStream.java:496)
- locked <0x0000000780a04118> (a java.io.PrintStream)
at java.io.PrintStream.println(PrintStream.java:687)
- locked <0x0000000780a04118> (a java.io.PrintStream)
at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:44)
- locked <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState)
at com.nbp.theplatform.threaddump.ThreadBlockedState$1.run(ThreadBlockedState.java:7)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Locked ownable synchronizers:
- <0x0000000780a31758> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
"BLOCKED_TEST pool-1-thread-2" prio=6 tid=0x0000000007673800 nid=0x260c waiting for monitor entry [0x0000000008abf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:43)
- waiting to lock <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState)
at com.nbp.theplatform.threaddump.ThreadBlockedState$2.run(ThreadBlockedState.java:26)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Locked ownable synchronizers:
- <0x0000000780b0c6a0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
"BLOCKED_TEST pool-1-thread-3" prio=6 tid=0x00000000074f5800 nid=0x1994 waiting for monitor entry [0x0000000008bbf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:42)
- waiting to lock <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState)
at com.nbp.theplatform.threaddump.ThreadBlockedState$3.run(ThreadBlockedState.java:34)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Locked ownable synchronizers:
- <0x0000000780b0e1b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

When in Deadlock Status

This is when thread A needs to obtain thread B’s lock to continue its task, while thread B needs to obtain thread A’s lock to continue its task. In the thread dump, you can see that DEADLOCK_TEST-1 thread has 0x00000007d58f5e48 lock, and is trying to obtain 0x00000007d58f5e60 lock. You can also see that DEADLOCK_TEST-2 thread has 0x00000007d58f5e60 lock, and is trying to obtain 0x00000007d58f5e78 lock. Also, DEADLOCK_TEST-3 thread has 0x00000007d58f5e78 lock, and is trying to obtain 0x00000007d58f5e48 lock. As you can see, each thread is waiting to obtain another thread’s lock, and this status will not change until one thread discards its lock.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"DEADLOCK_TEST-1" daemon prio=6 tid=0x000000000690f800 nid=0x1820 waiting for monitor entry [0x000000000805f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197)
- waiting to lock <0x00000007d58f5e60> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182)
- locked <0x00000007d58f5e48> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135)
Locked ownable synchronizers:
- None
"DEADLOCK_TEST-2" daemon prio=6 tid=0x0000000006858800 nid=0x17b8 waiting for monitor entry [0x000000000815f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197)
- waiting to lock <0x00000007d58f5e78> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182)
- locked <0x00000007d58f5e60> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135)
Locked ownable synchronizers:
- None
"DEADLOCK_TEST-3" daemon prio=6 tid=0x0000000006859000 nid=0x25dc waiting for monitor entry [0x000000000825f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197)
- waiting to lock <0x00000007d58f5e48> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182)
- locked <0x00000007d58f5e78> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135)
Locked ownable synchronizers:
- None

When Continuously Waiting to Receive Messages from a Remote Server

The thread appears to be normal, since its state keeps showing as RUNNABLE. However, when you align the thread dumps chronologically, you can see that socketReadThread thread is waiting infinitely to read the socket.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"socketReadThread" prio=6 tid=0x0000000006a0d800 nid=0x1b40 runnable [0x00000000089ef000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
- locked <0x00000007d78a2230> (a java.io.InputStreamReader)
at sun.nio.cs.StreamDecoder.read0(StreamDecoder.java:107)
- locked <0x00000007d78a2230> (a java.io.InputStreamReader)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:93)
at java.io.InputStreamReader.read(InputStreamReader.java:151)
at com.nbp.theplatform.threaddump.ThreadSocketReadState$1.run(ThreadSocketReadState.java:27)
at java.lang.Thread.run(Thread.java:662)

When Waiting

The thread is maintaining WAIT status. In the thread dump, IoWaitThread thread keeps waiting to receive a message from LinkedBlockingQueue. If there continues to be no message for LinkedBlockingQueue, then the thread status will not change.

1
2
3
4
5
6
7
8
9
10
"IoWaitThread" prio=6 tid=0x0000000007334800 nid=0x2b3c waiting on condition [0x000000000893f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007d5c45850> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
at java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:440)
at java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:629)
at com.nbp.theplatform.threaddump.ThreadIoWaitState$IoWaitHandler2.run(ThreadIoWaitState.java:89)
at java.lang.Thread.run(Thread.java:662)

When Thread Resources Cannot be Organized Normally

Unnecessary threads will pile up when thread resources cannot be organized normally. If this occurs, it is recommended to monitor the thread organization process or check the conditions for thread termination.

How to Solve Problems by Using Thread Dump

  • Example 1: When the CPU Usage is Abnormally High
  1. Extract the thread that has the highest CPU usage.
    1
    2
    3
    4
    5
    [user@linux ~]$ ps -mo pid.lwp.stime.time.cpu -C java
    PID LWP STIME TIME %CPU
    10029 - Dec07 00:02:02 99.5
    - 10039 Dec07 00:00:00 0.1
    - 10040 Dec07 00:00:00 95.5

From the application, find out which thread is using the CPU the most.

Acquire the Light Weight Process (LWP) that uses the CPU the most and convert its unique number (10039) into a hexadecimal number (0x2737).

  1. After acquiring the thread dump, check the thread’s action.

Extract the thread dump of an application with a PID of 10029, then find the thread with an nid of 0x2737.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"NioProcessor-2" prio=10 tid=0x0a8d2800 nid=0x2737 runnable [0x49aa5000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
- locked <0x74c52678> (a sun.nio.ch.Util$1)
- locked <0x74c52668> (a java.util.Collections$UnmodifiableSet)
- locked <0x74c501b0> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
at external.org.apache.mina.transport.socket.nio.NioProcessor.select(NioProcessor.java:65)
at external.org.apache.mina.common.AbstractPollingIoProcessor$Worker.run(AbstractPollingIoProcessor.java:708)
at external.org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:51)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)

Extract thread dumps several times every hour, and check the status change of the threads to determine the problem.

  • Example 2: When the Processing Performance is Abnormally Slow

After acquiring thread dumps several times, find the list of threads with BLOCKED status.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
" DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
- waiting to lock <0xe0375410> (a beans.ConnectionPool)
at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
"DB-Processor-14" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f020]
java.lang.Thread.State: BLOCKED (on object monitor)
at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
- waiting to lock <0xe0375410> (a beans.ConnectionPool)
at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
" DB-Processor-3" daemon prio=5 tid=0x00928248 nid=0x8b waiting for monitor entry [0x000000000825d080]
java.lang.Thread.State: RUNNABLE
at oracle.jdbc.driver.OracleConnection.isClosed(OracleConnection.java:570)
- waiting to lock <0xe03ba2e0> (a oracle.jdbc.driver.OracleConnection)
at beans.ConnectionPool.getConnection(ConnectionPool.java:112)
- locked <0xe0386580> (a java.util.Vector)
- locked <0xe0375410> (a beans.ConnectionPool)
at beans.cus.Cue_1700c.GetNationList(Cue_1700c.java:66)
at org.apache.jsp.cue_1700c_jsp._jspService(cue_1700c_jsp.java:120)

Acquire the list of threads with BLOCKED status after getting the thread dumps several times.

If the threads are BLOCKED, extract the threads related to the lock that the threads are trying to obtain.

Through the thread dump, you can confirm that the thread status stays BLOCKED because <0xe0375410> lock could not be obtained. This problem can be solved by analyzing stack trace from the thread currently holding the lock.

There are two reasons why the above pattern frequently appears in applications using DBMS. The first reason is inadequate configurations. Despite the fact that the threads are still working, they cannot show their best performance because the configurations for DBCP and the like are not adequate. If you extract thread dumps multiple times and compare them, you will often see that some of the threads that were BLOCKED previously are in a different state.

The second reason is the abnormal connection. When the connection with DBMS stays abnormal, the threads wait until the time is out. In this case, even after extracting the thread dumps several times and comparing them, you will see that the threads related to DBMS are still in a BLOCKED state. By adequately changing the values, such as the timeout value, you can shorten the time in which the problem occurs.

Coding for Easy Thread Dump

  • Naming Threads

When a thread is created using java.lang.Thread object, the thread will be named Thread-(Number). When a thread is created using java.util.concurrent.DefaultThreadFactory object, the thread will be named pool-(Number)-thread-(Number). When analyzing tens to thousands of threads for an application, if all the threads still have their default names, analyzing them becomes very difficult, because it is difficult to distinguish the threads to be analyzed.

Therefore, you are recommended to develop the habit of naming the threads whenever a new thread is created.

When you create a thread using java.lang.Thread, you can give the thread a custom name by using the creator parameter.

1
2
3
4
5

public Thread(Runnable target, String name);
public Thread(ThreadGroup group, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize);

When you create a thread using java.util.concurrent.ThreadFactory, you can name it by generating your own ThreadFactory. If you do not need special functionalities, then you can use MyThreadFactory as described below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadFactory implements ThreadFactory {
private static final ConcurrentHashMap<String, AtomicInteger> POOL_NUMBER =
new ConcurrentHashMap<String, AtomicInteger>();
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public MyThreadFactory(String threadPoolName) {
if (threadPoolName == null) {
throw new NullPointerException("threadPoolName");
}
POOL_NUMBER.putIfAbsent(threadPoolName, new AtomicInteger());
SecurityManager securityManager = System.getSecurityManager();
group = (securityManager != null) ? securityManager.getThreadGroup() :
Thread.currentThread().getThreadGroup();
AtomicInteger poolCount = POOL_NUMBER.get(threadPoolName);
if (poolCount == null) {
namePrefix = threadPoolName + " pool-00-thread-";
} else {
namePrefix = threadPoolName + " pool-" + poolCount.getAndIncrement() + "-thread-";
}
}
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY);
}
return thread;
}
}
  • Obtaining More Detailed Information by Using MBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] threadIds = mxBean.getAllThreadIds();
ThreadInfo[] threadInfos =
mxBean.getThreadInfo(threadIds);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(
threadInfo.getThreadName());
System.out.println(
threadInfo.getBlockedCount());
System.out.println(
threadInfo.getBlockedTime());
System.out.println(
threadInfo.getWaitedCount());
System.out.println(
threadInfo.getWaitedTime());
}

You can acquire the amount of time that the threads WAITed or were BLOCKED by using the method in ThreadInfo, and by using this you can also obtain the list of threads that have been inactive for an abnormally long period of time.

10.3 stack-overflow上一个介绍分析thread dump的回答: how-to-analyze-a-java-thread-dump

The TID is thead id and NID is: Native thread ID. This ID is highly platform dependent. It’s the NID in jstack thread dumps. On Windows, it’s simply the OS-level thread ID within a process. On Linux and Solaris, it’s the PID of the thread (which in turn is a light-weight process). On Mac OS X, it is said to be the native pthread_t value.

Go to this link: Java-level thread ID: for a definition and a further explanation of these two terms.

On IBM’s site I found this link: How to interpret a thread dump. that covers this in greater detail:

It explains what that waiting on means: A lock prevents more than one entity from accessing a shared resource. Each object in Java™ has an associated lock (gained by using a synchronized block or method). In the case of the JVM, threads compete for various resources in the JVM and locks on Java objects.

Then it describes the monitor as a special kind of locking mechanism that is used in the JVM to allow flexible synchronization between threads. For the purpose of this section, read the terms monitor and lock interchangeably.

Then it goes further:

To avoid having a monitor on every object, the JVM usually uses a flag in a class or method block to indicate that the item is locked. Most of the time, a piece of code will transit some locked section without contention. Therefore, the guardian flag is enough to protect this piece of code. This is called a flat monitor. However, if another thread wants to access some code that is locked, a genuine contention has occurred. The JVM must now create (or inflate) the monitor object to hold the second thread and arrange for a signaling mechanism to coordinate access to the code section. This monitor is now called an inflated monitor.

Here is a more in-depth explanation of what you are seeing on the lines from the thread dump. A Java thread is implemented by a native thread of the operating system. Each thread is represented by a line in bold such as:

“Thread-1” (TID:0x9017A0, sys_thread_t:0x23EAC8, state:R, native ID:0x6E4) prio=5

*The following 6 items explains this as I’ve matched them from the example, values in the brackets[]:

name [Thread-1],
identifier [0x9017A0],
JVM data structure address [0x23EAC8],
current state [R],
native thread identifier [0x6E4],
and priority [5].
The “wait on” appears to be a daemon thread associated with the jvm itself and not the application thread perse. When you get an “in Object.wait()”, that means the daemon thread, “finalizer” here, is waiting on a notification about a lock on an object, in this case it shows you what notification it’s waiting on: “- waiting on <0x27ef0288> (a java.lang.ref.ReferenceQueue$Lock)”

Definition of the ReferenceQueue is: Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

The finalizer thread runs so the garbage collection operates to clean up resources associated with an object. If I’m seeing it corectly, the finalizer can’t get the lock to this object: java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118) because the java object is running a method, so the finalizer thread is locked until that object is finished with it’s current task.

Also, the finalizer isn’t just looking to reclaim memory, it’s more involved than that for cleaning up resources. I need to do more study on it, but if you have files open, sockets, etc… related to an objects methods, then the finalizer is going to work on freeing those items up as well.

What is the figure in squared parenthesis after Object.wait in the thread dump?

It is a pointer in memory to the thread. Here is a more detailed description:

C.4.1 Thread Information

The first part of the thread section shows the thread that provoked the fatal error, as follows:

Current thread (0x0805ac88): JavaThread “main” [_thread_in_native, id=21139]
| | | | +– ID
| | | +————- state
| | +————————– name
| +———————————— type
+————————————————– pointer
The thread pointer is the pointer to the Java VM internal thread structure. It is generally of no interest unless you are debugging a live Java VM or core file.

This last description came from: Troubleshooting Guide for Java SE 6 with HotSpot VM

Here are a few more links on thread dumps:

How Threads Work
How to Analyze a Thread Dump
Java Thread Dumps
Java VM threads
Stackoverflow question: How threads are mapped

扩展阅读:
JVM: How to analyze Thread Dump

10.4 堆转储文件分析

1)获取java进程id,命令:jps -v
2)到处堆转储文件,jmap命令:jmap-dump:format=b,file=/data/…/heap.hprof pid
3)分析工具:Eclipse Memory Analyzer, IBM HeapAnalyzer, VisualVM

  • Heap堆分析(堆转储、堆分析)
    链接:Heap堆分析(堆转储、堆分析)

1)通过jcmd获得堆直方图:jcmd 26964 GC.class_histogram | more
2)通过jmap获得堆直方图:jmap -histo:live 26964 | more

3)使用jcmd进行堆转储:jmap -dump:live,file=/home/ciadmin/pos-gateway-cloud/heap_dump2.hprof 26964
4)自动堆转储OutOfMemoryError是不可预料的,我们很难确定应该何时获得堆转储。有几个JVM标志可以起到帮助。

-XX:+HeapDumpOnOutOfMemoryError该标志默认为false,打开该标志,JVM会在抛出OutOfMemoryError时创建堆转储。

-XX:HeapDumpPath=该标志知道了堆转储将被写入的位置,默认为当前工作目录下生产java_pid.hprof文件。

-XX:+HeapDumpAfterFullGC 这会在运行一次Full GC后生成一个堆转储文件。

-XX:+HeapDumpBeforeFullGC 这会在运行一次Full GC之前生成一个堆转储文件。

有的情况下,(入帮,因为执行了多次Full GC)会生成多个堆转储文件,这时JVM会在堆转储文件的名字上附加一个序号。

这两条命令都会在指定目录下创建一个命名为*.hprof的文件。生成之后,有很多工具可以打开该文件。以下是三个最常见的工具。

5)分析工具:jhat
最原始的分析工具,它会读取堆转储文件,并运行一个小型的HTTP服务器,可以通过网页链接查看堆转储信息

1
2
3
4
5
6
7
8
9
10
$ jhat heap_dump.hprof 
Reading from heap_dump.hprof...
Dump file created Mon Mar 05 18:33:10 CST 2018
Snapshot read, resolving...
Resolving 751016 objects...
Chasing references, expect 150 dots......................................................................................................................................................
Eliminating duplicate references......................................................................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

6)dump分析工具:jvisualvm
jvisualvm的监视(Monitor) 选项卡可以从一个运行中的程序获得堆转储文件,也可以打开之前生成堆转储文件。
更多信息见《八、jdk工具之JvisualVM、JvisualVM之一–(visualVM介绍及性能分析示例)》

7)分析工具:eclipse的内存分析器工具mat,(EclipseLink Memory Analyzer Tool,mat)
可以加载一个或多个堆转储文件并执行分析。它可以生成报告,向我们建议可能存在问题的地方,也可以用于流量堆,并对堆执行类SQL的查询。

特别指出的是:mat内置一功能:如果打开了两个堆转储文件,mat有一个选项用来计算两个堆中的直方图之间的差别。

更多信息见《mat之一–eclipse安装Memory Analyzer》

对堆的第一遍分析通常涉及保留内存。一个对象的保留内存,是指回收该对象可以释放出的内存量。
关于保留内存相关知识见《GC之二–GC是如何回收时的判断依据、shallow(浅) size、retained(保留) size、Deep(深)size》

8)常见内存溢出错误
在下面情况下,jvm会抛出内存溢出错误(OutOfMemeoryError):

JVM没有原生内存可用;
永久代(在java7和更早的版本中)或元空间(java8)内存不足;
java堆本身内存不足–对于给定的堆空间而言,应用中活跃对象太多;
JVM执行GC耗时太多;
1、原生内存不足

其原因与堆根本无关。在32位的JVM中,一个进程的最大内存是4GB,如果指定一个非常大的堆大小,比如说3.8GB,使应用的大小很接近4GB的限制,这很危险。

2、永久代或元空间内存不足

首先与堆无关,其根源可能有两种情况:

第一种情况是应用使用的类太多,超出了永久代的默认容纳范围;解决方案:增加永久代的大小
第二种情况相对来说有点棘手:它涉及类加载器的内存泄漏。这种情况经常出现于Java EE应用服务器中。类加载导致内存溢出可以通过堆转储分析,在直方图中,找到ClassLoader类的所有实例,然后跟踪他们的GC根,看哪些对象还保留了对它们的引用。

3、堆内存不足

当确实是堆内存本身不足时,错误信息会这样:

OutOfMemoryError:Java heap space

可能的原因有:

1、活跃对象数目在为其配置的堆空间中已经装不下了。

2、也可能是应用存在内存泄漏:它持续分配新对象,却没有让其他对象退出作用域。

不管哪种情况,要找出哪些对象消耗的内存最多,堆转储分析都是必要的;

4、达到GC的开销限制

JVM抛出OutOfMemoryError的最后一种情况是JVM任务在执行GC上花费了太多时间:

OutOfMemoryError:GC overhead limit exceeded

当满足下列所有条件时就会抛出该错误:

1、花在Full GC的时间超出了-XX:GCTimeLimit=N标志指定的值。默认为98

2、一次Full GC回收内存量少于-XX:GCHeapFreeLimit=N标志指定的值。默认值为2(2%)

3、上面两个条件连续5次Full GC都成立(这个值无法调整)

4、-XX:+UseGCOverhead-Limit标志为true(默认也为true)

这四个条件必须都满足。一般来说,连续5次Full GC以上,不一定会抛异常。即使98%时间在Full GC上,但每次GC期间释放的堆空间会超过2%,这种情况下可以增加-XX:GCHeapFreeLimit的值。

还请注意,如果前两个条件连续4次Full GC周期都成立,作为释放内存的最后一搏,JVM中所有的软引用都会在第五次Full GC之前被释放。这往往会防止该错误,因为第五次Full GC很可能会释放超过2%的堆内存(假设该应用使用了软引用)。

11. 既然 start() 方法会调用 run() 方法,为什么我们调用 start() 方法,而不直接调用 run() 方法?

当你调用 start() 方法时,它会新建一个线程然后执行 run() 方法中的代码。如果直接调用 run() 方法,并不会创建新线程,方法中的代码会在当前调用者的线程中执行。可以看这篇文章了解更多线程中 Start 和 Run 方法的区别。

12. Java 中你如何唤醒阻塞线程?

这是有关线程的一个很狡猾的问题。有很多原因会导致阻塞,如果是 IO 阻塞,我认为没有方式可以中断线程(如果有的话请告诉我)。另一方面,如果线程阻塞是由于调用了 wait(),sleep() 或 join() 方法,你可以中断线程,通过抛出 InterruptedException 异常来唤醒该线程。可以看这篇文章了解有关处理阻塞线程的知识Java 中如何处理阻塞方法。

13. Java 中 CyclicBarriar 和 CountdownLatch 有什么区别?

两者区别之一就是 CyclicBarrier 在屏障打开之后(所有线程到达屏障点),可以重复使用。而 CountDownLatch 不行。想了解更多可以参与课程Java 中的多线程和并行计算。

  • Examples of blocking methods in Java:
    There are lots of blocking methods in Java API and good thing is that javadoc clearly informs about it and always mention whether a method call is blocking or not. In General methods related to reading or writing file, opening network connection, reading from Socket, updating GUI synchronously uses blocking call. here are some of most common methods in Java which are blocking in nature:

1) InputStream.read() which blocks until input data is available, an exception is thrown or end of Stream is detected.
2) ServerSocket.accept() which listens for incoming socket connection in Java and blocks until a connection is made.
3) InvokeAndWait() wait until code is executed from Event Dispatcher thread.

  • Best practices while calling blocking method in Java:
    Blocking methods are for a purpose or may be due to limitation of API but there are guidelines available in terms of common and best practices to deal with them. here I am listing some standard ways which I employ while using blocking method in Java

1) If you are writing GUI application may be in Swing never call blocking method in Event dispatcher thread or
in the event handler. for example if you are reading a file or opening a network connection when a button is clicked
don’t do that on actionPerformed() method, instead just create another worker thread to do that job and return from
actionPerformed(). this will keep your GUI responsive, but again it depends upon design if the operation is something which requires user to wait than consider using invokeAndWait() for synchronous update.

2) Always let separate worker thread handles time consuming operations e.g. reading and writing to file, database or
socket.

3) Use timeout while calling blocking method. so if your blocking call doesn’t return in specified time period, consider
aborting it and returning back but again this depends upon scenario. if you are using Executor Framework for managing
your worker threads, which is by the way recommended way than you can use Future object whose get() methods support timeout, but ensure that you properly terminate a blocking call.

4) Extension of first practices, don’t call blocking methods on keyPressed() or paint() method which are supposed to
return as quickly as possible.

5) Use call-back functions to process result of a blocking call.

  • Important points:
  1. If a Thread is blocked in a blocking method it remain in any of blocking state e.g. WAITING, BLOCKED or TIMED_WAITING.

  2. Some of the blocking method throws checked InterrupptedException which indicates that they may allow cancel the task and return before completion like Thread.sleep() or BlockingQueue.put() or take() throws InterruptedException.

  3. interupt() method of Thread class can be used to interuupt a thread blocked inside blocking operation, but this is mere
    a request not guarantee and works most of the time.

Read more: https://javarevisited.blogspot.com/2012/02/what-is-blocking-methods-in-java-and.html#ixzz5QfVAkI3l

14. 什么是不可变类?它对于编写并发应用有何帮助?

什么是不可变类?

不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。

举个例子:String和StringBuilder,String是immutable的,每次对于String对象的修改都将产生一个新的String对象,而原来的对象保持不变,而StringBuilder是mutable,因为每次对于它的对象的修改都作用于该对象本身,并没有产生新的对象。

不可变类的优势

  • 方便构造、测试和使用
  • 线程安全,没有同步问题
  • 不需要拷贝构造方法
  • 不需要实现Clone方法
  • 可以缓存类的返回值,允许hashCode使用惰性初始化方式
  • 不需要防御式复制
  • 适合用作Map的key和Set的元素(因为集合里这些对象的状态不能改变)
  • 类一旦构造完成就是不变式,不需要再次检查
  • 总是“failure atomicity”(原子性失败):如果一个不可变对象抛出异常,它从不会保留一个烦人的或者不确定的状态

如何写一个不可变类?

  • 不提供setter方法,避免对象的域被修改
  • 将所有的域都设置为private final
  • 不允许子类覆盖父类方法。最简单的方法是将class设为final。更好点的方式是将构造方法设为private,同时通过工厂方法来创建实例
  • 如果域包含其他可变类的对象,也要禁止这些对象被修改:
    不提供修改可变对象的方法.

不要共享指向可变对象的引用。不要存储那些传进构造方法的外部可变对象的引用;如果需要,创建拷贝,保存指向拷贝的引用。类似的,在创建方法返回值时,避免返回原始的内部可变对象,而是返回可变对象的拷贝。

写一个不可变类的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.Date;

/**
* 注意实例的变量本身可能是不可变的,也可能是可变的
* 对于所有可变的成员变量,返回时需要复制一份新的
* 不可变的成员变量不用做特殊处理
* */
public final class ImmutableClass
{

/**
* Integer类是不可变的,因为它没有提供任何setter方法来改变值
* */
private final Integer immutableField1;
/**
* String类是不可变的,它也没有提供任何setter方法来改变值
* */
private final String immutableField2;
/**
* Date类是可变的,它提供了改变日期或时间的setter方法
* */
private final Date mutableField;

// 将构造方法声明为private,确保不会有意外情况构造这个类
private ImmutableClass(Integer fld1, String fld2, Date date)
{
this.immutableField1 = fld1;
this.immutableField2 = fld2;
this.mutableField = new Date(date.getTime());
}

// 工厂方法将创建对象的逻辑封装在一个地方
public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
{
return new ImmutableClass(fld1, fld2, date);
}

// 不提供setter方法

/**
* Integer类是不可变的,可以直接返回成员变量的实例
* */
public Integer getImmutableField1() {
return immutableField1;
}

/**
* String类是不可变的,可以直接返回成员变量的实例
* */
public String getImmutableField2() {
return immutableField2;
}

/**
* Date类是可变的,需要注意一下
* 不要返回原始成员变量的引用
* 创建一个新的Date对象,内容和成员变量一样
* */
public Date getMutableField() {
return new Date(mutableField.getTime());
}

@Override
public String toString() {
return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
}
}

不可变类对于编写并发应用有何帮助?

不可变对象天生就是线程安全的。
因为他们不能改变状态,它们不能被线程干扰所中断或者被其他线程观察到内部不一致的状态。

用volatile发布不可变对象:
使用volatile变量(仅可见性,而不具备同步性)不能保证线程安全性,但有时不可变对象也可以提供一种弱形式的原子性。

不可变对象可以在没有任何额外同步的情况下,安全地用于任意线程;甚至发布它时也不需要同步。

扩展知识:

  • 简单地发布安全对象

安全发布对象的条件:

1、通过静态初始化对象的引用;
2、将引用存储到volatile变量或AutomaticReference;
3、将引用存储到final域字段中;
4、将引用存储到由锁正确保护的域中;

在多线程中,常量(变量)最好是声明为不可变类型,即使用final关键字。如下是一个不正确发布的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package net.jcip.examples;

/**
* Holder
* <p/>
* Class at risk of failure if not properly published
*
* @author Brian Goetz and Tim Peierls
*/
public class Holder {
private int n;

public Holder(int n) {
this.n = n;
}

public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}

修正方式:将变量n用final修饰,使之不可变(即线程安全)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package net.jcip.examples;

/**
* Holder
* <p/>
* Class at risk of failure if not properly published
*
* @author Brian Goetz and Tim Peierls
*/
public class Holder {
private final int n;

public Holder(int n) {
this.n = n;
}

public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}

  • Java中支持线程安全保证的类:
    1、置入HashTable、synchronizedMap、ConcurrentMap中的主键以及值,会安全地发布到可以从Map获取他们的任意线程中,无论是直接还是通过迭代器(Iterator)获得。

2、置入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或者synchronizedSet中的元素,会安全地发布到可以从容器中获取它的任意线程中。

3、置入BlockingQueue或ConcurrentLinkedQueue的元素,会安全地发布到可以从容器中获取它的任意线程中。

通常,以最简单最安全的方式发布一个类就是使用静态初始化:

1
public static Holder holder=new Holder(99);

任何线程都可以在没有额外的同步下安全地使用一个安全发布的高效不可变对象。

1
public Map<String,Date> lastLogin=Collections.synchronizedMap(new HashMap<String, Date>());

Date是可变类型,通过Collections.synchronizedMap安全的数据结构使得使用它的结果不可变,从而不需要额外的同步。

  • 安全地共享对象:

在并发程序中,使用和共享对象的一些最有效的策略如下:

1)线程限制:

一个线程限制的对象,通过限制在线程中,而被线程独占,且只能被占有它的线程修改。

2)共享只读(read-only):

一个共享的只读对象,在没有额外同步的情况下,可以被多个线程并发地访问,但任何线程都不能修改它。共享只读对象包括可变对象与高效不可变对象。

3)共享线程安全(thread-safe):

一个线程安全的对象在内部进行同步,所以其它线程无额外同步,就可以通过公共接口随意地访问它。

4)被守护(Guarded):

一个被守护的对象只能通过特定的锁来访问。被守护的对象包括哪些被线程安全对象封装的对象,和已知特定的锁保护起来的已发布对象。

Why String is Immutable or Final in Java

  • The string is Immutable in Java because String objects are cached in String pool. Since cached String literals are shared between multiple clients there is always a risk, where one client’s action would affect all another client.

  • Since caching of String objects was important from performance reason this risk was avoided by making String class Immutable. At the same time, String was made final so that no one can compromise invariant of String class e.g. Immutability, Caching, hashcode calculation etc by extending and overriding behaviors.

  • Another reason of why String class is immutable could die due to HashMap.
    Since Strings are very popular as HashMap key, it’s important for them to be immutable so that they can retrieve the value object which was stored in HashMap. Since HashMap works in the principle of hashing, which requires same has value to function properly. Mutable String would produce two different hashcodes at the time of insertion and retrieval if contents of String was modified after insertion, potentially losing the value object in the map.

1) Imagine String pool facility without making string immutable , its not possible at all because in case of string pool one string object/literal e.g. “Test” has referenced by many reference variables, so if any one of them change the value others will be automatically gets affected i.e. lets say

String A = “Test”
String B = “Test”

Now String B called, “Test”.toUpperCase() which change the same object into “TEST”, so A will also be “TEST” which is not desirable. Here is a nice diagram which shows how String literals are created in heap memory and String literal pool.

Why String is Immutable or Final in Java

2) String has been widely used as parameter for many Java classes e.g. for opening network connection, you can pass hostname and port number as string, you can pass database URL as a string for opening database connection, you can open any file in Java by passing the name of the file as argument to File I/O classes.

In case, if String is not immutable, this would lead serious security threat, I mean someone can access to any file for which he has authorization, and then can change the file name either deliberately or accidentally and gain access to that file. Because of immutability, you don’t need to worry about that kind of threats. This reason also gels with, Why String is final in Java, by making java.lang.String final, Java designer ensured that no one overrides any behavior of String class.

3)Since String is immutable it can safely share between many threads which is very important for multithreaded programming and to avoid any synchronization issues in Java, Immutability also makes String instance thread-safe in Java, means you don’t need to synchronize String operation externally. Another important point to note about String is the memory leak caused by SubString, which is not a thread related issues but something to be aware of.

4) Another reason of Why String is immutable in Java is to allow String to cache its hashcode, being immutable String in Java caches its hashcode, and do not calculate every time we call hashcode method of String, which makes it very fast as hashmap key to be used in hashmap in Java. This one is also suggested by Jaroslav Sedlacek in comments below. In short because String is immutable, no one can change its contents once created which guarantees hashCode of String to be same on multiple invocations.

5) Another good reason of Why String is immutable in Java suggested by Dan Bergh Johnsson on comments is: The absolutely most important reason that String is immutable is that it is used by the class loading mechanism, and thus have profound and fundamental security aspects. Had String been mutable, a request to load “java.io.Writer” could have been changed to load “mil.vogoon.DiskErasingWriter”

扩展阅读:

[译] 不可变类有什么优势,应该如何创建?

15. 你在多线程环境中遇到的最多的问题是什么?你如何解决的?

内存干扰、竞态条件、死锁、活锁、线程饥饿是多线程和并发编程中比较有代表性的问题。这类问题无休无止,而且难于定位和调试。

hexo生成的静态文件如何更新到自己的服务器上

发表于 2018-09-10 | 更新于 2019-05-22 | 分类于 hexo

github pages为每个github用户提供了一个免费的静态空间。

而hexo是一款快速、简洁且高效的博客框架。

hexo + github pages实现一个静态博客,是非常流行的做法。这个方案是非常好的,但有2点不好,一个是博客不是自己的域名,第二个是github.io的访问速度不是很快。

于是,我们就会考虑说怎么样把hexo生成的静态博客网页文件部署到自己的服务器上呢?

hexo部署的内容,可以参考之前的文章:

如何使用github-pages和hexo搭建简单blog

使用hexo-deploy直接发布到github

以下是我的方案:

前提是,你已经利用xxx.github.io项目里面的静态文件,检出副本到你的服务器上,并利用nginx静态代理实现博客的访问。

然后自动更新的思路就是,hexo deploy的时候会提交更新到xxx.github.io项目,利用webhooks,向自己写的一个http服务器发送一个post请求,http服务器接收到post请求,利用shelljs库执行shell脚本,进入检出的xxx.github.io项目服务器副本,并git pull。即可更新自己的博客。

1. 把你的xxx.github.io项目检出到云服务器上

2. 在你的xxx.github.io git项目仓库中建立一个webhooks

webhooks的配置是playload={url}

url为web服务接收http post请求的url地址;

3. Express实现的http服务器接收到http请求后,利用node 的shelljs库,执行服务器的shell脚本

由于请求简单,我这里直接利用Express来作为http的服务。

  • app.js例子如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const express = require('express')
    const shell = require("shelljs");
    const app = express()

    app.post('/cmd', (req, res) => {
    shell.exec("/home/app/build/update-blog.sh");
    res.send('exec ok!')
    })

    app.listen(3000, () => console.log('Example app listening on port 3000!'))

然后在服务器上部署这个http项目

4. shell脚本内的过程就是,cd进入云服务器检出的xxx.github.io项目根目录,然后执行git pull更新。

  • 脚本例子:
    1
    2
    cd /您的服务器上xxx.github.io项目根目录
    git pull

5. 于是每当hexo执行’hexo deoloy’命令向xxx.github.io推送最新的修改后,xxx.github.io的webhooks就会向http服务器发送一个http请求。从而实现blog的更新。

Emacs快速指南

发表于 2018-09-09 | 更新于 2019-05-22 | 分类于 emacs

Emacs 快速指南.(查看版权声明请至本文末尾)

【注意:位于【】之间的内容是译注,比如本行,下同。】

Emacs 键盘命令通常包含 CONTROL 键(有时候以 CTRL 或 CTL 来标示)和
META 键(有时候用 EDIT 或 ALT 来标示)。为了避免每次都要写出全名,我们
约定使用下述缩写:

C- 表示当输入字符 时按住 CONTROL 键。
因此 C-f 就表示:按住 CONTROL 键再输入 f。

M- 表示当输入字符 时按住 META(或 EDIT 或 ALT)键。
如果你的键盘上没有 META 、EDIT 或 ALT 键,用下述方法也等效:
先按一下 ESC 键然后放开,再输入 。我们用 来表示
ESC 键。

重要提示:要退出 Emacs,请用 C-x C-c(两个连续的组合键)。
要退出一个正在运行中的命令,请用 C-g。
下文中左边顶行的“>>”字样用来提示你尝试键盘命令。比如:

[本页当中特意留出一些空白是出于教学目的,请继续往后阅读]

现在输入 C-v (查看下一屏文字)移动到下一屏。
(别紧张,在输入字符 v 的同时注意要按住 CONTROL 键)
从现在开始,每读完当前一屏你都需要这样做一次。

值得注意的是,当你从上一屏滚到下一屏时,中间会有两行的重复;这样做是为
了维持滚屏的连续性,方便你顺畅、连续地阅读。

用编辑器,开门第一件事就是学会在文字中移动。你已经知道了 C-v 可以向下移
动一屏,要往上移,请用 M-v (也就是按住 META 键,然后输入v,如果你没有
META、EDIT 或 ALT 键那么就先按 再按 v)。

试试 M-v,然后再试试 C-v,来回遛几次。

  • 小结(SUMMARY)

以下命令在翻页浏览时相当有用:

C-v     向前移动一屏
M-v     向后移动一屏
C-l     重绘屏幕,并将光标所在行置于屏幕的中央
        (注意是 CONTROL-L,不是 CONTROL-1)

找到光标,留意其附近的文字,然后输入 C-l。
找找光标在哪里,你会发现其附近的文字与之前相同,位置却变为屏幕的中心。
如果你再次输入 C-l ,附近的文字将移动到屏幕的顶端。再次输入 C-l ,
文字将移动到底端。

如果你的键盘上有 PageUp 和 PageDn,也可以用这两个键来滚屏。不过使用
C-v 和 M-v 的效率要更高一些。

  • 基本的光标控制(BASIC CURSOR CONTROL)

整屏的移动很有用,但是如何在文字中精确定位呢?

有几种方式可以选择。用方向键当然可以,不过更有效率的方法是保持双手位于
主键盘区,然后使用 C-p 、 C-b 、 C-f 和 C-n 这四个命令。它们的功能和方
向键是一样的,如下图所示:

                 上一行 C-p
                      :
                      :
向左移 C-b .... 目前光标位置 .... 向右移 C-f
                      :
                      :
                 下一行 C-n

用 C-n 或 C-p 将光标移到上图的中央。
按 C-l,整幅图会被显示在屏幕的中央。

“P N B F”四个字母分别代表了四个词,用这四个词记忆这些组合键会更容易:
P 代表 previous(上一行),N 代表 next(下一行),B 代表 backward(回
退),而 F 则代表 forward(前进)。这些组合键今后将与你形影不离。

按几次 C-n 把光标往下挪到这里。

用 C-f 把光标移动到这一行,然后再用 C-p 往上挪。
注意观察当光标在一行的中央时 C-p 命令的行为。

每行文字都以一个“换行符”结束,“换行符”把行与行区分开来。(通常情况下,
一个文件的最后一行会有一个换行符,但是 Emacs 不强制要求这一点。)

在一行的行头输入 C-b。
光标应该会移动到前一行的行尾,因为光标在回退过程中越过了换行符。

同样 C-f 也可以像 C-b 一样越过换行符。

连按几次 C-b,感受一下光标的移动。
然后按几次 C-f 回到本行的行尾。
再按一次 C-f,光标会移动到下一行。

当你移动光标穿越屏幕的上下边界时,在边界外的文字会移动到屏幕内,这称为
“滚动”(scrolling)。滚动使得光标可以被移动到文字中的任何位置,并且还
不会让光标跑到屏幕外边去。

用 C-n 将光标下移,一直越过屏幕边界,同时观察发生了什么变化。

如果你嫌一个字符一个字符地挪光标太慢,你还可以一个词一个词地跳。M-f
(META-f) 可以将光标往前移动一个词,而 M-b 则是往后移。【这里的“词”指
英文单词,对中文来说,则是指移动到下一个标点符号。】

试试 M-f 和 M-b。

如果光标停在一个词的中间,M-f 会移动到这个词的末尾。如果光标处于词与词
之间的空白处,M-f 会移动到下一个词的末尾。M-b 的功能类似,只是方向相反。

按几次 M-f 和 M-b,中间夹杂一些 C-f 和 C-b。
你可以观察到 M-f 和 M-b 在不同位置上所表现出来的不同行为。

请注意 C-f 、C-b 和 M-f 、M-b 两对之间的类比关系。通常的惯例是:META 系
列组合键用来操作“由语言定义的单位(比如词、句子、段落)”,而 CONTROL
系列组合键用来操作“与语言无关的基本单位(比如字符、行等等)”。

类似的惯例在“行”与“句子”之间也同样适用:C-a 和 C-e 可以将光标移动到
“一行”的头部和尾部;而 M-a 和 M-e 则将光标移动到“一句”的头部和尾部。

按两次 C-a,再按两次 C-e。
按两次 M-a,再按两次 M-e。

想一想为什么重复的 C-a 命令会没有作用,而重复的 M-a 命令则会让光标不断
地回退到上一个句子。虽然这个类比规律并不严格,但是很自然。

光标停留的位置也可以称作“点位”(point)。或者干脆说,光标指示出了屏幕
上“点位”在文本中的位置。

这里对简单的光标移动命令做一个总结,其中也包括了整词和整句的移动:

C-f     向右移动一个字符
C-b     向左移动一个字符

M-f     向右移动一个词【对中文是移动到下一个标点符号】
M-b     向左移动一个词【对中文是移动到上一个标点符号】

C-n     移动到下一行
C-p     移动到上一行

C-a     移动到行首
C-e     移动到行尾

M-a     移动到句首
M-e     移动到句尾

把上面所有的命令都练习几次,这些可都是最常用的命令。

这里还要介绍两个重要的光标移动命令:M-< (META 小于号)可以将光标移动到
所有文字的最开头;M-> (META 大于号)可以将光标移动到所有文字的最末尾。

注意,在大部分键盘上,小于号(<)需要用上档键(Shift)来输入,所以在这
些键盘上你应该用 Shift 键来输入 M-<,如果不按 Shift 键,你输入的会是
M-comma(META 逗号)。

试一试 M-< ,移到本快速指南的最开始。
然后再按几次 C-v 回到这里。

试一试 M-> ,移到本快速指南的最末尾。
然后再按几次 M-v 回到这里。

如果你的键盘上有方向键的话,也可以用它们来移动光标。不过我们有三个理由
推荐你学习 C-b 、C-f 、C-n 、和 C-p:(1)它们在任何键盘上都能用。(2)
当你熟练使用 Emacs 之后,你会发现用这些组合键比用方向键要快得多,因为你
的手不需要离开打字区。(3)一旦你习惯了使用这些组合键,你也可以很容易地
适应其它更高级的光标移动命令。

大部分的 Emacs 命令接受数字参数,并且对于多数命令而言,这些数字参数的作
用是指定命令的重复次数。为一个命令指定数字参数(也就是重复次数)的方法
是:先输入 C-u,然后输入数字作为参数,最后再输入命令。如果你有META (或
EDIT 或 ALT)键,那么还有另一种办法:按住 META 键不放,然后输入数字。不
过我们还是建议你用 C-u,因为它在任何终端机上都能用。这种数字参数也称为
“前缀参数”,意思是说这个参数是先于使用它的命令而输入的。

举例来说, C-u 8 C-f 会向前移动 8 个字符。

为 C-n 或者 C-p 指定一个数字参数,这样你可以只用一个命令就把光标移动
到本行的附近。

虽然大部分命令把数字参数解释为其重复次数,但是也有些命令例外,它们将数
字参数另做它用。比如有些命令(我们目前还没学到)仅仅将前缀参数作为一个
标志――只要给出有一个前缀参数,不管其值为何,它都会改变命令的功能。

而 C-v 和 M-v 则属于另一种类型的例外。当给定一个参数时,它们将滚动你指
定的“行数”,而不是“屏数”。举例来说,C-u 8 C-v 将文本向下滚动 8 行。

现在试试看,输入 C-u 8 C-v。

这个命令应该已经将文字向上滚动了 8 行。如果你想将它再次地向下滚动,你可
以给定一个参数然后执行 M-v。

如果你正在使用图形界面,比如 X 或者微软的 Windows,那么在 Emacs窗
口的一边应该有一个长方形的区域叫“滚动条”。你可以用鼠标操纵滚动条来滚动
文字。

如果你的鼠标有滚轮的话,你也可以使用滚轮来滚动。

  • 如果 EMACS 失去响应(IF EMACS STOPS RESPONDING)

如果 Emacs 对你的命令失去响应,你可以用 C-g 来安全地终止这条命令。C-g
也可以终止一条执行过久的命令。

C-g 还可以取消数字参数和只输入到一半的命令。

输入 C-u 100 设定一个值为 100 的数字参数,然后按 C-g。
现在再按 C-f,光标应该只会移动一个字符,因为你已经用 C-g 取消了参数。

如果你不小心按了一下 ,你也可以用 C-g 来取消它。
【这个说法似乎有问题,因为按照这个按键顺序输入的应该是 C-M-g。
取消 的正确做法是再连按两次 。】

  • 被禁用的命令(DISABLED COMMANDS)

有一些 Emacs 命令被“禁用”了,以避免初学者在不了解其确切功能的情况下误
用而造成麻烦。

如果你用到了一个被禁用的命令,Emacs 会显示一个提示消息,告诉你这个命令
到底是干什么的,询问你是否要继续,并在得到你的肯定之后再执行这命令。

如果你真的想用这条命令,在 Emacs 询问你的时候应该按空格。一般来说,如果
你不想用,就按“n”。

试试 C-x C-l (这是一个被禁用的命令)
然后用 n 来回答询问。

  • 窗格(WINDOWS)

Emacs 可以有多个“窗格”,每个窗格显示不同的文字。后面会介绍怎么对付多个窗
格,现在我们先学会如何关掉多余的窗格。其实也很简单:

C-x 1   只保留一个窗格(也就是关掉其它所有窗格)。

也就是先按 CONTROL-x 然后再按 1。C-x 1 会保留光标所在的窗格,并将其扩大
到整个屏幕,同时关掉所有其它的窗格。

把光标移到本行然后输入 C-u 0 C-l。

输入 C-h k C-f。观察当一个新窗格出现时当前窗格(用来显示
C-f 命令的文档)是如何缩小的。

输入 C-x 1 关掉文档窗格。

有一系列命令是以 CONTROL-x 开始的,这些命令许多都跟“窗格、文件、缓冲区
【缓冲区(buffer)会在后文详细介绍】”等等诸如此类的东西有关,其中有些
命令可能包含了 2 个、3 个或者 4 个字符。

  • 插入与删除(INSERTING AND DELETING)

插入文字很简单,直接敲键盘就可以了。普通的字符,比如 A、7、* 等等,会
随着你的输入而插入。要插入一个换行符,输入 (这个键在键盘上有
时会被标注成“Enter”)。

你可以用 来删除光标左边的字符,这个键通常被标注为“Backspace”――跟
你在 Emacs 之外的用法应该一样,删除最后一个输入的字符。

你的键盘上可能有另外一个键,标注着 ,但那个不是我们所说的 。

现在就来试试――敲点字,然后按几下 删除它们。
不用担心文件被修改,你做什么都没关系,这里就是专给你练习用的。

如果一行文字很长、超出了窗格的宽度,显示不下的部分会在紧邻的下一行继续
显示。如果你使用的是图形界面,文本区域两边的狭窄区域(左右“边缘”)会出
现小小的转弯箭头,表明这是某一行的接续显示。如果你使用的是文本终端,接
续显示由屏幕最右边一列的一个反斜线(“\”)来表示。

输入文字,一直到屏幕的右边界,然后继续。
你会看到一个接续行出现。

用 删掉一些文字,直到此行长度小于窗格宽度,接续行就消失了。

换行符跟其它字符一样可以被删除。两行中间的换行符被删除后,这两行将会合
并成一行。如果合并后的这一行太长,超出了窗格宽度,它就会以一个接续行来
显示。

移动光标到某行的开头并输入 。
这时该行将与其前一行一起被合并为一行。

输入 重新插入你刚才删除的换行符。

是一个特殊的键,因为按下这个键后,得到的可能不仅仅是一个换行
符。根据周围文本的不同,Emacs 可能会在换行符之后插入一些空白字符,这样,
当你在新的一行开始打字时,文本会自动与前一行对齐。

这是一个自动缩进的例子。
在这一行的末尾输入 。

可以看到,在插入换行符之后,Emacs 插入了空格,因此光标移动到了“在”这个
字的下面。

前面讲过,大部分的 Emacs 命令都可以指定重复次数,这其中也包括输入字符的
命令。重复执行输入字符的命令实际上就是输入多个相同的字符。

试试 C-u 8 *,这将会插入 **。

好,现在你应该已经掌握了最基本的的文本插入和修改功能,其实删除还可以
“以词为单位”进行,下面是一个关于“删除”操作的小结:

<DEL>        删除光标前的一个字符
C-d          删除光标后的一个字符

M-<DEL>      移除光标前的一个词
M-d          移除光标后的一个词

C-k          移除从光标到“行尾”间的字符
M-k          移除从光标到“句尾”间的字符

【可能你已经注意到了“删除(delete)”和“移除(kill)”的用词区别,后
文会有详细说明。】

注意“ 和 C-d”还有“M- 和 M-d”是根据前述惯例从 C-f和 M-f 衍生
出来的(其实不是控制字符,我们先忽略这一点)。C-k和 M-k 的关系在
某种程度上与 C-e 和 M-e 一样――如果把“一行”和“一句”作一个类比的话。

你也可以用一种通用的办法来移除缓冲区里的任何一部分:首先把光标移动到你
想要移除的区域的一端,然后按 C-(指空格)【注意,C- 往
往被中文用户设定成输入法热键,如果这样,C- 就被系统拦截而无法传递
给 Emacs 了,在这种情况下可以使用C-@。】,然后将光标移动到你准备移除的
文字的另一端。这个时候, Emacs 会高亮光标和你按下 C- 之间的文本。
最后,按下 C-w 。这样就可以把位于这两点之间的所有文字移除了。

移动光标到上一段开头的“你”字。
输入 C- 。Emacs 应该会在屏幕的下方显示一个“Mark set”的消息。
移动光标到第二行中的“端”字。
输入 C-w,从“你”开始到“端”之前的文字被全部移除。

注意,“移除(kill)”和“删除(delete)”的不同在于被移除的东西可以被重新
插入(在任何位置),而被删除的就不能使用相同的方法重新插入了(不过可以
通过撤销一个删除命令来做到,后文会提到)。【实际上,移除掉的东西虽然看
起来“消失”了,但实际上被 Emacs 记录了下来,因此还可以找回来;而删除掉
的东西虽然也可能还在内存里,但是已经被 Emacs“抛弃”了,所以就找不回来
了。】重新插入被移除的文字称为“召回(yank)”。一般而言,那些可能消除很
多文字的命令会把消除掉的文字记录下来(它们被设定成了“可召回”),而那些
只消除一个字符或者只消除空白的命令就不会记录被消除的内容(自然你也就无
法召回了)。

移动光标到一非空白行的行头,然后输入 C-k 移除那一行上的文字。

再次 C-k,你可以看到它移除了跟在那一行后面的换行符。

注意,单独的 C-k 会把一行的内容移除,而第二个 C-k 则会移除换行符,并使
其后所有的行都向上移动。C-k 处理数字参数的方式很特别,它会把参数指定的
那么多行连同其后的换行符一起移除,而不仅仅是重复 C-k 而已。比如 C-u 2
C-k 会把两行以及它们的换行符移除;而如果只是输入 C-k 两次显然不是这个结
果。

重新插入被移除的文字恢复的动作称为“召回(yanking)”。(就好像把别人从你身边
移走的东西又猛力地拉回来。)你可以在你删除文字的地方召回,也可以在别的
地方召回,还可以多次召回同样的文字以得到它的多个拷贝。很多其它的编辑器
把移除和召回叫做“剪切”和“粘贴” (详情可见 Emacs 使用手册里的术语表)。

召回的命令是 C-y。它会在光标所在处插入你最后移除的文字。

试试看,输入 C-y 将文字召回。

如果你一次连按了好几下 C-k,那么所有被移除的行会被存储在一起,只要一个
C-y 就可以把它们都召回。

在这里试试,连续按几次 C-k。

现在再来恢复刚刚被我们移除的文字:

按 C-y。然后把光标往下移动几行,再按一次 C-y。
现在你应该知道怎么复制文字了。

C-y 可以召回最近一次移除的内容,那如何召回前几次移除的内容呢?它们当然
没有丢,你可以用 M-y 来召回它们。在用 C-y 召回最近移除的文字之后,紧接
着再按 M-y 就可以召回再前一次被移除的内容,再按一次 M-y 又可以召回再上
一次的……连续使用 M-y 直到找到你想要召回的东西,然后什么也不用做,继续
编辑就行了。

如果连续按 M-y 很多次,你可能会回到起始点,也就是最近移除的文字。
【看得出这实际上是一个环。】

移除一行,移动一下光标,然后再移除另外一行。
按 C-y 将第二次移除的那行召回来。
紧接着再按 M-y,它将会被第一次移除的那行取代。
试着再按几下 M-y 看看会发生什么。
再继续,直到第二行被召回来,然后再做个几次。
如果感兴趣,你可以试着给 M-y 指定一个正的或负的参数。

  • 撤销(UNDO)

如果你修改了一段文字,又觉得改得不好,可以用 undo 命令进行撤销:C-/。

通常 C-/ 会消除一个命令所造成的所有改变;如果你在一行中连续多次地使用
C-/,你会把以前的命令也依次撤销。

但是有两个例外:
1) 没有改变文字的命令不算(包括光标移动命令和滚动命令)
2) 从键盘输入的字符以组为单位――每组最多 20 个字符――来进行处理。
(这是为了减少你在撤销“插入文字”动作时需要输入 C-/ 的次数)

用 C-k 将这一行移除,然后输入 C-/ ,它会再次出现。

C-_ 也是撤销命令;它的作用跟 C-/ 一样,但是它比较容易多次输入。在
某些终端上,输入 C-/ 实际上向 Emacs 发送的是 C-_ 。
另外, C-x u 和 C-/ 完全一样,但是按起来有些麻烦。

数字参数对于 C-/ 、 C-_ 和 C-x u 的意义是执行撤销的重复次数。

  • 文件(FILE)

想保存工作成果就要记得存盘,否则一旦退出 Emacs 你编辑的文字就会丢失。要
存盘,就要在编辑前“寻找”到一个存盘文件。(这个过程通常也被称为“访问”
文件。)

寻找到一个文件意味着你可以在 Emacs 里查看这个文件的内容。从许多角度看,
这就等于你在直接编辑这个文件,只是你所做的修改只有在“存盘”的时候才会
被写入文件。也正因为如此,你可以丢弃一个写到一半的文件而不必把这个残缺
文件也保存到计算机上。在存盘的时候,Emacs 会把存盘前的文件重命名保存,
以防你改完之后又想反悔。

在屏幕的下方,你应该能够看到头部有短线“-”的一行,行首通常是一些诸如“
-:— TUTORIAL.cn”的文字,这些文字代表了你当前正在访问的文件。比如你现
在正在访问的文件是对 Emacs 快速指南的一份临时拷贝,叫做“TUTORIAL.cn”。
每当Emacs 寻找到一个文件,文件名就会出现在这个位置。

寻找文件的命令有一个特点,那就是你必须给出文件名。我们称这个命令“读入
了一个参数”(在这里,这个参数显然就是文件名)。在你输入这条命令之后:

C-x C-f   寻找一个文件

Emacs 会提示你输入文件名。你输入的文件名会出现在屏幕最底端的一行,这一
行被称为小缓冲(minibuffer),在小缓冲里你可以使用通常的 Emacs 编辑命令
来编辑文件名。

在小缓冲里输入文件名(其实输入其它东西也一样)时可以用 C-g 取消。

输入 C-x C-f,然后输入 C-g
这会关掉小缓冲,同时也会取消使用小缓冲的 C-x C-f 命令。
当然了,你也没有找任何文件。

用 结束文件名的输入。之后,小缓冲会消失,C-x C-f 将会去寻找你
指定的文件。小缓冲在 C-x C-f 命令结束之后也会消失。

文件被显示在了屏幕上,你可以开始编辑了。存盘用这条命令:

C-x C-s   储存这个文件

这条命令把 Emacs 中的文字存储到文件中。第一次存盘的时候 Emacs 会将原文
件重命名以备份。重命名的规则通常是在原文件名之后添加一个“~”字符。
【对许多人来说,这是一个烦人的特性,关掉文件备份可以用如下命令:
M-x customize-variable make-backup-files 】

存盘结束后,Emacs 会显示写入文件的文件名。你最好养成经常存盘的习惯,这
可以减少系统崩溃和死机给你带来的损失(也可参见下面的“自动保存”一节)。

输入 C-x C-s TUTORIAL.cn 。
这将会把该指南保存为一个名为 TUTORIAL.cn 的文件,并且在屏幕的下方显
示一条消息:“Wrote …TUTORIAL.cn”。

你不但可以寻找一个已有的文件来查看或编辑,还可以寻找一个不存在的文件。
实际上这正是 Emacs 创建新文件的方法:找到不存在的新文件。事实上,只有
在存盘的时候,Emacs 才会真正创建这个文件。而在这之后的一切就跟编辑一个
已有文件没有区别了。

  • 缓冲区(BUFFER)

你可以用 C-x C-f 找到并打开第二个文件,但第一个文件仍然在 Emacs 中。要
切回第一个文件,一种办法是再用一次 C-x C-f。这样,你就可以在 Emacs 中同
时打开多个文件。

Emacs 把每个编辑中的文件都放在一个称为“缓冲区(buffer)”的地方。每寻
找到一个文件,Emacs 就在其内部开辟一个缓冲区。用下面的命令可以列出当前
所有的缓冲区:

C-x C-b   列出缓冲区

现在就试一下 C-x C-b

观察一下缓冲区是如何被命名的,它很可能跟与其对应的文件同名。实际上,一
个 Emacs 窗格里的任何文字都是某个缓冲区的一部分。

输入 C-x 1 离开缓冲区列表

不管存在多少缓冲区,任何时候都只能有一个“当前”缓冲区,也就是你正在编
辑的这个。如果你想编辑其它的缓冲区,就必须“切换”过去。上面讲过,用
C-x C-f 是一种办法。不过还有一个更简单的办法,那就是用 C-x b。用这条命
令,你必须输入缓冲区的名称。

通过输入 C-x C-f foo 创建一个名为“foo”的文件。
然后输入 C-x b TUTORIAL.cn 回到这里。

大多数情况下,缓冲区与跟其对应的文件是同名的(不包括目录名),不过这也
不是绝对的。用 C-x C-b 得到的缓冲区列表总是显示缓冲区名。

缓冲区未必有对应文件。显示缓冲区列表的缓冲区(叫做“Buffer List”)就
是这样。这个 TUTORIAL.cn 缓冲区起初没有对应的文件,但是现在有了,因为
在前一节你输入了 C-x C-s , 将它保存成了一个文件。

“Messages”缓冲区也没有对应文件,这个缓冲区里存放的都是在 Emacs 底部
出现的消息。

输入 C-x b Messages 瞅瞅消息缓冲区里都有什么东西。
然后再输入 C-x b TUTORIAL.cn 回到这里。

如果你对某个文件做了些修改,然后切换到另一个文件,这个动作并不会帮你把
前一个文件存盘。对第一个文件的修改仍然仅存在于 Emacs 中,也就是在它对
应的缓冲区里。并且,对第二个文件的修改也不会影响到第一个文件。这很有用,
但也意味着你需要一个简便的办法来保存第一个文件的缓冲区。先切换回那个缓
冲区,再用 C-x C-s 存盘,太麻烦了。你需要一个更简便的方法,而 Emacs 已
经为你准备好了:

C-x s   保存多个缓冲区

C-x s 会找出所有已被修改但尚未存盘的缓冲区,然后向你逐个询问:是否需要
存盘?

插入一行文字,然后输入 C-x s。
它应该会问你,是否要储存名为 TUTORIAL.cn 的缓冲区?
按“y”告诉它你想存盘。

  • 命令集扩展(EXTENDING THE COMMAND SET)

Emacs 的命令就像天上的星星,数也数不清。把它们都对应到 CONTROL 和 META
组合键上显然是不可能的。Emacs 用扩展(eXtend)命令来解决这个问题,扩展
命令有两种风格:

C-x     字符扩展。  C-x 之后输入另一个字符或者组合键。
M-x     命令名扩展。M-x 之后输入一个命令名。

很多扩展命令都相当有用,虽然与你已经学过的命令比起来,他们可能不那么常
用。我们早已经见过一些扩展命令了,比如用 C-x C-f 寻找文件和用 C-x C-s
保存文件;退出 Emacs 用的 C-x C-c 也是扩展命令。(不用担心退出 Emacs 会
给你带来什么损失,Emacs 会在退出之前提醒你存盘的。)

如果你使用图形界面,你不需要任何特殊的命令来切换 Emacs 和其他应用程序。
你可以使用鼠标或者窗口管理器的命令。然而,如果你使用只能同时显示一个应
用程序的文本终端,你需要“挂起” Emacs ,以切换到其他的应用程序。

C-z 可以暂时离开 Emacs――当然,你还可以再回来。在允许 C-z 的系统中,C-z
会把 Emacs“挂起”,也就是说,它会回到 shell但不杀死 Emacs 的进程。在常
用的 shell 中,通常可以用“fg”或者“%emacs”命令再次回到 Emacs 中。

你最好在打算退出登陆的时候再用 C-x C-c。在把 Emacs 当做一个临时的编辑
器的时候(比如被一个邮件处理程序调用),也可以用 C-x C-c 退出。

C-x 的扩展命令有很多,下面列出的是你已经学过的:

C-x C-f         寻找文件。
C-x C-s         保存文件。
C-x C-b         列出缓冲区。
C-x C-c         离开 Emacs。
C-x 1           关掉其它所有窗格,只保留一个。
C-x u           撤销。

用命令名扩展的命令通常并不常用,或只用在部分模式下。比如
replace-string(字符串替换)这个命令,它会把一个字符串替换成另一个。在
输入 M-x 之后,Emacs 会在屏幕底端向你询问并等待你输入命令名。如果你想
输入“replace-string”,其实只需要敲“repl s”就行了,Emacs 会帮你自
动补齐。输入完之后按 提交。

字符串替换命令需要两个参数――被替换的字符串和用来替换它的字符串。每个
参数的输入都以换行符来结束。

将光标移到本行下面第二行的空白处,然后输入
M-x repl schangedaltered。

【以下保留一行原文,以应练习之需:】
Notice how this line has changed: you’ve replaced…

请注意这一行的变化:在光标之后的范围内,你已经将“changed”这个词――不
论它在哪里出现――全部用“altered”替换掉了。

  • 自动保存(AUTO SAVE)

如果你已经修改了一个文件,但是还没来得及存盘你的计算机就罢工了,那么你
所做的修改就很可能会丢失。为了避免这样的不幸发生,Emacs 会定期将正在编
辑的文件写入一个“自动保存”文件中。自动保存文件的文件名的头尾各有一个
“#”字符,比如你正在编辑的文件叫“hello.c”,那么它的自动保存文件就叫
“#hello.c#”。这个文件会在正常存盘之后被 Emacs 删除。

所以,假如不幸真的发生了,你大可以从容地打开原来的文件(注意不是自动保
存文件)然后输入 M-x recover file 来恢复你的自动保存文件。在
提示确认的时候,输入 yes。

  • 回显区(ECHO AREA)

如果 Emacs 发现你输入多字符命令的节奏很慢,它会在窗格的下方称为“回显区”
的地方给你提示。回显区位于屏幕的最下面一行。

  • 状态栏(MODE LINE)

位于回显区正上方的一行被称为“状态栏”。状态栏上会显示一些信息,比如:

-:**- TUTORIAL.cn 63% L749 (Fundamental)

状态栏显示了 Emacs 的状态和你正在编辑的文字的一些信息。

你应该知道文件名的意思吧?就是你找到的那个文件嘛。-NN%– 显示的是光标在
全文中的位置。如果位于文件的开头,那么就显示 –Top– 而不是 –00%–;如
果位于文件的末尾,就显示 –Bot–。如果文件很小,一屏就足以显示全部内容,
那么状态栏会显示 –All–。

“L” 和其后的数字给出了光标所在行的行号。

最开头的星号(*)表示你已经对文字做过改动。刚刚打开的文件肯定没有被改动
过,所以状态栏上显示的不是星号而是短线(-)。

状态栏上小括号里的内容告诉你当前正在使用的编辑模式。缺省的模式是
Fundamental,就是你现在正在使用的这个。它是一种“主模式”。

Emacs 的主模式林林总总。有用来编辑程序代码的――比如 Lisp 模式;也有用
来编辑各种自然语言文本的――比如 Text 模式。任何情况下只能应用一个主模
式,其名称会显示在状态栏上,也就是现在显示“Fundamental”的地方。

主模式通常会改变一些命令的行为。比方说,不管编辑什么语言的程序代码,你
都可以用一个相同的命令来添加注释。但是在不同的语言中注释的语法往往是不
同的,这时不同的主模式就会用各自不同的语法规则来添加注释。主模式都是可
以用 M-x 启动的扩展命令,M-x fundamental-mode 就可以切换到 Fundamental
模式。

编辑自然语言文本――比如现在――应该用 Text 模式。

输入 M-x text-mode 。

别担心,什么都没变。不过细心一些可以发现,M-f 和 M-b 现在把单引号(’)
视为词的一部分了。而在先前的 Fundamental 模式中,M-f 和 M-b 都将单引号
视为分隔单词的符号。

主模式通常都会搞一些类似的小动作,因为很多命令其实完成的是“相同的工
作”,只是在不同环境下会有不同的工作方式而已。【所谓“求同存异”,在
Emacs 里得到了很好的体现】

用 C-h m 可以查看当前主模式的文档。

把光标移动到下一行。
用 C-l C-l 将本行带到屏幕的最上方。
输入 C-h m,看看 Text 模式与 Fundamental 模式有哪些不同。
输入 C-x 1 关掉文档窗格。

主模式之所以称之为“主(major)”模式,是因为同时还有“辅模式”(minor
mode)存在。辅模式并不能替代主模式,而是提供一些辅助的功能。每个辅模式
都可以独立地开启和关闭,跟其它辅模式无关,跟主模式也无关。所以你可以不
使用辅模式,也可以只使用一个或同时使用多个辅模式。

有一个叫做自动折行(Auto Fill)的辅模式很有用,特别是在编辑自然语言文本
的时候。启用自动折行后,Emacs 会在你打字超出一行边界时自动替你换行。

用 M-x auto-fill-mode 启动自动折行模式。再用一次这条命令,自
动折行模式会被关闭。也就是说,如果自动折行模式没有被开启,这个命令会开
启它;如果已经开启了,这个命令会关闭它。所以我们说,这个命令可以用来
“开关(toggle)”模式。

现在输入 M-x auto-fill-mode 。然后随便敲点什么,直到你看到它
分成两行。你必须敲一些空格,因为 Auto Fill 只在空白处进行断行。
【输入空格对英文来说是必须的,而对中文则不必。】

行边界通常被设定为 70 个字符【这里指英文字符】,你可以用 C-x f 命令配合
数字参数来重新设定它。

输入 C-x f 并传递参数 20: C-u 2 0 C-x f。
然后输入一些文字,观察 Emacs 的自动折行动作
最后再用 C-x f 将边界设回 70。

如果你在段落的中间做了一些修改,那么自动折行模式不会替你把整个段落重新
折行,你需要用 M-q 手动折行。注意,光标必须位于你需要折行的那一段里。

移动光标到前一段中,然后输入 M-q。

  • 搜索(SEARCHING)

Emacs 可以向前或向后搜索字符串(“字符串”指的是一组连续的字符)。搜索命
令是一个移动光标的命令:搜索成功后,光标会停留在搜索目标出现的地方。

Emacs 的搜索命令是“渐进的(incremental)”。意思是搜索与输入同时进行:
你在键盘上一字一句地输入搜索词的过程中,Emacs 就已经开始替你搜索了。

C-s 是向前搜索,C-r 是向后搜索。不过手别这么快!别着急试。

在按下 C-s 之后,回显区里会有“I-search”字样出现,表明目前 Emacs 正处
于“渐进搜索”状态,并等待你输入搜索字串。按 可以结束搜索。

输入 C-s 开始一个搜索。注意敲慢一点,一次输入一个字符。
慢慢输入“cursor”这个词,每敲一个字都停顿一下并观察光标。
现在你应该已曾经找到“cursor”这个词了。
再按一次 C-s,搜索下一个“cursor”出现的位置。
现在按四次 ,看看光标是如何移动的。
敲 结束搜索。

看仔细了么?在一次渐进式搜索中,Emacs 会尝试跳到搜索目标出现的位置。要
跳到下一个命中位置,就再按一次 C-s。如果找不到目标,Emacs 会发出“哔”
的一声,告诉你搜索失败。在整个过程中,都可以用 C-g 来终止搜索。【你会发
现 C-g 会让光标回到搜索开始的位置,而 则让光标留在搜索结果上,
这是很有用的功能。】

在渐进式搜索中,按 会“撤回”到最近一次搜索的命中位置。如果之前没
有一个命中, 会抹去搜索字符串中的最后一个字符。比如你已经输入了
“c”,光标就停在“c”第一次出现的位置,再输入“u”,光标停在“cu”第一次出现
的位置,这时再按 ,“u”就从搜索字串中消失了,然后光标会回到“c”第
一次出现的位置。

另外,如果你在搜索的时候输入了 control 或者 meta 组合键的话,搜索可能会
结束。(也有例外,比如 C-s 和 C-r 这些用于搜索的命令。)

前面说的都是“向下”搜索,如果想“向上”搜索,可以用 C-r。C-r 与 C-s
相比除了搜索方向相反之外,其余的操作都一样。

  • 多窗格(MULTIPLE WINDOWS)

Emacs 的迷人之处很多,能够在屏幕上同时显示多个窗格就是其中之一。

移动光标到这一行,然后输入 C-l C-l。

现在输入 C-x 2,它会将屏幕划分成两个窗格。
这两个窗格里显示的都是本篇快速指南,而光标则停留在上方的窗格里。

试试用 C-M-v 滚动下方的窗格。
(如果你并没有 META 键,用 ESC C-v 也可以。)
【向上滚动是 C-M-S-v,也就是同时按住 CONTROL、META 和 SHIFT 再按 v】

输入 C-x o(“o”指的是“其它(other)”),
将光标转移到下方的窗格。

在下方的窗格中,用 C-v 和 M-v 来滚动。
同时继续在上方的窗格里阅读这些指导。

再输入 C-x o 将光标移回到上方的窗格里。
光标会回到它在上方窗格中原本所在的位置。

连续使用 C-x o 可以遍历所有窗格。“被选中的窗格”,也就是绝大多数的编辑
操作所发生的地方,是在你不打字时闪烁光标的那个窗格。其他的窗格有它们自
己的光标位置; 如果你在图形界面下运行 Emacs ,这些光标是镂空的长方形。

当你在一个窗格中编辑,但用另一个窗格作为参考的时候,C-M-v 是很有用的命
令。无需离开被选中的窗格,你就可以用 C-M-v 命令滚动另外一个窗格中的文
字。【比如翻译和校对就很适合用这种方式进行。】

C-M-v 是一个 CONTROL-META 组合键。如果你有 META (或 Alt)键的话,可以
同时按住CONTROL 和 META 键并输入 v。CONTROL 和 META 键先按哪个都可以,
因为它们只是用来“修饰(modify)”你输入的字符的。

如果你并没有 META 键,你也可以用 ESC 来代替,不过这样的话就要注意按键顺
序了:你必须先输入 ESC ,然后再输入 CONTROL-v。CONTROL-ESC v 是没用的,
因为 ESC 本身是一个字符键,而不是一个修饰键(modifier key)。

(在上方窗格里)输入 C-x 1 关掉下方窗格。

(如果你在下方的窗格里输入 C-x 1,那么就会关掉上方的窗格。你可以把这个
命令看成是“只保留一个窗格”――就是我们正在编辑的这个。)

不同的窗格可以显示不同的缓冲区。如果你在一个窗格里用 C-x C-f 打开了一个
文件,另一个窗格并不会发生什么变化。任何一个窗格里都可以用来打开文件。

用下面的方法可以在一个新开窗格里打开文件:

输入 C-x 4 C-f,紧跟着输入一个文件名,再用 结束。
可以看到你指定的文件出现在下方的窗格中,同时光标也跳到了那里。

输入 C-x o 回到上方的窗格,然后再用 C-x 1 关掉下方窗格。

  • 多窗口(MULTIPLE FRAMES)

Emacs 可以创建多个窗口。窗口由许多窗格以及菜单、滚动条、回显区等组成。
在图形界面下,多个窗口可以同时显示出来。在文本终端中,只能同时显示一个
窗口。

输入 M-x make-frame 。
可以看到一个新的窗口出现在了你的屏幕上。

你可以在新的窗口里做最初的窗口里可以做的任何事情。第一个窗口没有什么特
别的。

输入 M-x delete-frame .
这个命令将会关闭选中的窗口。

你也可以通过图形系统来关闭某个窗口(通常是在窗口上面的某个角落里的一个
“X”按钮)。如果你关闭的是 Emacs 进程的最后一个窗口, Emacs 将会退出。

  • 递归编辑(RECURSIVE EDITING LEVELS)

有时候你会进入所谓的“递归编辑”。递归编辑状态由位于状态栏的方括号所指
示,其中包含了用小括号来指明的模式名称。比如说,你有时可能会看到
[(Fundamental)],而不是 (Fundamental)。【比如在用 M-% 进行交互式替换的
时候你又用了 C-s 进行搜索,这时替换模式并没有结束,但你又进入了搜索模式,
这就是所谓的递归编辑。】

离开递归编辑可以用 ESC ESC ESC。这是一个最通用的“离开”命令,你甚至可
以使用它来关掉多余的窗格,或者离开小缓冲。

输入 M-x 进入小缓冲;然后输入 ESC ESC ESC 离开。

你不能用 C-g 退出递归编辑,因为 C-g 的作用是取消“本层递归编辑之内”的
命令和其参数(arguments)。

  • 获得更多帮助(GETTING MORE HELP)

本快速指南的目的仅仅是帮助你在 Emacs 的海洋里下水,不至于束手无策望洋兴
叹。有关 Emacs 的话题可谓汗牛充栋,这里自然是难尽万一。不过 Emacs 很理
解你求知若渴的心情,因为它提供的强大功能实在是太多了。为此,Emacs 提供
了一些命令来查看 Emacs 的命令文档,这些命令都以 CONTROL-h 开头,这个字
符也因此被称为“帮助(Help)字符”。

要使用帮助(Help)功能,请先输入 C-h,然后再输入一个字符以说明你需要什
么帮助。如果你连自己到底需要什么帮助都不知道,那么就输入 C-h ?,Emacs
会告诉你它能提供了哪些帮助。如果你按了 C-h 又想反悔,可以用 C-g 取消。

(如果你按 C-h 之后没有任何帮助信息显示出来,那么试试 F1 键或者 M-x help 。)

最基本的帮助功能是 C-h c。输入 C-h c 之后再输入一个组合键,Emacs 会给出
这个命令的简要说明。

输入 C-h c C-p。

显示的消息应该会是这样:

C-p runs the command previous-line

这条消息显示了 C-p 命令对应的函数名。命令的功能由函数完成,所以函数名
本身也可以被看成是最简单的文档――至少对于你已经学过的命令来说,它们的函
数名足以解释它们的功能了。

多字符命令一样可以用 C-h c 来查看。

想得到更多的信息,请把 C-h c 换成 C-h k 试试看。

输入 C-h k C-p。

上面的命令会新打开一个 Emacs 窗格以显示函数的名称及其文档。你读完之后可
以用 C-x 1 关掉这个帮助窗格。当然你并不需要立即这样做,你完全可以先在编
辑窗格里做点别的事情,然后再关掉帮助窗格。

还有一些其它有用的 C-h 命令:

C-h f 解释一个函数。需要输入函数名。

试试看,输入 C-h f previous-line 。
Emacs 会给出它所知道的所有有关“实现 C-p 命令功能的函数”的信息。

C-h v 用来显示 Emacs 变量的文档。Emacs 变量可以被用来“定制 Emacs 的行
为”。同样,你需要输入变量的名称。

C-h a 相关命令搜索(Command Apropos)。
输入一个关键词然后 Emacs 会列出所有命令名中包含此关键词
的命令。这些命令全都可以用 M-x 来启动。对于某些命令来说,
相关命令搜索还会列出一两个组合键。

输入 C-h a file 。

Emacs 会在另一个窗格里显示一个 M-x 命令列表,这个列表包含了所有名称中含
有“file”的命令。你可以看到像“C-x C-f”这样的组合键显示在“find-file”
这样的命令名的旁边。

用 C-M-v 来回滚动 help 窗格,多试几次。

输入 C-x 1 来删除 help 窗格。

C-h i 阅读手册(也就是通常讲的 Info)。
这个命令会打开一个称为“info”的特殊缓冲区,在那里,
你可以阅读安装在系统里的软件包使用手册。要读 Emacs 的使
用手册,按 m emacs 就可以了。如果你之前从没用
过 Info 系统,那么请按“?”,Emacs 会带你进入 Info 的使
用指南。在看完本快速指南之后,Emacs Info 会成为你的主要
参考文档。

  • 更多精彩(MORE FEATURES)

想学习更多的使用技巧,Emacs 使用手册(manual)值得一读。你可以读纸版的
书,也可以在 Emacs 中读(可以从 Help 菜单进入或者按 C-h r)。提两个你
可能会很感兴趣的功能吧,一个是可以帮你少敲键盘的 completion(自动补全),
另一个是方便文件处理的 dired(目录编辑)。

Completion 可以替你节省不必要的键盘输入。比如说你想切换到 Message 缓
冲区,你就可以用 C-x b *M 来完成。只要 Emacs 能够根据你已经输入的
文字确定你想要输入的内容,它就会自动帮你补齐。Completion 也可用于命令
名和文件名。有关 Completion 的详细说明可以在 Emacs Info 中的
“Completion”一节里找到。

Dired 能够在一个缓冲区里列出一个目录下的所有文件(可以选择是否也列出子
目录),然后你可以在这个文件列表上完成对文件的移动、访问、重命名或删除
等等操作。Dired 也在 Emacs 使用手册中有详细介绍,参见“Dired”一节。

Emacs 使用手册里还有许许多多的精彩功能等着你来了解。

  • 总结(CONCLUSION)

要退出 Emacs 请用 C-x C-c。

本文完全是为零起点新手所写的起步教程。如果你觉得哪里还看不明白,千万不
要怀疑自己,那一定是我们没有写好。我们永远欢迎你的不满和抱怨。

  • 翻译(TRANSLATION)

翻译:孙一江 sunyijiang@gmail.com
维护:薛富侨 xfq.free@gmail.com
校对:水木社区(www.newsmth.net)Emacs 板众多网友及众多 Emacs 中文用户

Emacs 快速指南(Tutorial)早有两个刘昭宏的中文译本,繁简各一。其简体版本
(TUTORIAL.cn)基本由繁体版本(TUTORIAL.zh)经词语替换而得。然而繁简中文
不仅在用词习惯上有所不同,更有诸多表达方式与句法方面的差异,因此一直以来
用户使用 TUTORIAL.cn 都会略觉生硬和晦涩。这次重新翻译 TUTORIAL.cn 的动机
正是源于这种体验,希望我们的工作能够让本文更好地发挥其作用。TUTORIAL.zh
的译文质量很高,在翻译过程中给予过我们许多借鉴和参考,在此对刘昭宏的工作
表示感谢。

翻译过程中最大的挑战莫过于术语译词的选择了。经过水木社区 Emacs 板热心
网友小范围内的讨论,我们选择了现在的译法。用户的广泛参与是自由软件生命
力的源泉,所以如果你有任何建议、勘误或想法,请用你喜欢的方式向我们提出。
你可以通过电子邮件直接联系维护者,也可以放到 GNU Emacs 的开发邮件列表
或者水木社区的 Emacs 板上进行讨论。

下面列出主要术语的译词对照,并给出注释说明:

command               命令
cursor                光标
scrolling             滚动
numeric argument      数字参数
window                窗格 [1]
insert                插入
delete                删除 [2]
kill                  移除 [2]
yank                  召回 [2]
undo                  撤销
file                  文件
buffer                缓冲区
minibuffer            小缓冲
echo area             回显区
mode line             状态栏
search                搜索
incremental search    渐进式搜索 [3]

对于其他没有提到的术语,读者可以参考 Emacs 使用手册里的术语表。

[1] “window”一词在计算机相关的领域一般都被译为“窗口”。但是在 Emacs
中,还有一个“frame”的概念。在被广泛使用的 X 窗口系统和微软的视窗
(Windows)系列操作系统中,Emacs 的一个“frame”就是一个“窗口”,因
此把 Emacs 中的“frame”译成“窗口”更加符合通常的习惯。这样,Emacs
中的“window”就只能译成“窗格”了。我们认为 Emacs 中 window 和
frame 的关系用窗格和窗口来类比是十分形象的。

《学习GNU Emacs》(第二版)一书对“window”和“frame”的翻译与本教程
刚好相反(分别译作“窗口”和“窗格”)。在此特别注明,以消除可能产生
的疑惑。(感谢李旭章 <lixuzhang@gmail.com> 指出)

[2] 对于“delete”和“kill”的区别,正文已经给出了详细的说明。“删除”和
“移除”相比较起来,前者更多地隐含着“破坏”和“不可恢复”的意思,而
后者更多地隐含着“被转移”和“可恢复”的意思。因此分别选择它们作为上
述两词的译词,希望能够体现出区别。“yank”在中文文档中鲜有对应译词出
现,翻译的困难较大。究其本意是:“a strong sudden pull”(参见韦氏词
典),即“猛然拉回”。在原文档中 yank 被引申为“将先前移除的东西再移
回来”这个意思,所以我们选择了“召回”一词与其对应。

[3] “incremental”一词在计算机著作中广泛出现,被广泛接受的中文译词有两
个:“增量的”和“渐进的”。“incremental search”翻译成“增量式搜索
”或者“渐进式搜索”都讲得通,且都有各自的形象之处。还是参考原文对其
的解释:“… means that the search happens while you type in the
string to search for”。意思是之所以称其为“incremental search”,是
因为“在你输入搜索字符串的过程中,搜索就已经在进行了”。我们认为“增
量的”更加强调在现有基础上的变化(比如“增量备份”,“增量编译”);
而“渐进的”更加强调过程的逐渐发展,也更加符合原文的意思。因此我们选
择将“incremental search”译作“渐进式搜索”。

  • 版权声明(COPYING)

This tutorial descends from a long line of Emacs tutorials
starting with the one written by Stuart Cracraft for the original Emacs.

This version of the tutorial is a part of GNU Emacs. It is copyrighted
and comes with permission to distribute copies on certain conditions:

Copyright (C) 1985, 1996, 1998, 2001-2018 Free Software Foundation,
Inc.

This file is part of GNU Emacs.

GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see https://www.gnu.org/licenses/.

Please read the file COPYING and then do give copies of GNU Emacs to
your friends. Help stamp out software obstructionism (“ownership”) by
using, writing, and sharing free software!

【下面为版权声明的译文,仅供参考。实际法律效力以英文原文为准。】

本快速指南沿袭自历史悠久的 Emacs 快速指南,可上溯至 Stuart Cracraft 为最
初的 Emacs 所作的版本。

本篇指南是 GNU Emacs 的一部分,并允许在下列条件的约束下发行其拷贝:

Copyright (C) 1985, 1996, 1998, 2001-2018 Free Software Foundation,
Inc.

本文件为 GNU Emacs 的一部分。

GNU Emacs 为自由软件;您可依据自由软件基金会所发表的GNU通用公共授权
条款,对本程序再次发布和/或修改;无论您依据的是本授权的第三版,或
(您可选的)任一日后发行的版本。

GNU Emacs 是基于使用目的而加以发布,然而不负任何担保责任;亦无对适
售性或特定目的适用性所为的默示性担保。详情请参照GNU通用公共授权。

您应已收到附随于 GNU Emacs 的GNU通用公共授权的副本;如果没有,请参照
https://www.gnu.org/licenses/.

敬请阅读文件“COPYING”,然后向你的朋友们分发 GNU Emacs 拷贝。让我们以使
用、编写和分享自由软件的实际行动来共同祛除软件障碍主义(所谓的“所有
权”)!

;;; Local Variables:
;;; coding: utf-8
;;; End:

ReactJS静态网站生成器-Gatsby

发表于 2018-03-15 | 更新于 2019-05-22 | 分类于 tools

ReactJS静态网站生成器-Gatsby

Gatsby 可以使用 React.js 把纯文本转换到动态博客或者网站上。

特点:

  • 无需重载页面转换

  • 热重载编辑

  • 为构建静态网站创建 React.js 组件模型和生态系统

  • 直观的基于目录的 URLs

  • 支持 “Starters”

文档写作与部署工具docsify

发表于 2018-03-14 | 更新于 2019-05-22 | 分类于 tools

docsify:一个文档展示与部署的工具

基于md文档的写作,构建一个好看的文档展示样式,并启动服务提供访问。

官网

https://docsify.js.org

快速开始

推荐安装 docsify-cli 工具,可以方便创建及本地预览文档网站。

1
npm i docsify-cli -g

初始化项目

如果想在项目的 ./docs 目录里写文档,直接通过 init 初始化项目。

1
docsify init ./docs

开始写文档

初始化成功后,可以看到 ./docs 目录下创建的几个文件

1
2
3
4
index.html 入口文件
README.md 会做为主页内容渲染
.nojekyll 用于阻止 GitHub Pages 会忽略掉下划线开头的文件
直接编辑 docs/README.md 就能更新网站内容,当然也可以写多个页面。

本地预览网站

运行一个本地服务器通过 docsify serve 可以方便的预览效果,而且提供 LiveReload 功能,可以让实时的预览。默认访问 http://localhost:3000 。

1
docsify serve docs

多页文档

如果需要创建多个页面,或者需要多级路由的网站,在 docsify 里也能很容易的实现。例如创建一个 guide.md 文件,那么对应的路由就是 /#/guide。

假设你的目录结构如下:

1
2
3
4
5
6
-| docs/
-| README.md
-| guide.md
-| zh-cn/
-| README.md
-| guide.md

那么对应的访问页面将是

1
2
3
4
docs/README.md        => http://domain.com
docs/guide.md => http://domain.com/guide
docs/zh-cn/README.md => http://domain.com/zh-cn/
docs/zh-cn/guide.md => http://domain.com/zh-cn/guide

123…9
Wanglv Yihua

Wanglv Yihua

82 日志
20 分类
206 标签
RSS
© 2019 Wanglv Yihua
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Muse v6.4.1