【单机和分布式环境下的多线程并发访问的安全性问题】
有时间就稍微记录一下。【持续更新】
多线程并发访问的安全性问题
首先要知道的是:多线程安全性问题产生的原因是什么?
多线程并发访问是指当多个线程无序的访问同一个共享资源时,这个线程就受到了“多个线程的并发访问”。
多线程并发访问的安全问题
在出现多个线程并发访问的时候,这时就有可能产生并发访问的安全性问题,可能会导致共享资源的最后结果不是我们期望的结果。(此处不做具体演示)
该如何解决多线程并发访问的安全问题呢?
利用【线程同步】可以解决多线程并发访问的安全性问题。
线程同步的方式:
在单机环境中:
- synchronized同步方法
- synchronized同步代码块
- Lock锁(JDK1.5以后提供的显示锁,通常使用Lock接口的实现类ReentrantLock,它包含了:公平锁、非公平锁、可重入锁、读写锁 等更多更强大的功能)
注:
(1)synchronized锁属于jvm级别的锁,当代码执行完之后,JVM底层会自动释放锁。
(2)如果使用ReentrantLock,需要在finally代码块中手动释放锁。使用Lock显示锁的方式,解决线程安全问题,给开发人员提供了更多的灵活性。
(3)在单机的情况下,使用synchronized和Lock保证线程安全是没有问题的。
同步方法
我这里写一个方法:
public synchronized int getNumber(){
//被同步的代码。。
}
当一个线程进入到方法时,会立即锁死这个方法,其他线程会在外边等待。为啥呢?因为在加了synchronized同步锁之后,其他线程进不来,当内部的线程线程执行完之后,其他的线程才能进来,以此类推。这样可以保证每个线程可以完整的执行完,这样就避免了多线程并发访问的安全问题。
同步代码块
通过锁对象,来完成对线程的同步。
private Object obj = new Object();
public synchronized int getNumber(){
synchronized(obj){
//被同步的代码。。
}
}
这里是通过锁对象,保证多个线程进来后,访问到的都是一个对象,来完成对线程的同步。它可以出现在方法内部,一个方法内可以有多个同步代码块。
Lock锁
Lock它是一个接口,是在JDK1.5以后新出的锁。这里简单举个例子,实际工作中不要以此为标准。
Lock lock = new Lock的实现类
public synchronized int getNumber(){
lock.lock();//加锁
try{
//被同步的代码。。
}finally{
lock.unlock();//解锁
}
}
在分布式的环境中
如果是在分布式环境中,某应用部署了多个节点,每个节点可以使用synchronized和Lock保证线程安全,但是不同节点之间是没办法保证线程安全的。
- redis分布式锁
- zookeepper分布式锁
个人更推荐使用redis分布式锁,其效率相对来说更高一些
这里我详细说一下自己对redis分布式锁的简单理解。
实现redis分布式锁的方式我知道的有两种方式,使用jedis实现分布式锁可以很好的让我们去理解他的实现方式,但是redission开源之后,简化了redis分布式锁实现的复杂度(只是我们用起来简化了,内部还是原来的样子)。
jedis实现:
1.生成一个UUID
2.调用setNx方法拿到锁,设置这把锁的key对应的value(这个value的值就是第一步生成的UUID),同时设置他的有效期。setNx方法保证了它的原子性。
3.接下来要注意的问题点就是释放锁的时候,需要保证他的原子性。可以借助于lua脚本来实现。这也是为什么在第一步生成UUID的原因。
友情提示,上面的几点还存在一些问题。涉及到redis的一些特性的问题。如果想详细了解redis的分布式锁,可以自己去动手学习。
在实际工作中,我们可以使用redission来实现。为啥? 因为它简化了redis分布式锁的实现。仅仅几行代码就能搞定。但是如果去看它的源码会发现,redission是做了封装,他内部实现还是和上面提到的jedis的实现方式一样, 同时也将lua脚本封装到了代码中。
···························································································································································
持续更新,共同进步!