2019 -- 并发
[TOC]
一、线程状态转换
以前记录的状态
创建、可运行、运行中、阻塞、死亡;这几个很好理解,创建就是线程对象被创建,但是还没有调用start方法来开启,而开启之后也不是立马就能得到执行的,是否执行取决于CPU调度,这时就是可运行状态,得到CPU调度之后叫做运行中,而线程在运行中因某些原因不能继续运行时叫做阻塞,当线程执行完run方法后认为它死去
更详细的版本
- 新建:创建后尚未启动
- 可运行:可能在在运行也可能在等待CPU时间片
- 阻塞:等待获取一个排它锁,如果其他线程释放了锁就会结束此状态(阻塞和等待的区别在于,阻塞是被动的在程序运行的时候出现不被开发者控制的,等待是主动的编码的时候就已经确定了)
- 无限期等待:等待其他线程显式的唤醒,否则不会被分配CPU时间片,下面整理了会进入这个状态的情形和退出的方法(关于这些内容的详细解释留在下面的内容中)
- 没有设置Timeout参数的Object.wait()方法,退出Object.notify()/Object.notifyAll()
- 没有设置Timeout参数的Thread.join()方法,被调用的线程执行完毕
- LockSupport.park()方法,LockSupport.unpark(Thread)
- 期限等待:无需等待其他线程的显示唤起,在一定时间之后会被系统自动唤起
- Thread.sleep()方法,时间结束时退出
- 设置了参数的Object.wait(),时间结束、Object.notify()、Object.notifyAll()
- 设置了参数的Thread.join(),时间结束、被调用的线程执行完成
- LockSupport.parkNanos(),LockSupport.unpark(Thread)
- LockSupport.parkUntil(),LockSupport.unpark(Thread)
- 死亡:线程结束或者因为出现异常结束
二、使用线程
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
实现runnable接口
实现Callable接口
与Runnable相比,Callable可以有返回值,是通过FutureTask进行封装的
继承Thread类
对比接口和继承
总体来说实现接口会更好一些,其一java不支持多继承,其二继承Thread类可能会造成不必要的开销
三、基础线程机制
Executor
管理多个异步任务的执行,不清楚到底是干啥的
Daemon
守护线程是线程的保姆,后续补充相应内容
sleep()
sleep方法会休眠当前执行的线程,单位为毫秒,注意一下有时候会问sleep和wait方法谁是object的谁是Thread的,sleep是Thread自己写的方法,sleep可能会抛出InterruptedException
yield()
是个静态方法,准确的说是Thread的静态方法,所以调用就是Thread.yield(),这哥们大概就是说我重要的逻辑都处理完了,CPU可以选择先执行别的线程,至于CPU接不接受那就不知道了
四、中断
线程会在执行完毕之后自动结束,运行中出现异常也会提前结束,也就是说程序在执行又没有异常的时候想要中断它就得执行一些操作了,而下面说的几个方法都是为了中断程序的
interrupt()
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
interrupted()
很明显前面的interrupt方法生效是有前提的(阻塞等待),要是run里面是个无限循环但又没有sleep之类的操作,可以在循环条件中加入interrupted(),这时如果线程丢下去调用了interrupt方法,interrupted就能接收到这个信号,将自己的返回值变为true,从而跳出循环
1 | // InterruptExample.java |
Executor的中断操作
和前面线程机制里面说的Excutor有关,再补充
五、互斥同步
其实java有两种方式来实现互斥访问,一种是常见的synchronized,另一个ReentrantLock,反正不知道后面这个是干啥的
synchronized
同步代码块
作用于对象,只有当不同线程中使用同一个对象时才会对修饰的代码块进行同步
同步方法
和同步代码块一样
同步一个类
这个应该也叫类锁,这时不同线程调用不同的对象也会对这部分语句进行同步
public class SynchronizedExample {
public void func2() {
synchronized (SynchronizedExample.class) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func2());
executorService.execute(() -> e2.func2());
}
同步一个静态方法
作用于整个类
ReentrantLock
是java.util.concurrent包中的锁
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 确保释放锁,从而避免发生死锁。
}
}
}
public static void main(String[] args) {
LockExample lockExample = new LockExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> lockExample.func());
executorService.execute(() -> lockExample.func());
}
比较
- 实现:synchronized是JVM实现的,ReentrantLock是JDK实现的
- 性能:两者差不多
- 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情;对于这件事情ReentrantLock是可以的,而synchronized不行(不知道r是通过什么方式去处理所谓其他的事情,要是其他的事情依赖于同步代码中的数据那也没法去执行啊)
- 公平锁:是指多个线程在等待同一个锁时,安装申请的时间顺序来获得锁,这件事儿上synchronized是不行的,ReentrantLock默认也不行,但是可以设置为公平锁
- 锁绑定多个条件:一个ReentrantLock可以同时绑定多个Condition对象
使用选择
其实从上面的对比中可以看出,ReentrantLock的功能是比synchronized要强大的,但实际使用中synchronized就足够了,而且synchronized属于是JVM亲生的,ReentrantLock来说的话不一定所有版本的JVM都支持,而且synchronized锁不需要手动释放
六、线程间的协作
当多个线程要一起去处理问题时,由于CPU对线程的调用是随机的,当我们想要他们有一定的执行顺序时就需要对他们进行协调
join()
在一个线程中调用另一个线程的join(),当前线程将会挂起直到目标线程结束
public class JoinExample {
private class A extends Thread {
@Override
public void run() {
System.out.println("A");
}
}
private class B extends Thread {
private A a;
B(A a) {
this.a = a;
}
@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}
public void test() {
A a = new A();
B b = new B(a);
b.start();
a.start();
}
}
//创建JoinExample类的对象调用test方法即可
wait() notify() notifyAll()
同步代码中调用wait()时将线程挂起,当其他的线程去调用了notify()或者notifyAll()时唤醒挂起的线程
- 他们都是Object的方法,而不是Thread的方法
- 只有在被同步的代码中使用,否则会在运行时抛出IllegalMonitorStateException为什么不能在非同步的代码中调用呢
- 使用wait挂起期间,线程会释放锁,这个就比较明确了,不释放锁别的线程就无法执行同步代码,看起来notify和notifyAll方法也都只能在同步代码中执行,基于这个原因的话,不释放锁就永远无法唤醒也就死锁了
- 案例:
public class WaitNotifyExample {
public synchronized void before() {
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
WaitNotifyExample example = new WaitNotifyExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
和sleep方法的区别
- wait是Object的方法,sleep是Thread的静态方法
- wait会释放锁,sleep不会
await() signal() signalAll()
前面的的wait是synchronized里面的,而这一套方法是ReentrantLock的
public class AwaitSignalExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
1 | public static void main(String[] args) { |
七、J.U.C-AQS
java.util.concurrent(J.U.C)其实也就是前面说的ReentrantLock,这个东西提供了比synchronized更多的对线程的控制,能大大的提高并发的性能,而这个AQS被认为是J.U.C的核心
CountDownLatch
用来控制一个线程等待多个线程,内部维护了一个计数器cnt,每次调用countDown()方法的时候计数器值减一,减到0的时候那些因为调用了await方法而在等待的线程会被唤醒
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("run..");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
CyclicBarrier
控制多个线程相互等待,就是只有当多个线程都执行到这个await方法时大家才能继续往下执行,内部同样是一个计数器,没当一个线程执行到await方法时计数器值减一,当计数器为0时,大家又能一起开开心心的往下执行了
public class CyclicBarrierExample {
public static void main(String[] args) {
final int totalThread = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("before..");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.print("after..");
});
}
executorService.shutdown();
}
}
Semaphore
类似于操作系统中的信号量,可以控制对互斥资源的访问线程数
public class SemaphoreExample {
public static void main(String[] args) {
final int clientCount = 3;
final int totalRequestCount = 10;
Semaphore semaphore = new Semaphore(clientCount);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalRequestCount; i++) {
executorService.execute(()->{
try {
semaphore.acquire();
System.out.print(semaphore.availablePermits() + " ");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executorService.shutdown();
}
}
八、J.U.C-其他组件
实在没啥耐心看了,哈哈,以后要是需要用了再看看
- FutureTask
- BlockingQueue
- ForkJoin
九、线程不安全示例
就是不对共享资源的访问操作进行同步操作的时候,结果会和预期不一致
public class ThreadUnsafeExample {
private int cnt = 0;
public void add() {
cnt++;
}
public int get() {
return cnt;
}
}
public static void main(String[] args) throws InterruptedException {
final int threadSize = 1000;
ThreadUnsafeExample example = new ThreadUnsafeExample();
final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadSize; i++) {
executorService.execute(() -> {
example.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(example.get());
}
期望中得到的结果是1000,但实际可能得到的是998、997之类的不确定的数值
十、java的内存模型
也不知道为啥这突然就讲起了java的内存模型