赞
踩
从以下几个方面回答:
多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
因为单线程处理能力低打个比方,一个人去搬砖与几个人去搬砖,一个人只能同时搬一车,但是几个人可以同时一起搬多个车。
在Java里如何实现线程,Thread、Runnable、Callable。
线程可以获得更大的吞吐量,但是开销很大,切换线程需要的时间、还会产生死锁
采用无锁编程、使用线程池,重复利用线程,避免线程创建与销毁的开销、使用定时锁,超过时间自动释放。
ReentrantLock可重入锁是jdk内置的一个锁对象
synchronized也支持可重入锁。但是它是一个关键字,是一种语法级别的同步方式,称为内置锁
wait/notifyAll方式跟ReentrantLock/Condition方式的原理是一样的。
Java中每个对象都拥有一个内置锁,在内置锁中调用wait,notify方法相当于调用锁的Condition条件对象的await和signalAll方法
是一种把变量放到线程本地的方式来实现线程同步的。
使用ThreadLocal<UserInfo> userInfo = new ThreadLocal<UserInfo>()
的方式,让每个线程内部都会维护一个ThreadLocalMap,里边包含若干了 Entry(K-V 键值对),每次存取都会先获取当前线程,然后得到该线程对象中的Map,然后与Map交互。
Semaphore信号量被用于控制特定资源在同一个时间被访问的个数。类似连接池的概念,保证资源可以被合理的使用
一般情况下,我们不会使用wait/notifyAll或者ReentrantLock这种比较底层的类,而是使用并发包下提供的一些工具类。
CountDownLatch 计数器
CyclicBarrier 栅栏
AQS是很多同步工具类的基础,比如ReentrentLock里的公平锁和非公平锁,Semaphore里的公平锁和非公平锁,CountDownLatch里的锁等他们的底层都是使用AbstractQueuedSynchronizer完成的
wait/notify
如果调用wait方法,会释放当前锁持有的monitor,而sleep是没有释放锁的
控制线程执行顺序
锁是在在不同线程竞争资源的情况下对资源进行同步控制的工具
通常和wait,notify,notifyAll一块使用。
wait:释放占有的对象锁,释放CPU,进入等待队列只能通过notify/all继续该线程。
sleep:则是释放CPU,但是不释放占有的对象锁,可以在sleep结束后自动继续该线程。
notify:唤醒等待队列中的一个线程,使其获得锁进行访问。
notifyAll:唤醒等待队列中等待该对象锁的全部线程,让其竞争去获得锁。
拥有synchronize相同的语义,但是添加一些其他特性,如中断锁等候和定时锁等候,所以可以使用lock代替synchronize,但必须手动加锁释放锁
我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。
假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。synchronized关键字的实现也是悲观锁。
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。原子变量类就是使用了乐观锁的一种实现方式CAS实现的。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
下面是摘自《阿里云-java编码规范》
并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁
可中断锁:顾名思义,就是可以interrupt()中断的锁。 在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。 非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。 ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
new Thread弊端:
以下任务提交逻辑来自ThreadPoolExecutor.execute方法:
Java多线程与高并发(四):java.util.concurrent包
JUC中有非常多的类,将部分类按功能进行分类,分别是:
AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架,它是JUC并发包中的核心基础组件。
计数器闭锁是一个能阻塞主线程,让其他线程满足特定条件下主线程再继续执行的线程同步工具。
new CountDownLatch(10)首先设置计数器的数到AQS的state中,当调用await方法之后,A线程阻塞,随后每次其他线程调用countDown的时候,将state减1,直到计数器为0的时候,A线程继续执行。
信号量是一个能阻塞线程且能控制统一时间请求的并发量的工具。比如能保证同时执行的线程最多200个,模拟出稳定的并发量。
new Semaphore(3)传入的3就是AQS中state的值,也是许可数的总数,在调用acquire时,检测此时许可数如果小于0,就将被阻塞,然后将线程构建Node进入AQS队列
可以让一组线程相互等待,当每个线程都准备好之后,所有线程才继续执行的工具类
与CountDownLatch类似,都是通过计数器实现的,当某个线程调用await之后,计数器减1,当计数器大于0时将等待的线程包装成AQS的Node放入等待队列中,当计数器为0时将等待队列中的Node拿出来执行。
与CountDownLatch的区别:
名为可重入锁,其实synchronized也可重入,是JDK层级上的一个并发控制工具
条件对象的意义在于对于一个已经获取锁的线程,如果还需要等待其他条件才能继续执行的情况下,才会使用Condition条件对象。
与ReentrantLock结合使用,类似wait与notify。
不是AQS的子类,但是能拿到线程执行的结果非常有用。
Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。
但其实在项目中使用到最多的Future类是1.8提供的这个类,因为虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞方式得到任务的结果,阻塞的方式显然和我们的异步编程的初衷相违背。
Runnable:run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果
Callable:泛型接口,call()函数返回的类型就是传递进来的V类型,同时能结合lambda使用
为什么会有阻塞队列?
如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者暂停等待一下(阻塞生产者线程)或者继续将产品放入队列中
LockSupport是一个非常方便实用的线程阻塞工具类,他可以在线程内任意位置让线程阻塞,和Thread.suspend()相比,它填补了resume()在线程挂起之前执行,而导致的线程无法继续执行的情况。和Object.wait()相比,他又不需要先获得对象锁,也不会抛出InterruptedException异常。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。