《Java并发线程之美》第一章 并发编程线程基础

1. wait() 函数

1.1 阻塞线程

线程调用共享变量的wait(),此调用线程会被阻塞挂起,

直到发生以下情况:

  • a. 其他线程调用该共享对象的 notify() 或者 notifyAll() ;
  • b. 其他线程调用该线程的 interrupt(), 该线程抛出InterruptedException异常返回

1.2 共享变量监视器锁

调用共享变量的wait() 方法时, 选哟获取骑监视器锁

如何获取监视器锁?

  1. 同步代码块; synchronized(共享变量) {}
  2. 同步方法; synchronized void method(int a , int b){}
  • wait()
  • wait(long timeout) 超时
  • wait(long timeout, int nanos)

Demo02

2. notify()函数

  • notify()
  • notifyAll()

  • 当一个线程调用共享对象的notify() , 会唤醒一个在该共享变量上用wait系列方法挂起的线程
  • 多个线程时随机唤醒

  • notifyAll(), 唤起所有

Demo03.java

3. join()

等待线程执行终止的方法

(等待某几件事情完成后才能继续往下执行,
比如多线程加载资源, 需要等待多个线程全部加载完毕再汇总处理)
Demo04_join.java

4. sleep()

public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException;

调用线程会暂时让出指定时间的执行权, 不参与CPU调度,
但是该线程拥有所有的监视器资源, 比如锁持有不让出.

Demo05_Sleep.java

当一个线程处于睡眠状态时, 另一个线程中断它, 则在sleep()调用处抛出异常
Demo05_SleepInterruptedException.java

5. public static native void yield();

yiled 屈服;出产,产生;放弃;投降;

让出CPU执行权.


一般会很少用这个方法, 在调试或者测试时使用或许可以帮助复现由于并发竞争条件导致的问题

* sleep与yield区别

| sleep | yield | 
| --- | --- |
|阻塞线程(挂起指定时间) | 不被阻塞挂起, 线程让出剩余时间片, 处于就绪状态 |
|期间线程调度器不会取调用线程 | 线程调度器下一次调度可能会调用当前线程执行 |

## 6. 线程中断
设置线程中断标志, 不能直接终止线程, 而是被中断线程根据中断状态自行处理

| method | function | description | 
| --- | --- |
|void interrupt()        | 中断线程 | 别的线程调用本线程的interrupt(), 仅仅设置标志, 并不会中断线程; 如果调用时本线程被阻塞挂(wait,join,sleep), 则会抛出InterruptedExceptiony异常(标记就会失败) |
|boolean isInterrupted() | 检测当前线程是否被中断 | 是则返回true  |
|boolean interrupted()   | 检测当前线程是否被中断 | 是则返回true, 并清除中断标志 |

**需要注意的是: interrupted() 是静态方法, 而且内部是操作当前线程, 而非操作 调用interrupted()的实例对象的中断标志**
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

Demo07_DetermineIfTheThreadIsTerminatedByInterruptFlag.java```

Demo07_IsInterruptedAndInterrupted.java

7. 理解线程上下文切换

CPU在同一时刻只能被一个线程使用, CPU资源分配采用时间片轮转的策略;
给每个线程分配一个时间片, 使用完这个时间片后就会切换到就绪状态, 让出CPU让其他线程使用;

8. 线程死锁

8.1 What(什么是死锁)

多个线程在执行过程中, 因相互争夺对方已经持有的资源造成相互等待而无法继续运行下去;

8.2 Why (为什么产生死锁)

4个条件:

  • 互斥: 对持有的资源进行排他性使用, 资源同时只能由一个线程占用;(一个碗只能由一个人用)
  • 请求并持有: 自己持有一个资源, 请求持有别人的资源, 又不释放已持有的资源, 因此阻塞;(筷子伸到别人碗里, 同时不让别人夹自己的)
  • 不可剥夺: 已持有的资源在未使用完之前不能被其他线程抢占; (自己还没吃完,碗不能给其他人用)
  • 环路等待: 线程发生死锁时, 一个资源 -- 资源 的环形链 ( 如demo08中, threadA 持有resourceA并等待获取resourceB, 而threadB又正好相反, 因此构成环路等待条件)

Demo08_DeadLock_Why.java

8.2 How (如何避免)

只要破坏其中至少一个条件; 只有 请求并持有 和 环路可以被破坏

使用资源申请的有序原则就可以避免死锁

Demo08_DeadLock_How.java

  • 资源的有序分配时如何避免死锁的?

代码中, threadA 和 threadB 在start() 之后同时去抢占resourceA,
但是只有一个线程可以获取到resourceA的监视器锁, 如果threadA获取到, 则threadB 只能被组撒等到resourceA被释放后,
这时 threadA 可以继续获取resourceB 的监视器锁, 直到 resourceA 和 resourceB 都被threadA释放后, threadB就
可以从阻塞到激活态, 从而避免了死锁;

9.守护线程与用户线程

9.1 区别: 但最后一个非守护线程结束时, JVM 会正常退出

比如gc线程是一个守护进程, 如果所有用户线程已经结束, 这时gc的存在也就失去了意义

当当前进程中不存在用户线程, 虽然还存在运行任务的守护进程, JVM也会直接结束
Demo09_DaemonAndUser

附:

可在使用jdk的工具jps命令查看jvm是否还在运行;

Linux 也可用 ps -eaf|grep java查看

9.2 创建守护进程 setDaemon(true)

public final void setDaemon(boolean on) {...}

10. ThreadLocal

多线程范围同一个共享变量, 对一个共享变量进行写入时, 容易出现并发问题;

为了保证线程安全, 一般需要对其进行适当同步;

同步的并发一般就是加锁; 需要对锁有一定了解, 加重了使用者的负担;

需求: 创建一个变量后, 每个线程对其访问时都是自己线程的变量;

ThreadLocal 可以做到;

ThreadLocal 给线程提供了本地变量, 当线程访问时就会创建一个本地副本, 从而避免线程安全问题;

10.1 使用示例

Demo10_Instance.java

10.2 实现原理

Demo10_Principle.java

10.3 不支持继承性

即: 父线程中设置的值, 在子线程访问不到


**原因: 由get()可见, 调用的是当前线程: **

public T get() {
Thread t = Thread.currentThread();
...
```

10.4 InheritableThreadLocal类

解决ThreadLocal不支持继承性的问题

重写了三个方法

Demo10_NoSuppotInheritance.java

需要子线程可以获取辅线程的ThreadLocal变量的情况:

  • 子线程需要使用存放在父线程中的threadLocal中的用户登录信息
  • 一些中间件需要统一的id追踪整个调用链路记录下来

码云: java-concurrent/TheBeautyOfConcurrentProgram/01Base

猜你喜欢

转载自www.cnblogs.com/52liming/p/11080397.html