JavaWeb语法六:线程安全以及死锁

前言:

接下来我们要了解一下,线程安全的集合类有哪些?什么是死锁以及怎么避免死锁问题。

1.多线程环境使用哈希表

1.1:HashTable

只是简单的把关键方法加上synchronized关键字。

8b3ad3f714d24d6d84d3c9af2d61849d.png

public synchronized V put(K key, V value)

public synchronized V get(Object key)

这个是针对this来进行加锁。当多个线程来访问这个HashTable的时候,无论是啥样的操作,无论是什么样的数据,都会出现锁竞争。

一个HashTable只有一把锁,两个线程访问HashTable 中任意的数据都会出现锁竞争。

 这相当于直接针对hashtable对象本身加锁。

如果多线程访问同一个hashtable就会直接造成锁冲突。

size属性也是通过synchronized来控制同步,也是比较慢的。

一旦触发扩容,就有该线程完成整个扩容过程,这个过程会涉及大量的元素拷贝,效率会比较低。这是一次就完成整个扩容。

1.2:ConcurrentHashMap

81670615c6734a778f876e8e8d4dcabb.png

 1.读操作没有加锁(但是使用了volatile保证从内存读取结果),只对写操作进行加锁,加锁的方式仍然是使用synchronized。但只是"锁桶"(用每个链表的头节点作为锁对象)这样减低锁冲突的概率)。

2.充分利用CAS特性,比如size属性通过CAS来更新,避免出现重量级锁的情况。

3.优化了扩容方式:化整为零。

   。发现需要扩容的线程,每次操作只搬运一点点,通过多次操作完成整个搬运的过程。同时维护一个新的HashMap和一个旧的,查找的时候即需要查找旧的也要查找新的,插入的时候只插入新的,直到搬运完毕再销毁旧的。

1.3:面试题

hashtable和HashMap,ConcurrentHashMap之间的区别:

HashMap:线程不安全,key允许为null

HashTable:线程安全,使用synchronized锁HashTable对象,效率低,key不允许null.

ConcurrentHashMap:线程安全,使用synchronized锁每个链表头结点,锁冲突概率降低,充分利用CAS机制,优化了扩容机制,key不允许为null.

2:死锁

2.1:死锁是什么

多个线程同时被阻塞,他们中一个或者全部都在等待莫个资源被释放,由于线程被无限期的阻塞,因此程序不可能正常终止。

举一个例子:

你和你朋友一起去吃过桥米线,你首先拿到了辣椒油,你朋友拿到了醋,你们都要拿到醋和辣椒油才会吃。当你要你朋友先给你醋,你朋友要你先给他辣椒油。你们两个就一直坚持不下。这样就一直干着,这就叫死锁。

2.2:死锁的案例

1.一个线程,一把锁,连续加锁两次,如果这把锁是不可重入锁,就会形成死锁。

2.两个线程,先对自己拥有的锁加锁,在获取对方的锁。

  public static void main(String[] args) {
        Object lajiao =new Object();
        Object cu=new Object();
        Thread me=new Thread(()->{
            synchronized (lajiao) {
                System.out.println("me拿到了辣椒油");
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (cu) {
                    System.out.println("me拿到醋了");
                }
            }
        });
        Thread him=new Thread(()->{
            synchronized (cu) {
                System.out.println("him拿到醋了");
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (lajiao) {
                    System.out.println("him拿到辣椒油");
                }
            }
        });
        me.start();
        him.start();
    }
}

3.多个线程多把锁---哲学家就餐问题

1b37148d8d094f93a7b3635bf20ab790.png

 每个哲学家只做两件事:思考人生或者吃面条,思考人生的时候就会放下筷子。吃面条就会拿起左右两边的筷子(先拿起左手,再拿起右手)

如果哲学家发现筷子拿不起来(被别人占用),就会阻塞等待。

假设同一个时刻,哲学家同时拿起左手边的筷子,然后再尝试拿右手的筷子,就会发现右手的筷子都被占用,由于哲学家互不相让,这时候就形成了死锁。

2.3:死锁产生的四个必要条件:

互斥使用:即当资源被一个线程使用(占有)时,别的线程不能使用。

例如:线程1拿到了锁,线程2要想拿到锁,只能等着。

不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

例如:线程1拿到了锁,线程2要想要拿到锁,只等等到线程1主动释放锁。

请求和保持:即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

例如:线程1拿到锁A,再尝试获取锁B,但A这把锁还是保持的。(不会因为获取锁B,把锁A给释放了)。

以上三个条件都是锁自身的性质。

循环等待:即存在一个等待队列:p1占有p2的资源,p2占有p3的资源,p3占有p1的资源,这样就会形成一个循环等待。

2.4:如何避免死锁

上面讲了四个形成死锁的必要条件,要四个条件都要满足。但前三个都是锁。是无法动的,所以我们要避免死锁,只能破环循环等待。

破坏循环等待:N个线程尝试获取锁的时候,都按照固定的按标号从小到大的顺序来获取锁,这样就可以避免环路等待。

 

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值