Posts Threads and Concurrent
Post
Cancel

Threads and Concurrent

并发和线程(看心情更新)

A. 基础知识

1. 线程(thread)和进程(process)

  • 线程是操作系统能够进行运算调度的最小单位,大部分情况下,它被包含在进程之中,是进程中的实际运作单位
  • 进程是指计算机中已运行的程序
  • 多进程vs多线程:本质区别在多线程共享数据,多进程每个进程拥有自己的一整套变量

2. 创建新线程常见方法

  • Method1: 定义Thread的子类(extends Thread),并override其run()方法,然后new一个实例,并用start()启动
1
2
3
4
5
6
7
8
9
10
class Thread extends Thread {
  .....
  @Override
  public void run() {

  }
}

Thread t1=new Thread();
t1.start();
  • Method2:定义实现Runnable接口的类(implements Runnable),同样重写run(),然后new一个实例,并用start()启动
1
2
3
4
5
6
7
8
9
10
11
12
class Thread2 implements Runnable {
  ....
  @Override
  public void run() {

  }
}
// imp is the implementation of Runnable
Thread2 imp=new Thread2();
// t2 is new thread
Thread t2=new Thread(imp);
t2.start();
  • 对比: Runnable只实现了接口,还可以继承其他类,而且imp可以重复利用来创建其他新线程,Thread不能继承其他类了
  • 补充:其他方法还有实现Callable接口和用线程池

3. 线程安全

  • 一个类在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成
  • 主要可能是多个线程调用同一个code block的代码时,线程A的操作改变了某个变量,使线程B本应得到的结果受到了影响
  • 不仅仅是在对象创建后的业务逻辑中要考虑线程的安全性,在对象创建的过程中,也要考虑线程安全

4. 线程状态(Life Cycle)

  • New(新创建),此时还没开始运行
  • Runnable(可运行),调用start()进入Runnable,此时不一定在运行
  • Blocked(被阻塞),当线程A试图获取一个内部对象锁,而该锁被其他线程持有,则A进入阻塞,暂时不活动,等待被激活
  • Waiting(等待),当线程A等待另一个线程B通知调度器一个条件时,A进入等待状态,常见于Object.wait/Thread.join调用中
  • Timed waiting(计时等待),Thread.sleep/Object.wait/Thread.join/Lock.tryLock/Condition.await的计时版有个超时参数,调用时会进入此状态,并保持到超时期满或其他适当的通知
  • Terminated(被终止),有两种可能,一是run()正常退出而自然死亡,二是因一个未被捕获的异常终止了run()而意外死亡

5. 线程中断

  • 没有可以强制线程终止的方法,然而interrupt()可以用来请求终止线程,线程的中断状态将被设为true,若此线程被sleep()阻塞,将抛出InterruptedException
  • 每个线程都会时不时检查中断状态,用isInterrupted(),但如果线程被阻塞,会抛出InterruptedException
  • static boolean interrupted() vs boolean isInterrupted(),前者有副作用,会把中断状态重置为false,后者不会改变状态

6. 线程属性

a. 线程优先级(Priority)

默认继承父类优先级,可以用setPriority()改变,在[MIN_PRIORITY, MAX_PRIORITY](即[1, 10])之间,NORM_PRIORITY为5

b. 守护线程(Daemon)

  • 守护线程唯一作用是为其他线程提供服务,不应该访问任何固有资源
  • setDaemon(boolean isDaemon)来设置守护线程或用户线程

7. 竞争条件(race condition)

两个线程存取相同的对象,且每个线程都调用了修改该对象状态的方法,可能会造成错误的结果,解决办法:加锁

8. 锁对象(Lock)

synchronize关键字提供锁,后面再仔细介绍,还有Java SE 5.0后引入了ReenTrantLock类,使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
class myLockTest {
    private Lock myLock = new ReentrantLock();

    public void doThings() {
        myLock.lock();
        try {
            // functional codes
        } finally {
            myLock.unlock();
        }
    }
}

要注意unlock()一定要在finally block中,否则出现Exception会让其他线程永远阻塞

9. 条件对象(Condition)

当线程得到锁,进入function部分准备执行时发现需要满足条件后才能执行,这时候就需要条件对象来处理,一个 锁对象可以对应多个条件对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class testCondition {
    private Lock myLock = new ReentrantLock();
    private Condition cond = myLock.newCondition();
    // myLock is Lock object here
    public testCondition(int a, int b) {
        myLock.lock();
        try {
            if (a > b) {    // this now not satisfied
                cond.await();
                // functional codes here:
                // code: some operations to change value of a and b
                // after operation, use this to reactive other thread which might be blocked
                cond.signalAll();
            }
        } finally {
            myLock.unlock();
        }
    }
}

