JAVA-DAY15(锁和死锁。集合容器的整理及其比较)

锁和死锁

锁是java中用来保证线程操作原子性的一种机制。

锁是数据库中用来保证事物操作原子性的一种机制。

 java中的锁有synchronized和Lock锁。

synchronized是关键字,可以锁代码块,也可以锁方法。

Lock是类(官方推荐),只能锁代码块

package Charter16;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo01 {
    public static void main(String[] args) {
        CountRunnable runnable=new CountRunnable();
        for (int i = 0; i <10000; i++) {
            //会开辟10000子线程,共享同一个runnable对象
            Thread t=new Thread(runnable,"子线程"+i);
            t.start();
        }
    }
    static class CountRunnable implements Runnable{
        Lock lock=new ReentrantLock();//声明一个锁变量(成员变量)
        //static int i=0;//(1)静态变量i在方法区,线程共享
        int i=0;//(2)i在这是成员变量在对象中,对象在堆中,线程共享
        @Override
        public void  run() {
            //int i=0;//(3)i在这是局部变量。在方法入栈的时候,在栈中创建,线程私有的
            //上锁就不会发生脏读,这里是锁代码块
            lock.lock();//上锁
                i++;
                System.out.println(Thread.currentThread().getName() + ":" + i);
                lock.unlock();//开锁
        }
    }
}

 我们把数据类型分为线程安全类型线程不安全类型(多线程共享情况下才需要考虑线程安全问题,不安全的性能更好)

如果一个数据类型需要我们自己手动加锁来保证其操作的原子性,那么它就是线程不安全的。

如果一个数据类型能够自己在方法中加锁来保证其操作的原子性,那么它就是线程安全的。

线程不安全线程安全
ArrayList1.Vector   2.CopyOnWriteArrayList
HashMap1.Hashtable   2.ConcurrentHashMap
String,StringBuilderStringBuffer
int,IntegerAtomicInteger
其他..........................

产生死锁的必要条件是什么?

   1,互斥条件:锁要具有排他性,在同一时刻,锁只能被一个线程持有。

   2,请求与保持条件:一个线程因为请求其他资源被阻塞的时候,对已获取的资源保持不释放

   3,不剥夺条件:一个线程没有主动释放资源之前,是不能被其他线程强行剥夺的

   4,循环等待条件:A线程持有资源a的锁,B线程持有资源b的锁,在互相不释放自己持有的锁的情况下,去请求对方持有的锁,这时候会形成双方循环等待,造成永久阻塞。

如何解决死锁?

   破坏任意一个条件即可

    1,破坏互斥条件:用共享锁,在同一时刻,锁可以被多个线程持有

    2,破坏请求与保持条件:一次性申请所有资源

    3,破坏不剥夺条件:一个线程因为请求其他资源被阻塞的时候,主动释放对已获取的资源

    4,破坏循环等待条件:所有的线程按照同样的顺序请求资源

集合容器的整理

1,Collection和Map的区别

   1.Collection和Map是官方提供的集合容器的两大体系的顶层接口

   2.Collection代表单元素集合体系

   3.Map代表KV键值对集合体系

   4.Collection接口继承了Iterable接口,所有的子类都提供了迭代器的实现,Map体系没有。

2,List, Set , Queue的区别

   1.List,Set,Queue都是Collection体系下的子接口,分别代表三个体系

   2.List体系的特点是有序,不唯一

   3.Set体系的特点是无序,唯一

   4.Queue体系特点是先入先出

3,队列(Queue)和栈(Stack)的区别   

(这里的栈是一种先入后出的数据结构,虚拟机栈中的栈是这种数据结构的一种应用)

   1.队列是一种FIFO(First In First Out)先入先出的结构

   2.栈是一种FILO(First In Last Out)先入后出的结构

   java集合体系中LinkedList类可以实现队列和栈结构

   在链表头部插入尾部取出或者尾部插入头部取出就是队列(插入和取出在不同方法上进行)

   在链表头部插入头部取出或者尾部插入尾部取出就是(插入和取出在相同方法上进行)

