并发编程之线程!

线程优先级

线程的优先级:线程抢占 CPU 时间片的概率,优先级越高的线程优先执行的概率就越大。

但并不能保证优先级高的线程一定先执行。

1
2
3
4
5
6
7
8
// 线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;

// 线程默认优先级
public final static int NORM_PRIORITY = 5;

// 线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10

线程的常用方法

join():

在一个线程中调用 other.join() ,这时候当前线程会让出执行权给 other 线程。

直到 other 线程执行完或者过了超时时间之后再继续执行当前线程。

yield():

给线程调度器一个当前线程愿意出让 CPU 使用权的暗示,但是线程调度器可能会忽略这个暗示。

  • yield() 执行非常不稳定,线程调度器不一定会采纳 yield() 出让 CPU 使用权的建议。

wait/notify和sleep方法的异同?

wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。

在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。

sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言。

意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。

wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

线程状态

新建状态(New):

通过实现Runnable接口或继承Thread声明一个线程类,new一个实例后,线程就进入了新建状态。

就绪状态(Ready):

线程创建成功后,调用该线程的start()函数,线程进入就绪状态,该状态的线程进入可运行线程池中,等待获取CPU的使用权。

运行状态(Running):

可运行线程池中选择一个线程,该线程进入运行状态。

  • 线程获取到了CPU时间片。
  • 当线程时间片用完或调用的yield()函数,该线程回到就绪状态。

终止状态(Terminated):

线程执行结束或执行过程中因异常意外终止都会使线程进入终止状态。

等待状态(Waiting):

运行状态的线程执行wait()、join()、LockSupport.park()任意函数,该线程进入等待状态。

  • 其中wait()join()函数会让JVM把该线程放入锁等待队列。

  • 处于这种状态的线程不会被分配CPU执行时间,它们要等待被主动唤醒,否则会一直处于等待状态。

  • 执行LockSupport.unpark(t)函数唤醒指定线程,该线程回到就绪状态。

  • 通过notify()、notifyAll()、join线程执行完毕方式,会唤醒锁等待队列的线程,出队的线程回到就绪状态。

超时等待状态(Timed waiting):

超时等待与等待状态一样,唯一的区别就是多了超时机制,不会一直等待被其他线程主动唤醒,而是到达指定时间后会自动唤醒

阻塞状态(Blocked):

运行状态的线程获取同步锁失败或发出I/O请求,该线程进入阻塞状态。

  • 如果是获取同步锁失败JVM还会把该线程放入锁的同步队列。
  • 同步锁被释放时,锁的同步队列会出队所有线程,进入就绪状态。
  • I/O处理完毕时,该线程重新回到就绪状态。

线程安全

多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行问题,也不需要进行额外的同步。

而调用这个对象的行为都可以获得正确的结果,那这个对象便是线程安全的。

什么场景需要注意线程安全问题?

访问共享变量或资源:

有访问共享对象的属性,访问 static 静态变量,访问共享的缓存等等。

因为这些信息不仅会被一个线程访问到,还有可能被多个线程同时访问。

  • 那么就有可能在并发读写的情况下发生线程安全问题。

依赖时序的操作:

如果我们操作的正确性是依赖时序的,而在多线程的情况下又不能保障执行的顺序和我们预想的一致。

  • 这个时候就会发生线程安全问题。
1
2
3
if (map.containsKey(key)) {
map.remove(obj)
}

互斥与同步的区别

互斥:

  • 某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。

同步:

  • 实现访问者对资源的有序访问。

停止线程

stop()

直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止。

  • 会导致出现数据完整性等问题。

suspend()resume()

如果线程调用 suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,这样就容易导致死锁问题。

  • 因为这把锁在线程被 resume() 之前,是不会被释放的。

interrupt()

一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true

每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位。

  • 如果标记位被设置成 true,就说明有程序想终止该线程。

while 循环体判断语句中,通过 Thread.currentThread().isInterrupt()判断线程是否被中断。

1
2
3
while (!Thread.currentThread().islnterrupted() && more work to do) {
do more work
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StopThread implements Runnable {

@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
}
}

public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}

如果处于休眠(sleep、wait)中的线程被中断,那么线程是可以感受到中断信号的。

  • 并且会抛出一个 InterruptedException 异常。

  • 同时清除中断信号,将中断标记位设置成 false

这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。

如果负责编写的方法需要被别人调用,方法内调用了 sleep 或者 wait 等能响应中断的方法时,仅仅 catch 住异常是不够的。

方式一:方法签名抛异常,run() 强制 try/catch

将中断信号层层传递到顶层,最终让 run() 方法可以捕获到异常。

1
2
3
void subTask() throws InterruptedException {
Thread.sleep(1000);
}

方式二:再次中断:

手动添加中断信号,中断信号依然可以被捕捉到。

这样后续执行的方法依然可以检测到这里发生过中断,可以做出相应的处理。

1
2
3
4
5
6
7
8
private void reInterrupt() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}

生产者消费者模式

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

private Queue queue;
private int max = 16;
private ReentrantLock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();


public MyBlockingQueueForCondition(int size) {
this.max = size;
queue = new LinkedList();
}

public void put(Object o) throws InterruptedException {
lock.lock();
try {
while (queue.size() == max) {
notFull.await();
}
queue.add(o);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}

public Object take() throws InterruptedException {
lock.lock();
try {
while (queue.size() == 0) {
notEmpty.await();
}
Object item = queue.remove();
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}