0%

浅析Java线程(一)

进程与线程

首先提一下操作系统中的多任务

多任务(multitasking):同一刻运行多个任务/程序的能力

现在,多核CPU已经非常普及了,但并发执行的进程数目并不是由CPU数目制约的,操作系统将CPU的时间片分给每一个进程给人并行处理的感觉。所以,过去的单核CPU,也可以执行多任务

对于操作系统来说,一个任务就是一个进程(Process)

在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread),一个进程至少有一个线程

可以简单总结一下

进程是操作系统资源分配和管理的基本单位
线程是任务调度和执行的基本单位

Java实现线程的两种方式

1.继承Thread类,重写run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ThreadDemo extends Thread {

@Override
public void run() {
System.out.println("运行的线程是:" + this.getName());;
}

public static void main(String[] args) {
new ThreadDemo().start();
new ThreadDemo().start();
}
}
/* Output
运行的线程是:Thread-1
运行的线程是:Thread-0
*/

2.继承Runnable接口,实现run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ThreadDemo implements Runnable {

@Override
public void run() {
System.out.println("运行的线程是:" + Thread.currentThread().getName());
}

public static void main(String[] args) {
new Thread(new ThreadDemo()).start();
new Thread(new ThreadDemo()).start();
}

}
/* Output
运行的线程是:Thread-1
运行的线程是:Thread-0
*/

线程状态

线程可以有六种状态

1.NEW – 新创建
2.RUNNABLE – 可运行
3.BLOCKED – 被阻塞
4.WAITING – 等待着
5.TIMED_WAITING – 计时等待
6.TERMINATED – 被终止

源码解析如下

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
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,

/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}

线程大致关系图

API java.lang.Thread

  • public final void join()
  • public final synchronized void join(final long millis)
  • public final synchronized void join(long millis, int nanos)

    等待该线程死亡,参数为0意味着永远等待

  • public State getState()

    获取线程状态

线程属性

线程优先级

Java中,每一个线程都有一个优先级,默认情况下,继承父线程的优先级

可以用方法setPriority()设置优先级
优先级可以是MIN_PRIORITYMAX_PRIORITY之间的任何值
其中NORM_PRIORITY = 5

API java.lang.Thread

  • public final void setPriority(int newPriority)

    设置线程优先级

  • public static native void yield();

    让当前线程处于让步状态,如果有其他相同或更高优先级可运行线程,你们这些线程会被调度

  • public static final int MIN_PRIORITY = 1;

    The minimum priority that a thread can have.

  • public static final int NORM_PRIORITY = 5;

    The default priority that is assigned to a thread.

  • public static final int MAX_PRIORITY = 10;

    The maximum priority that a thread can have.

守护线程

用途:为其他线程提供服务,主要被用作程序中后台调度以及支持性工作,如计时线程

  • 当程序只剩下守护线程时,虚拟机会自动退出

API java.lang.Thread

  • public final void setDaemon(boolean on)

    标识该线程为守护线程或用户线程
    该方法必须在线程启动之前调用

未捕获异常处理器

线程的run()方法不能抛出任何受查异常,非受查异常会导致线程终止,即线程死亡
在线程死亡之前,异常被传递到一个用于未捕获异常的处理器

API java.lang.Thread

  • public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

    为线程安装默认异常处理器

  • public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)

    为线程安装处理器

同步

竞争条件(race coondition)

  • 多数线程中,两个或两个以上的线程需要共享对同一数据的存取
  • 若每一个线程都调用了修改该数据对象的方法,根据线程访问数据的次序,可能会产生讹误的对象。这样一个情况通常称为竞争条件(race coondition)

锁对象

有两种机制防止代码块受并发访问的干扰

  • synchronized关键字
  • java.util.concurrent框架

我们常常使用ReentrantLock类保护代码块,它在java.util.concurrent.locks包下该锁支持可重入,因为线程可以重复获得已经持有的锁
附上一段样例

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
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo extends Thread {

public static int cnt = 1;

public static ReentrantLock myLock = new ReentrantLock();

@Override
public void run() {
for (int i = 0; i < 5; i++) {
myLock.lock();
try {
System.out.println("线程:" + this.getName() + " —— cnt:" + cnt++);
} finally {
myLock.unlock();
}
}
}

public static void main(String[] args) {
new ReentrantLockDemo().start();
new ReentrantLockDemo().start();
new ReentrantLockDemo().start();
}
}
/* Output
线程:Thread-2 —— cnt:1
线程:Thread-2 —— cnt:2
线程:Thread-2 —— cnt:3
线程:Thread-2 —— cnt:4
线程:Thread-2 —— cnt:5
线程:Thread-0 —— cnt:6
线程:Thread-0 —— cnt:7
线程:Thread-0 —— cnt:8
线程:Thread-0 —— cnt:9
线程:Thread-0 —— cnt:10
线程:Thread-1 —— cnt:11
线程:Thread-1 —— cnt:12
线程:Thread-1 —— cnt:13
线程:Thread-1 —— cnt:14
线程:Thread-1 —— cnt:15
*/

API java.util.concurrent.ReentrantLock

  • implements Lock, java.io.Serializable
  • public ReentrantLock()

    无参构造,默认构造一个非公平锁对象

  • public ReentrantLock(boolean fair)

    可以传入参数fair来指定锁是否公平

  • public void lock()
  • public void unlock()

条件对象

  • 通常,线程进入临界区,却发现在某一条件满足后才能执行需要
  • 这就需要使用一个条件对象来管理那些已经获得了锁却不能做有用工作的线程
  • 条件对象相关接口Condition