4,Array怎么转换为ArrayList,ArrayList怎么转换为Array。

   1.官方提供的数组工具类Arrays中提供了一个静态方法asList()可以把数组转换成List,参数是数组,返回值是List

   2.ArrayList类中提供了toArray()方法,可以把ArrayList转换成Array后进行返回

package Charter16;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Demo03 {
    public static void main(String[] args) {
        List<String>list=new ArrayList<>();
        list.add("张三");
        list.add("李四");
        //直接调用toArray返回Object类型数组
        Object[]objectArray= list.toArray();
        //调用toArray的时候给一个空数组来告诉他需要的数组类型
        String[]array=list.toArray(new String[]{});
        a(array);
        b(Arrays.asList(array));
    }
    static void a(String[]arr){
        for(String s:arr){
            System.out.println(s);
        }
    }
    static void b(List<String>list){
        for(String s:list){
            System.out.println(s);
        }
    }
}

5,HashSet和TreeSet的区别

   HashSet和TreeSet都是Set接口下面的子类

   HashSet的底层是HashMap,他将数据存储在HashMap的key中,HashSet是无序,唯一的,这是因为HashMap的key无序,唯一是的

   TreeSet的底层是TreeMap,他将数据存储在TreeMap的key中,TreeSet是有序,唯一的,这是因为TreeMap的key是有序,唯一

6,HashMap和TreeMap的区别

    HashMap底层是数组+链表/红黑树,key是无序的,唯一的

    TreeMap底层是红黑树,key是有序的,唯一的

    HashMap的性能比TreeMap更好,但如果需要一个有序的key集合,需要使用TreeMap

7,HashMap的底层原理(数据结构+put()流程+resize()扩容策略)

    1,HashMap的put方法的底层原理

       HashMap在]DK1.8之前是数组+链表,JDK1.8之后是数组+链表/红黑树,HashSet的底层是                 HashMap

      1.根据key的hashCode计算出数组index

      2.落槽时

      (1).如果数组中节点为null,创建新的节点对象,把k,v存储在节点对象中,把节点对象存储在数组中

     (2).如果数组的节点不为null,判断节点的key与插入元素的key是否相等
           (1).相等,直接用新的k ,v覆盖原节点中的k,v
           (2).不相等,判断此时节点是否为红黑树
               (1).是红黑树,创建红黑树节点对象存储k,v,插入到红黑树中
               (2).不是红黑树,创建链表节点对象存储k,v,插入到链表中,判断链表长度是否大                                于阈值8
                    1.大于阈值8,链表转换为红黑树
      3.判断++size是否大于阈值,是就扩容

  2.HashMap的resize()扩容方法的底层原理

         HashMap默认初始容量是16

         resize()方法是在Hashmap中的size大于阈值时或者初始化时,就调用resize方法进行扩容每次扩容的时候始终是原数组长度的2倍,即长度永远是2的n次方
         扩容后节点对象的位置要么在原位置,要么偏移到两倍的位置

8, HashMap的长度为什么是2的幂次方

为了能让HashMap存取高效,尽量减少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。

9,ArrayList和Vector的区别(带一下CopyOnWriteArrayList)

    ArrayList是线程不安全的,Vector是线程安全的。ArrayList中所有的方法都没有加同步锁,Vector中所有的方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个CopyOnWriteArrayList,使用了Lock锁实现线程安全。然后弃用了Vector,因为Lock锁的性能比synchronized锁的性能更好

   在并发编程中,如果多个线程共享一个ArrayList,那么必须考虑线程安全的问题,可以自己在代码中对ArrayList操作代码加锁,或者直接用线程安全的CopyOnWriteArrayList类

   在不考虑线程安全的环境下,用ArrayList性能更好,因为加锁开锁是很影响性能的

