Interview multithreading and concurrency topics

1. Synchronous container classes and defects in Java

In Java, the synchronization container mainly includes 2 categories:

  1)Vector、HashTable。

  2) The class created by the static factory method provided in the Collections class. Collections.synchronizedXXX ().

defect:

  1) Performance issues.

  When there are multiple threads to access, if multiple threads are only performing read operations, then only one county can read at a time, and other threads can only wait, and these threads must compete for the same lock.

  2) ConcurrentModificationException exception.

  When iteratively modifying Vector and other containers, this problem will not occur in concurrent containers (such as ConcurrentHashMap, CopyOnWriteArrayList, etc.).

2. Why is ConcurrentHashMap weakly consistent? And why does multiple threads modify ConcurrentHashMap concurrently without reporting ConcurrentModificationException?

1)ConcurrentHashMap #get()

  It is precisely because the GET operation is a lock-free operation almost all the time (There is a readValueUnderLock call in GET, but the probability of this sentence being executed is extremely small), so that PUT and GET on the same Segment instance can be performed at the same time, which is GET Operation is the root cause of weak consistency.

2)ConcurrentHashMap #clear()

public void clear(){
    for(int i=0;i<segments.length;++i)
        segments[i].clear;
}  

  Because there is no global lock, after the elimination of a segment, when the next segment is being cleaned, the cleaned segment may be added to the data, so when the clear returns, there may be data in ConcurrentHashMap. Therefore, the clear method is weakly consistent.

Iterator in ConcurrentHashMap

  During the traversal, if the contents of the traversed array have changed, the iterator will not throw a ConcurrentModificationException exception. If the contents of the untraversed array have changed, it may be reflected in the iteration process. This is the weakly consistent performance of the ConcurrentHashMap iterator.

  In this iterative method, when the iterator is created, the collection will no longer throw a ConcurrentModificationException when it is changed. Instead, it will replace the new data with the new data when it is changed, so as not to affect the original data. Replace with new data, so that the iterator thread can use the old data, and the writer thread can also complete the changes concurrently. More importantly, this ensures the continuity and scalability of multiple threads concurrent execution, which is the key to performance improvement. .

  In summary, the weak consistency of ConcurrentHashMap is mainly to improve efficiency, and is a trade-off between consistency and efficiency. To become strong consistent, you need to use locks everywhere, even global locks, which is the same as HashTable and synchronized HashMap.

3. Implementation principle of CopyOnWriteArrayList

  The CopyOnWrite container is a container that is copied when writing, that is, when we add elements to a container, it is not directly added to the current container, but the current container is first copied, a new container is copied, and then added to the new container Element, after adding the element, point the reference of the original container to the new container (change the point of the reference). The advantage of this is that we can concurrently read the CopyOnWrite container without locking, because the current container will not add any elements. Therefore, the CopyOnWrite container is also a separate idea of ​​reading and writing. Reading and writing are performed on different containers. Note that you need to lock when writing.

  1) The following code is to implement the add method in CopyOnWriteArrayList. You can find that you need to lock when adding, otherwise N copies will be copied when multi-threaded writing.

public boolean add(E e){
    final ReentrantLock lock = this.lock;//加的是lock锁
    lock.lock();
    try{
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements,len+1);
        newElements [len] = e;
        setArray (newElements); // Point the reference of the original container to the new container; 
        return  true ;
    }finally{
        lock.unlock();
    }
}

  在CopyOnWriteArrayList里处理写操作(包括add,remove,set等)是先将原始的数据通过Arrays.copyof()来生成一份新的数据,然后再新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象,这样保证了每次写都是在新的对象上。然后读的时候就是在引用的当前对象上进行读(包括get,iterator等),不存在加锁和阻塞。

  CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的“=”将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用。

  2)读的时候不需要加锁,如果读的时候有线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据(在原容器中进行读)。

public E get(int index){
    return get(getArray(),index);
}

  CopyOnWriteArrayList在读上效率很高,由于,写的时候每次都要将源数组复制到一个新的数组中,所以写的效率不高。

CopyOnWriteArrayList容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性的问题。

  1)内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。针对内存占用问题,可以

    a. 通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。

    b. 不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。

  2)数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据,马上能读到,请不要使用CopyOnWrite容器!!

4、Java中堆和栈有什么不同?

  栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其他线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率,县城会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile变量就可以发挥作用了。它要求线程从主存中读取变量的值。

5、Java中的活锁、死锁、饥饿有什么区别?

  死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁。

  饥饿:考虑一台打印机分配的例子,当有多个进程需要打印文件时,系统按照短文件优先的策略排序,该策略具有平均等待时间短的优点,似乎非常合理,但当短文件打印任务源源不断时,长文件的打印任务将被无限期地推迟,导致饥饿以至饿死。

  活锁:与饥饿相关的另外一个概念称为活锁,在忙式等待条件下发生的饥饿,称为活锁。

  不进入等待状态的等待称为忙式等待。另一种等待方式是阻塞式等待,进程得不到共享资源时将进入阻塞状态,让出CPU给其他进程使用。忙等待和阻塞式等待的相通之处在于进程都不具备继续向前推进的条件,不同之处在于忙等待的进程不主动放弃CPU,尽管CPU可能被剥夺,因而是低效的;而处于阻塞状态的进程主动放弃CPU,因而是高效的。

  活锁的例子:如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统有批准了T4的请求......T2可能永远等待(在整个过程中,事务T2在不断的重复尝试获取锁R)。

  活锁的时候,进程是不会阻塞的,这会导致耗尽CPU资源,这是与死锁最明显的区别。

  活锁指的是任务或执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。活锁和死锁的区别在于,处于活锁的实体是在不断地改变状态,所谓的“活”,而处于死锁的实体表现为等待;活锁有一定几率解开,而死锁是无法解开的。

  避免活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列中第一个事务获得锁。

 

Guess you like

Origin www.cnblogs.com/HuiH/p/12691355.html