Java并发
2020-12-25 16:06:11 0 举报
AI智能生成
Java并发思维导图
作者其他创作
大纲/内容
基础线程机制
Executor<br>Executor 管理多个异步任务的执行,这里的异步是指多个任务的执行互不干扰,不需要进行同步操作<br>
CachedThreadPool :一个任务创建一个线程
FixedThreadPool : 所有任务只能使用固定大小的线程
SingleThreadExecutor : 相当于大小为1的FixedThreadPool
Daemon<br>守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分<br>当所有非守护线程结束,程序也就终止,同时会杀死所有守护线程<br>main()属于非守护线程
public static void main(String[] args) {<br> Thread thread = new Thread(new MyRunnable());<br> thread.setDaemon(true);<br>}
sleep()<br>Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒<br>sleep()可能会抛出InterruptException, 因为异常不能跨线程传播灰main() 中,<br>因此必须在本地进行处理,线程中抛出的其它异常也同样需要在本地进行处理.
public void run() {<br> try {<br> Thread.sleep(3000);<br> } catch (InterruptedException e) {<br> e.printStackTrace();<br> }<br>}
yield()<br>对静态方法Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分<br>可以切换给其它线程来执行,该方法只是对线程调度器的一个建议,而且也只是建议具有<br>优先级的其它线程可以运行
public void run() {<br> Thread.yield();<br>}
互斥同步
Java提供了两种锁机制来控制多个线程对共享资源的互斥访问, 第一个是JVM 实现的synchronize ,<br>而另一个是JDK实现的ReentrantLock
synchronize
同步代码块
public void func() {<br> synchronized (this) {<br> // ...<br> }<br>}
它只作用于同一个对象,如果调用;两个对象上的同步代码块,就不会进行同步
同步一个方法<br>(作用于用一个对象)
public synchronized void func () {<br> // ...<br>}
同步类<br>作用于整个类, 也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步
public class SynchronizedExample {<br><br> public void func2() {<br> synchronized (SynchronizedExample.class) {<br> for (int i = 0; i < 10; i++) {<br> System.out.print(i + " ");<br> }<br> }<br> }<br>}
同步一个静态方法<br>作用于整个类
public synchronized static void fun() {<br> // ...<br>}
ReentrantLock<br>实现了Lock接口,但需要自己释放锁<br>
比较
锁的实现<br>
synchronize 是JVM 实现的, 而 ReentrantLock 是JDK 实现的
性能
新版本Java 对 synchronize 进行了很多优化, 例如自旋锁等, synchronize 和 ReentrantLock 大致相同
等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其它事情, ReentrantLock可以中断,但synchronize不行
公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁<br>synchronize 中的锁是非公平的, ReentrantLock默认情况下也是非公平的,但是也可以是公平的
锁绑定多个条件
一个ReentrantLock可以同时绑定多个condition 对象
使用选择
除非需要使用ReentrantLock 的高级功能,否则优先使用synchronize ,这是因为synchronize 是JVM实现的一种锁机制<br>JVM原生的支持它, 而ReentrantLock不是所有的JDK 版本都支持,并使用synchronize 不用担心没有释放锁而导致死锁问题<br>因为JVM会确保锁的释放
线程状态
新建(NEW)
创建后尚未启动
可运行(RUNABLE)
正在Java虚拟机中运行,但是在操作系统层面,它可能处于运行状态,也可能等待资源调度<br>资源调度完成就进入运行状态,所以该状态的可运行是指可以被运行,具体有没有运行要看<br>底层操作系统的资源调度
阻塞(BLOCKED)
请求获取monitor lock 从而进入synchronize 函数或者代码块,但是其它线程已经占用了该monitor lock<br>所以出于阻塞状态,要结束该状态进入从而RUNABLE 需要其它线程释放monitor lock<br>
无限期等待(WAITING)
等待其它线程显地唤醒<br>阻塞和等待的区别在于,阻塞是被动的,它是在等待获取线程锁.<br>而等待是主动的,通过调用Object.wait() 等方法进入.
进入方法 退出方法<br>没有设置 Timeout 参数的 Object.wait() 方法 Object.notify() / Object.notifyAll()<br>没有设置 Timeout 参数的 Thread.join() 方法 被调用的线程执行完毕<br>LockSupport.park() 方法 LockSupport.unpark(Thread)
限期等待(TIMED_WAITING)
无需等待其他线程显式地唤醒,在一定时间之后会被系统自动唤醒
进入方法 退出方法<br>Thread.sleep() 方法 时间结束<br>设置了 Timeout 参数的 Object.wait() 方法 时间结束 / Object.notify() / Object.notifyAll()<br>设置了 Timeout 参数的 Thread.join() 方法 时间结束 / 被调用的线程执行完毕<br>LockSupport.parkNanos() 方法 LockSupport.unpark(Thread)<br>LockSupport.parkUntil() 方法 LockSupport.unpark(Thread)
死亡(TERMINATED)
可以是线程结束任务之后自己结束,或者产生了异常而结束
Java内存模型
主内存与工作内存
处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,<br>在它们之间加入了高速缓存<br>
加入高速缓存带来了一个新的问题:缓存一致性.如果多个缓存共享同一块主内存区域<br>那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题
所有的变量都存储在主内存中,每一个线程还有自己的工作内存<br>工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的<br>主内存副本拷贝
线程只能直接操作工作内存中的变量, 不同线程之前的变量值传递需要通过主内存来完成
内存间交互操作
read
把一个变量的值从主内存传输到工作内存中
load
在read之后执行,把read得到的值放入工作内存的变量副本中
use
把工作内存中一个变量的值传递给执行引擎
assign
把一个从执行引擎接收的值赋给工作内存的变量
store
把工作内存的一个变量的值传送到主内存中
write
在store之后执行,把store得到的值放入主内存的变量中
lock
作用于主内存的变量
unlock
内存模型三大特征
原子性
java内存模型保证了内存间交互操作时具有原子性
可见性
可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改<br>java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主<br>内存刷新变量值来实现可见性的.<br>
实现方式
volatile (关键字修饰)
synchronize ,对一个变量执行unlock 操作之前,必须把变量值同步回主内存
final ,被final 关键字修饰的字段在构造器中一旦初始化完成<br>并且没有发生this逃逸(其它线程通过this引用访问到初始化<br>一半的对象),那么其它线程就能看见final字段的值<br>
有序性
在本线程内观察,所有操作都是有序的,在一个线程观察另一个线程 <br>所有操作都是无序的,无序是因为发生了指令重排序,在java内存模<br>型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响<br>到单线程程序的执行,却会影响到多线程并发执行的正确性<br>
volatile 关键字通过添加内存屏障的方式来禁止指令重排<br>即重排序时不能把后面的指令放在内存屏障之前
也可以通过synchronize 来保证有序性,它保证每个时刻只有一个线程执行同步代码<br>相当于是让线程顺序执行同步代码
先行发生原则
单一线程原则
在一个线程内,在程序前面的操作先行发生于后面的操作
管程锁定规则
一个unlock操作先行发生于后面对同一个锁的lock操作
volatile 变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作
线程启动规则
Thread对象的start()方法调用先行发生于此线程的每一个动作
线程加入规则
Thread对象的结束先行发生于join()方法返回
线程中断规则
对线程interrupt()方法的调用先行发生于中断线程的代码检测到中断事件的发生<br>可以通过Interrupt() 方法检测到是否有中断发生
对象终结规则
一个对象的初始化完成(构造函数执行结束)先行于他的finalize() 方法的开始
传递性
如果操作A先行发生于操作B ,操作B先行发生于操作C,那么操作A先行发生于操作C
使用线程
实现Runnable接口
需要实现接口中的run() 方法 使用Runnable 实例再创建一个Thread,<br> 然后调用Thread实例的start()方法来启动线程<br>
public class MyRunnable implements Runnable {<br> @Override<br> public void run() {<br> // ...<br> }<br>}<br>public static void main(String[] args) {<br> MyRunnable instance = new MyRunnable();<br> Thread thread = new Thread(instance);<br> thread.start();<br>}<br>
实现Callable接口
与Runnable相比, Callable 可以有返回值, 返回值通过FutureTask 进行封装
public class MyCallable implements Callable<Integer> {<br> public Integer call() {<br> return 123;<br> }<br>}<br>public static void main(String[] args) throws ExecutionException, InterruptedException {<br> MyCallable mc = new MyCallable();<br> FutureTask<Integer> ft = new FutureTask<>(mc);<br> Thread thread = new Thread(ft);<br> thread.start();<br> System.out.println(ft.get());<br>}<br>
继承Thread类
同样也是需要实现run()方法,因为Thread类也实现了Runnable接口
public class MyThread extends Thread {<br> public void run() {<br> // ...<br> }<br>}<br>public static void main(String[] args) {<br> MyThread mt = new MyThread();<br> mt.start();<br>}<br>
实现接口 VS 继承Thread
Java不支持多重继承,因此继承了Thread类就无法继承其它的类,但是可以实现多个接口
类可能只要求可执行就行,继承整个Thread 类开销过大
中断
一个线程执行完毕之后会自动结束,如果再运行过程中发生异常也会提前结束
InterruptedException
通过调用一个线程interrupt() 来中断该线程,如果该线程处于阻塞,限期等待或者无限期等待状态<br>那么就会抛出InterruptedException.从而提前结束该线程,但是不能中断 I / O阻塞和synchronize<br>锁阻塞
public class InterruptExample {<br><br> private static class MyThread1 extends Thread {<br> @Override<br> public void run() {<br> try {<br> Thread.sleep(2000);<br> System.out.println("Thread run");<br> } catch (InterruptedException e) {<br> e.printStackTrace();<br> }<br> }<br> }<br>}
public static void main(String[] args) throws InterruptedException {<br> Thread thread1 = new MyThread1();<br> thread1.start();<br> thread1.interrupt();<br> System.out.println("Main run");<br>}
Interrupted()
如果一个线程的run() 方法执行一个无限循环,并且没有执行sleep() 等会抛出InterruptedException 的操作<br>那么调用线程的Interrupt() 方法就无法使线程提前结束
Executor 的中断操作
调用Executor 的shutdown() 方法会等待线程都执行完毕之后再关闭 , 但是如果调用的是 shutdownNow() 方法,<br>则相当于调用每个线程的Interrupt() 方法<br>
线程之间的协作
join <br>在线程中调用另一个线程的join() 方法,会将当前线程挂起,而不是忙等待<br>直到目标线程结束
public class JoinExample {<br><br> private class A extends Thread {<br> @Override<br> public void run() {<br> System.out.println("A");<br> }<br> }<br><br> private class B extends Thread {<br><br> private A a;<br><br> B(A a) {<br> this.a = a;<br> }<br><br> @Override<br> public void run() {<br> try {<br> a.join();<br> } catch (InterruptedException e) {<br> e.printStackTrace();<br> }<br> System.out.println("B");<br> }<br> }<br><br> public void test() {<br> A a = new A();<br> B b = new B(a);<br> b.start();<br> a.start();<br> }<br>}<br>public static void main(String[] args) {<br> JoinExample example = new JoinExample();<br> example.test();<br>}<br>
wait() notify () notifyAll()<br>调用wait() 使得线程等待某个条件满足, 线程在等待时会被挂起,当其他线程运行是的这个条件满足时<br>其它线程会调用notify() 或者 notifyAll() 来唤醒挂起的线程
wait() 和 sleep() 的区别<br>
wait()是Object的方法 , 而sleep() 是Thread 的静态方法
wait() 会释放锁 , sleep()不会
J.U.C - AQS<br>
CountDownLatch
用来控制一个或者多个线程等待多个线程
CyclicBarrier
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行
和CountDownLatch 相似 ,都是通过维护技术器来实现的,<br>线程执行await()方法之后计数器会减 1 ,并进行等待,直到<br>计数器为 0 ,所有调用await()方法而在等待的线程才能继续<br>执行
CyclicBarrier 和 CountDownLatch 的一个区别是,<br>CyclicBarrier 的计数器通过调用reset() 方法可以循环<br>使用,所以它才叫做循序屏障
Semaphore
semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数
FutureTask
FutureTask 实现了 RunnableFuture接口,该接口继承自Runnable 和 Future<V> ,<br>这使得FutureTask 即可以自当做一个任务执行,也可以有返回值<br>
BlockingQueue
FIFO 队列 : LinedBlockingQueue . ArrayBlockingQueue(固定长度)
优先级队列 : PriorityBlockingQueue
提供了阻塞的take() 和 put() 方法,如果队列为空take() 将阻塞,直到队列中有内容<br>如果队列为满put() 将阻塞,直到队列有空闲位置
线程安全
0 条评论
下一页