Java中的线程安全和非线程安全以及锁的几个知识点

1.  线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

   线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是“脏”数据。

   比方说ArrayList是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashVector是线程安全的;StringBuilder是非线程安全的,StringBuffer是线程安全的。

2.  线程安全是通过线程同步来控制的,也就是synchronized关键字,因此会导致性能的降低,所以使用的时候如果是多个线程操作一个对象,那么就使用线程安全的Vector,否则就使用效率更高的ArrayList。

   需要注意的是非线程安全并不等于不安全,因为我如果每个线程都有一个自己的ArrayList,各自不会访问,那么用ArrayList是没有问题的。

3.  锁的几个机制:

   1.可重入锁:基于线程分配锁,而不是根据方法的调用来进行分配

class Main 
{
    public synchronized void method1() 
    {
        method2();
    }
     
    public synchronized void method2() 
    {    
    }
}

   假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。

   2.可中断锁

   如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。synchronized就不是可中断锁,而Lock是可中断锁。

   3.公平锁

   尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。相对的就有非公平锁,如synchronized就是非公平锁

4.  了解一下Synchronized

   大致的描述一下,个人感觉被Synchronized修饰的线程或者代码块,就只能一个人单独访问,其他人想访问就必须等到找个人使用完交给下一个人。这样的好处就是对于很多线程共用的一些变量不会出现几个线程同时在修改它,比方说我们的临界区元素和变量。它的最大的特征就是在同一时刻只有一个线程能够获得对象的监视器,从而进入到同步代码块或者同步方法之中。

   但是这样也会有一个问题,打个比方,我们在购物的时候排队,大家要是都到结账的时候才从钱包掏钱就不如自己先把自己的付款二维码准备好的效率快,就是使用Synchronized修饰的话运行时候效率会变低,这个时候我们可以了解下一个知识点:悲观锁和乐观锁。  

   1.悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。Synchronized就是悲观锁。

   2.乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。但是这样会有一个问题就是可能会出现冲突,就是你假设归你假设,别人不一定这么乖。所以我们利用版本号和CAS操作来实现这个。

   CAS设计到三个操作数::V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值了,自然而然可以将新值N赋值给V。反之,V和O不相同,表明该值已经被其他线程改过了则该旧值O不是最新版本的值了,所以不能将新值N赋给V,返回V即可。所以多个线程同时访问的时候就只会有一个线程成功。

   实例理解一下:

   比方说在内存地址V中,存放着值为10的变量,此时此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。线程1进行SWAP,把地址V的值替换为B,也就是12。

   3.那么这两种锁应用场景:乐观锁的话简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),悲观锁如synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

5.  上锁的话除了synchronized还要Lock操作

   1.synchronized的缺点:当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,并且等待的线程是不能做其他事情的,会影响执行的效率。通过Lock可以让等待的进程一直等待下去。

   2.Lock的几种方法:lock()方法是平常使用得最多的一个方法,就是用来获取锁;tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。unlock()释放锁,我们应该把释放锁的操作放在finally块中进行保证锁一定会被释放防止死锁的事情发生。

   3.Lock和Synchronized的区别:

    Lock不是Java语言内置的是一个接口,synchronized是Java语言的关键字,所以synchronized不需要用户去手动释放锁但是Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

    Lock可以让等待锁的线程响应中断,而synchronized却不行。

    通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

    Lock可以提高多个线程进行读操作的效率。

    

 

posted @ 2019-03-13 09:36  从让帝到the_rang  阅读(1064)  评论(0编辑  收藏  举报