Java 并发详解 ①:线程

1. 创建线程

继承 Thread 类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SubThread extends Thread {
//重写 run()方法
public void run() {
//输出...
}
}
public class TestThread {
public static void main(String[] args) {
SubThread st = new SubThread();
//调用start()方法运行线程
st.start();
}
}

实现 Runnable 接口

1
2
3
4
5
6
7
8
9
10
11
12
public class PrintNum1 implements Runnable {
public void run() {
...
}
}
public class TestThread1 {
public static void main(String[] args) {
PrintNum1 p = new PrintNum1();
Thread t1 = new Thread(p);
t1.start();
}
}

实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装

1
2
3
4
5
6
7
8
9
10
11
12
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}

2. 线程方法

2.1. 调度

  • Thread.sleep (millisec):睡眠
  • Thread.yield ():调用此方法的线程释放当前 cpu 的执行权(可能会再次调用该线程)
    声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
  • join ():等其他线程调用结束
    在线程中调用另一个线程的 join () 方法,会将当前线程挂起,直到目标线程结束。
    例:A 线程中调用 B 线程的 join,当执行到 join 方法时 A 停止,直到 B 执行完,A 再接着 join 之后的代码执行

2.2. 优先级

Api:

  • getPriority ():返回线程优先值
  • setPriority (int newPriority):改变线程的优先级

概述:
优先级高只能是说明,它获得时间片的概率大,但不是一定会执行它
线程创建时继承父线程的优先级

  • 1(MIN_PRIORITY)
  • 5(NORMAL_PRIORITY)
  • 10(MAX_PRIORITY)

2.3. 中断

  • void interrupt ():向线程发送中断请求。线程的中断状态将被设置为 true。如果该线程被 sleep 调用阻塞、限期等待或者无限期等待状态,抛出 InterruptedException 异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
    try {
    Thread.sleep(2000);
    } catch(InterruptedException e) {
    e.printStackTrace();
    }
    }).start();
    // 线程在 sleep 时中断,抛出异常
    thread.interrupt();
    System.out.println("Main run");
    }
  • static boolean interrupted ():测试当前线程是否被中断,并将当前线程的中断状态重置为 false

  • boolean isInterrupted ():测试线程是否被终止,不改变线程的中断状态

3. 线程的生命周期

新建、就绪、运行、阻塞、死亡

4. 线程异常

多线程中的异常处理

5. 线程安全场景

多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为

5.1. 不可变

不可变(Immutable)的对象一定是线程安全的。

  • final 关键字修饰的基本数据类型
  • String
  • 枚举类型
  • Number 部分子类,如 Long 和 Double 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的原子类 AtomicInteger 和 AtomicLong 则是可变的
  • 不可变集合:可以使用 Collections.unmodifiableXXX () 方法来获取一个不可变的集合。对集合进行修改的方法都直接抛出异常。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ImmutableExample {
    public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map);
    unmodifiableMap.put("a", 1);
    }
    }
    /* Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
    at ImmutableExample.main(ImmutableExample.java:9) */

5.2. 同步

  • 阻塞同步:悲观锁:synchronized 和 ReentrantLock
  • 非阻塞同步
    • CAS(Compare-and-Swap,CAS)乐观锁
    • AtomicInteger 等原子类

5.3. 无同步

如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性

  • 栈封闭:
    多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的
  • ThreadLocal:
    把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题
  • 可重入代码:
    这种代码也叫做 纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。
    可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。

6. 进程、线程、纤程(协程)

线程在内核态
纤程在用户态,可以启动的数据比线程多