Java 并发详解 ①:线程
1. 创建线程
继承 Thread 类
1 | public class SubThread extends Thread { |
实现 Runnable 接口
1 | public class PrintNum1 implements Runnable { |
实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装
1 | public class MyCallable implements Callable<Integer> { |
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
12public 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
10public 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. 进程、线程、纤程(协程)
线程在内核态
纤程在用户态,可以启动的数据比线程多