10,HashMap和Hashtable的区别(带一下ConcurrentHashMap)

  1. HashMap是线程不安全的,Hashtable是线程安全的。HashMap中所有的方法都没有加同步锁,Hashtable中所有的方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个ConcurrentHashMap,使用了Lock锁实现线程安全。然后弃用了Hashtable,因为Lock锁的性能比synchronized锁的性能更好

  2.在并发编程中,如果多个线程共享一个HashMap,那么必须考虑线程安全的问题,可以自己在代码中对ArrayList操作代码加锁,或者直接用线程安全的ConcurrentHashMap类

  3.在不考虑线程安全的环境下,用HashMap性能更好,因为加锁开锁是很影响性能的。

  4.对Null key支持:HashMap支持key为null,但只能有一个,而Hashtable不支持key为null,会直接抛NPE,HashMap和Hashtable支持Value为空,不限制个数,ConcurrentHashMap的key和value都不支持null。

  5.HashMap在1.8之后,设置了阈值=8,当链表长度超过阈值的时候,会转换为红黑树以减少检索时间,Hashtable被弃用了,没有更新

  6.初始容量大小和扩容容量大小的区别:

     HashMap初始容量是16,扩容策略是原来的2倍。

     HashMap如果手动指定了初始容量,不是2的n次方他也会找到最近的一个2的n次方作为初始容量。

     例如: HashMap map=new HashMap();size=16

                 HashMap map=new HashMap(27);size=32

   Hashtable默认初始容量是11,扩容策略是原来的2n+1

   Hashtable如果手动指定了初始容量,会直接使用给定的大小。

    例如: Hashtable map=new Hashtable();size=11

                HashMap map=new HashMap(27);size=27

  7.Hashtable采用的锁全表的机制,ConcurrentHashMap采用了分段锁的设计,锁粒度更细,性能更好

10,HashMap为什么不直接使用key的hashCode()函数返回的哈希码作为落槽时的索引号, HashMap是怎么解决的呢?


这题也可以这样问"HashMap的底层是如何计算key落槽时的索引的”
hashCode()方法返回的是 int整数类型,其范围为-(2^31)~(2^31-1),约有40亿个映射空间,而 HashMap的容量范围是在16(初始化默认值)~2N^30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过`hashCode()`计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
HashMap自己实现了自己的 hashCode()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均。

11,== 和equals()方法的区别

   ==和equals()都可以用于比较,语法是a==b或者a.equals(b)

   ==比较的是内存地址

   equals()方法是Object类中方法,可以被任意类继承或者重写,通过看官方Object类原码知道equals()方法默认也是用==比较内存地址

   如果想要修改equals()方法的比较规则,可以重写equals()方法

   String类就重写equals()方法的比较规则,由默认的比较两个字符串对象的内容地址,修改为比较字符串在中每个字符是否相等。

   因为堆区中可能出现两个一模一样的字符串对象,但内存地址不一样,所以字符串的比较必须用equals()方法,否则可能会出现两个内容一模一样的字符串,因为地址不一样比较后出现不相等的情况

12,为什么重写了hashCode()方法,必须也要重写equals()方法

HashMap的底层采用了key的Hashcode()来计算数组的索引 index

如果数组[index]为null,说明Key不存在,直接落槽插入

如果数组[index]不为null的话,说明该位置有key存在,但不一定说明已存在的key与要插入的key重复,因为可能会发生哈希碰撞,此时应该进一步用equals方法比较已存在的key与要插入的key是否相等。如果相等就说明一定是重复的,应该覆盖,如果不相等,说明发生了哈希碰撞,那么应该插入链表中。

重写equals方法的目的是为了不去比较两个对象对方内存地址,而是改为比较对象的内容,如果一个类重写了equals,没有重写hashcode就可能出现两个地址不同对象equals比较相等,但是hashcode比较不相等,这样会违反HashMap的唯一性,因此,重写equals方法必须也要重写hashcode方法,且必须满足两个对象equals相等,hashcode也必须相等。

13, Arrays和Collections的区别


Arrays是操作数组的工具类, Collections是操作collection体系的工具类工具类中提供了对数组和集合的排序,二分查找等静态方法
(注意 Collection是接口, Collections是类)
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值