借用Java并发编程实践中的话”编写正确的程序并不容易,而编写正常的并发程序就更难了",相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,下面算是对多线程情况下同步策略的一个简单介绍。
问题—:什么是线程安全问题?
线程安全问题是指当多个线程同时读写一个状态变量,并且没有任何同步措施时候,导致脏数据或者其他不可预见的结果的问题。Java中首要的同步策略是使用Synchronized关键字,它提供了可重入的独占锁。
问题二:什么是共享变里可见性问题?
要谈可见性首先需要介绍下多线程处理共享变量时候的Java中内存模型。
Java内存模型规定了所有的变量都存放在主内存中,当线程使用变量时候都是把主内存里面的变量拷贝到了自己的工作空间或者叫做工作内存。
问题三:原子性?
3.1介绍
假设线程A执行操作Ao和线程B执行操作Bo ,那么从A看,当B线程执行Bo操作时候,那么Bo操作全部执行,要么全部不执行,我们称Ao和Bo操作互为原子性操作,在设计计数器时候一般都是先读取当前值,然后+1,然后更新会变量,是读-改-写的过程,这个过程必须是原子性的操作。
问题四:CAS介绍?
CAS即CompareAndSet,也就是比较并设置,CAS有三个操作数分别为:内存位置,旧的预期值,新的值,操作含义是当内存位置的变量值为旧的预期值时候使用新的值替换旧的值。通俗的说就是看内存位置的变量值是不是我给的旧的预期值,如果是则使用我给的新的值替换他,如果不是返回给我旧值。这个是处理器提供的一个原子性指令。上面介绍的AtomicLong 的自增就是使用这种方式实现:
问题五:什么是可重入锁?
当一个线程要获取一个被其他线程占用的锁时候,该线程会被阻塞,那么当一个线程再次获取它自己已经获取的锁时候是否会被阻塞那?如果不需要阻塞那么我们说该锁是可重入锁,也就是说只要该线程获取了该锁,那么可以无限制次数进入被该锁锁住的代码。
问题六:Synclronized关键字?
6.1 Synchronized介绍
synchronized块是Java提供的一种强制性内置锁,每个Java对象都可以隐式的充当一个用于同步的锁的功能,这些内置的锁被称为内部锁或者叫监视器锁,执行代码在进入synchronized 代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时候会阻塞掉。拿到内部锁的线程会在正常退出同步代码块或者异常抛出后释放内部锁,这时候阻塞掉的线程才能获取内部锁进入同步代码块。
问题七: ReentrantReadWriteLock介绍?
使用synchronized可以实现同步,但是缺点是同时只有一个线程可以访问共享变量,但是正常情况下,对于多个读操作操作共享变量时候是不需要同步的,synchronized时候无法实现多个读线程同时执行,而大部分情况下读操作次数多于写操作,所以这大大降低了并发性,所以出现了ReentrantReadWriteLock,它可以实现读写分离,多个线程同时进行读取,但是最多一个写线程存在。
问题八:Volatile变里?
对于避免不可见性问题,Java还提供了一种弱形式的同步,即使用了volatile关键字。该关键字确保了对一个变量的更新对其他线程可见。当一个变量被声明为volatile时候,线程写入时候不会把值缓存在寄存器或者或者在其他地方,当线程读取的时候会从主内存重新获取最新值,而不是使用当前线程的拷贝内存变量值。
问题九:乐观锁与悲观锁?
9.1悲观锁
悲观锁,指数据被外界修改持保守态度(悲观),在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。数据库中实现是对数据记录进行操作前,先给记录加排它锁,如果获取锁失败,则说明数据正在被其他线程修改,则等待或者抛出异常。如果加锁成功,则获取记录,对其修改,然后事务提交后释放排它锁。
一个例子:select * from表where .. for update;复制代码
悲观锁是先加锁再访问策略,处理加锁会让数据库产生额外的开销,还有增加产生死锁的机会,另外在多个线程只读情况下不会产生数据不一致行问题,没必要使用锁,只会增加系统负载,降低并发性,因为当一个事务锁定了该条记录,其他读该记录的事务只能等待。
问题十:独占锁与共享锁?
根据锁能够被单个线程还是多个线程共同持有,锁又分为独占锁和共享锁。独占锁保证任何时候都只有一个线程能读写权限,ReentrantLock就是以独占方式实现的互斥锁。共享锁则可以同时有多个读线程,但最多只能有一个写线程,读和写是互斥的,例如ReadWriteLock读写锁,它允许一个资源可以被多线程同时进读操作,或者被一个线程写操作,但两者不能同时进行。
问题十一:公平锁与非公平锁?
根据线程获取锁的抢占机制锁可以分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的时间多少来决定的,也就是最早加锁的线程将最早获取锁,也就是先来先得的FIFO顺序。而非公平锁则运行闯入,也就是先来不一定先得。
ReentrantLock 提供了公平和非公平锁的实现:
- 公平锁ReentrantLock pairLock = new ReentrantLock(true);
- 非公平锁ReentrantLock pairLock = new ReentrantLock(false);
问题十二:bstractQueuedSynchronizer介绍?
AbstractQueuedSynchronizer提供了一个队列,大多数开发者可能从来不会直接用到AQS,AQS有个变量用来存放状态信息 state,可以通过protected的getState,setState,compareAndSetState函数进行调用。对于ReentrantLock来说,state可以用来表示该线程获可重入锁的次数,semaphore来说state用来表示当前可用信号的个数,FutuerTask用来表示任务状态(例如还没开始,运行,完成,取消)。
问题十三:CountDownLatch原理?
问题十四:EeentrantLock独占锁原理?
问题十五:ReentrantReadHriteLock原理?
问题十六:什么是重排序问题?
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序可以保证最终执行的结果是与程序顺序执行的结果一致,并且只会对不存在数据依赖性的指令进行重排序,这个重排序在单线程下对最终执行结果是没有影响的,但是在多线程下就会存在问题。
问题十七:什么是中断?
Java中断机制是一种线程间协作模式,通过中断并不能直接终止另一个线程,而是需要被中断的线程根据中断状态自行处理。例如当线程A运行时,线程B可以调用A的interrupt()方法来设置中断标志为true,并立即返回。设置标志仅仅是设置标志,线程A并没有实际被中断,会继续往下执行的,然后线程A可以调用isInterrupted方法来看自己是不是被中断了,返回true 说明自己被别的线程中断了,然后根据状态来决定是否终止自己活或者干些其他事情。
问题十八:FutureTask原理?
问题十九:ConcurrentHasHMap原理简述?
翻看ConcurrentHashMap 的源码知道ConcurrentHashMap使用分离锁,整个map分段segment,每个segments是继承了ReentrantLock,使用ReentrantLock的独占锁用来控制同一个段只能有一个线程进行写,但是不同段可以多个线程同时写。另外无论是段内还是段外多个线程都可以同时读取,因为他使用了volatile语义的读,并没加锁。并且当前段有写线程时候,该段也允许多个读线程存在。put的大概逻辑,首先计算key 的hash值,然后根据一定算法(位移和与操作)计算出该元素应该放到那个segment,然后调用segment.put方法,该方法里面使用ReentrantLock进行写控制,第一个线程tryLock获取锁进行写入,其他写线程则自旋调用tryLock循环尝试。get的大概逻辑,使用UNSAFE.getObjectVolatile在不加锁情况下获取 volatile 语义的值。
需要获取这份阿里P8大牛总结的这20个并发编程知识点的小伙伴可以转发+关注后私信(学习)即可获取!