一般用法的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void someMethod() {
myLock.lock();
try {
while (/*条件不满足*/) {
conditionImpl.await();
}

// do some jobs...

conditionImpl.signalAll();
} finally {
myLock.unlock();
}
}

API java.util.concurrent.locks.Lock接口

  • Condition newCondition()

API java.util.concurrent.locks.Condition接口

  • void await() throws InterruptedException

    将线程放入条件的等待集中

  • void signal()

    随机唤醒一个等待集中的线程

  • void signalAll()

    唤醒所有等待集中的线程

关键字synchronized

synchronized关键字也可以解决并发中的同步问题

  • 可以修饰类的实例方法
  • 可以修饰类的静态方法
  • 可以修饰代码块

没有同步的代码

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
public class SynchronizedDemo {

public void method() {
System.out.println(Thread.currentThread().getName() + "'s method starts...");
try {
System.out.println(Thread.currentThread().getName() + "'s method executes...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "'s method ends...");
}

public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();

new Thread(new Runnable() {
@Override
public void run() {
demo.method();
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
demo.method();
}
}).start();
}

}
/*
Thread-1's method starts...
Thread-0's method starts...
Thread-1's method executes...
Thread-0's method executes...
Thread-0's method ends...
Thread-1's method ends...
*/

为明显看出线程执行过程,在方法中调用Thread.sleep(1000),让进程暂缓
通过输出可以看出,两个线程同时进入执行状态

修饰类的实例方法的代码

对方法做如下修改,在void之前加上synchorized关键字

1
2
3
4
5
6
7
8
9
10
public synchronized void method() {
System.out.println(Thread.currentThread().getName() + "'s method starts...");
try {
System.out.println(Thread.currentThread().getName() + "'s method executes...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "'s method ends...");
}

输出如下

1
2
3
4
5
6
Thread-0's method starts...
Thread-0's method executes...
Thread-0's method ends...
Thread-1's method starts...
Thread-1's method executes...
Thread-1's method ends...

可以看出,线程Thread-1需要等待线程Thread-0完成后才执行

修饰类的静态方法的代码

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
public class SynchronizedDemo {

public static synchronized void method() {
System.out.println(Thread.currentThread().getName() + "'s method starts...");
try {
System.out.println(Thread.currentThread().getName() + "'s method executes...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "'s method ends...");
}

public static void main(String[] args) {

new Thread(new Runnable() {
@Override
public void run() {
method();
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
method();
}
}).start();
}

}
/*
Thread-0's method starts...
Thread-0's method executes...
Thread-0's method ends...
Thread-1's method starts...
Thread-1's method executes...
Thread-1's method ends...
*/

可以看出,线程Thread-1等待线程Thread-0完成后才执行
对静态方法的同步本质上是对类的同步,也只能顺序的执行method()方法

修饰代码块

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
public class SynchronizedDemo {

public void method() {
System.out.println(Thread.currentThread().getName() + "'s method starts...");
try {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "'s method executes...");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "'s method ends...");
}

public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();

new Thread(new Runnable() {
@Override
public void run() {
demo.method();
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
demo.method();
}
}).start();
}

}

输出如下

1
2
3
4
5
6
7
8
/*
Thread-1's method starts...
Thread-0's method starts...
Thread-1's method executes...
Thread-0's method executes...
Thread-1's method ends...
Thread-0's method ends...
*/

在观察输出的过程可以发现,虽然Thread-0Thread-1都进入了对应的方法开始执行,但Thread-0进入同步块之前,需要等待Thread-1中的同步块执行完成

synchronizedReentrantLock用途总结

  • synchronized适用于资源竞争不是很激烈的情况下
  • ReentrantLock在高并发量情况下更加适用,而且需要用到可中断、公平锁、冲入锁的时候也需要使用ReentrantLock

关键字volatile

  • volatile关键字为实例域的同步访问提供了一种免锁机制
  • 如果声明一个域为volatile,编译器和虚拟机就知道该域可能被另一个线程并发更新
  • 三大特性
    1. 可见性:一个线程对共享变量修改之后会立即被写回内存并通知其他线程
    2. 不提供原子性:原子-最小操作执行单元,不可中断分割
    3. 禁止指令重排

假定一个对象有一个布尔标记done,其值被一个线程设置并被另一个线程查询,这种情况,将域声明为volatile是合理的

1
2
3
private volatile boolean done;
public boolean isDone() { return done; }
public void setDone() { done = true; }

final变量

  • 当某个域声明为final时,对其进行并发访问就是安全的
  • 当然,对它的操作不是安全的,仍然需要进行同步

读/写锁

源码浅析

java.util.concurrent.locks包下的ReentrantReadWriteLock类可以为方法添加读写锁
通过API可以看到,ReentrantReadWriteLock类有两个读写锁对象readerLockwriterLockReadLockWriteLock分别是其内部的静态类

1
2
3
4
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

使用读写锁的必要步骤

  1. 构造ReentrantReadWriteLock对象
    1
    private ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
  2. 抽取读锁和写锁
    1
    2
    private Lock readLock = rrwl.readLock();
    private Lock writeLock = rrwl.writeLock();
  3. 对所以获取方法加读锁
    1
    2
    3
    4
    5
    6
    7
    8
    public Xxx readMethod() {
    readLock.lock();
    try {
    // ...
    } finally {
    readLock.unlock();
    }
    }
  4. 对所有修改方法加写锁
    1
    2
    3
    4
    5
    6
    7
    8
    public Xxx writeMethod() {
    writeLock.lock();
    try {
    // ...
    } finally {
    writeLock.unlock();
    }
    }