一旦一个线程调用await(),那它本身无法再激活自己,只能期待其他线程调用signalAll()来释放它,如果没有那就dead lock了。注意signalAll()并不会立即激活等待线程,只是解除阻塞,还是要竞争资源

10. 两个关键字synchronized和volatile

  • synchronized: Java的每个对象都有一个内部锁,如果一个方法用了synchronized声明,那么对象锁会保护整个方法,能够保证在同一时刻最多只有一个线程执行该段代码,即锁对象(Lock)的用法等效于:
1
2
3
4
5
6
class myLockTest {

    public synchronized void doThings() {
        // functional codes
    }
}

不同于锁对象的是,内部对象锁只有一个条件对象,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
class testCondition {

    public synchronized testCondition(int a, int b) {
        if (a > b) {    // this now not satisfied
            wait();
            // functional codes here:
            // code: some operations to change value of a and b
            // after operation, use this to reactive other thread which might be blocked
            notifyAll();
        }
    }
}

显然通常情况下用synchronized简便很多,它还可以用于静态方法,但这种情况下锁不再是对象的内部锁,而是类的内部锁(所谓的对象锁vs类锁?),更常用的方法是synchronized代码块,效果一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class myLockTest {

    // non-static
    public void doThings() {
        synchronized(this) {
            // functional codes
        }
    }

    // static
    public static void something() {
        synchronized(myLockTest.class) {
            // functional codes
        }
    }
}
  • volatile: 在较低数量的实例上使用同步方法开销过大,这时候就需要vloatile这种关键字,这是一种免锁机制:
1
2
3
4
5
6
7
8
private boolean sign;
public synchronized boolean isTrue() {}
public synchronized void setSign() {}

// equals to this:
private volatile boolean sigh;
public boolean isTrue() {}
public void setSign() {}

volatile很少用到,大概了解就行,具体实现原理涉及JVM,还有就是volatile不保证原子性

11. 线程相关方法

Thread类下

  • void setPriority(int new Priority): 设置此线程优先级
  • static void yield(): 当前进程让步,如果有其他大于等于此优先级的可运行线程则调度这些
  • void setDaemon(boolean isDaemon): 标记守护/用户线程,必须在线程启动前调用
  • void join(): 当前线程A等待子线程b终止(用法A中call b.join()),在调用此方法时当前线程A阻塞,等子线程b死亡才继续执行A后续代码
  • void join(long millis): 同上,加了个超时判断
  • void interrupt(): 向线程发送中断请求,线程的中断状态被设置为true
  • static boolean interrupted(): 检查当前线程是否被中断,副作用是中断状态会被重置为false
  • boolean isInterrupted(): 同上,但不改变中断状态
  • static Thread currentThread(): 返回代表当前正在执行线程的对象
  • Thread(Runnable target): 构造新线程
  • void start(): 启动线程(只允许启动一次),处于Runnable状态,一旦得到cpu资源就将调用run()开始真正运行,所有线程异步执行,这叫并发
  • void run(): 其中的内容就是这个线程需要做的事,可多次调用,但是是同步执行
  • static void sleep(long millis): 休眠millis毫秒

Java.util.concurrent下

  • void lock(): 获取这个锁,如果该锁同时被另一个线程持有则阻塞
  • void unlock(): 释放这个锁
  • ReentrantLock(): 构造保护共享变量的锁
  • Condition newCondition(): 构造锁的条件对象
  • void await(): 条件不满足时调用,该线程阻塞并放入等待集,并等待其他线程的操作满足条件后用signalAll()释放阻塞
  • void signalAll(): 解除该条件等待集中所有线程的阻塞
  • void signal(): 随机从等待集中选择一个线程解除阻塞

Object类下

  • void wait(): 线程进入等待集,用法效果基本等同await()
  • void wait(long millis): 同上,多个超时时间
  • void notifyAll(): 解除等待集中所有线程阻塞,基本同理signalAll()
  • void notify(): 随机解除一个,基本同理signal()
This post is licensed under CC BY-NC-SA 4.0 by the author.

Computer Networks Fundamentals

Python Fundamentals

Comments powered by Disqus.