X

Java多线程知识点(下)

26) 如何写代码来解决生产者消费者问题?

我们先尝试使用队列实现一个生产者和消费者模式。那么就需要先了解队列,队里的接口定义如下
    Queue.
         添加:
                  add(E element) 添加一个,如果超出了界限,则抛出异常
                  Offer(E element) 添加一个,如果超出了界限,则返回false
        获取并删除
                  remove() 获取并删除队头,如果队列为空,则抛出异常
                  Poll() 获取并删除队头, 如果队列为空,则返回null
        获取但不删除
                   element() 获取但是并不删除,如果队列为空,则抛出异常
                  peek() 获取但是并不删除,如果队列为空,则返回null.
 而阻塞队列则多了两个阻塞的方法,put 和 take,参见下表

27) 如何避免死锁?

    首先,死锁是什么。死锁发生在Java的多线程里,通常是一个线程持有了锁A,尝试获取锁B;于此同时,另外一个线程持有了锁B,尝试获取锁A。两个线程发生了循环等待的问题。
依照我的经验,我一般使用jstack来打印线程信息,然后搜索deadlock来查看生产环境下的死锁发生在什么地方。避免死锁的方法也很简单,那就是获取锁的顺序,保持一致即可。
推荐一篇文章,总结的很好 https://javarevisited.blogspot.com/2018/08/how-to-avoid-deadlock-in-java-threads.html

28) Java中活锁和死锁有什么区别?

    活锁还是我第一次听到,大概的意思就是讲,两个线程在竞争同一个资源的时候,都不直接获取资源,下一次获取的时候,恰好又发生了碰撞。这样,每次都在碰撞,导致总是拿不到锁。解决的办法就是重试机制的随机扰动,比如等待的时间随机一下,就不会发生总是遇到的情况。

29) 怎么检测一个线程是否拥有锁?

        如何检验一个线程是否拥有锁,正确的办法是在Thread上有一个方法holdLocks(Object o) 可以判断。其实我觉得这个老外的方法也挺有意思的,他是怎么做的呢?他会调用object的wait方法,如果当前线程没有锁,会抛出IllegalMonitorStateException,通过检验这个异常就可以了。

30) 你如何在Java中获取线程堆栈?

    当然是使用Jstack命令来查看当前JVM中的所有线程的情况。同样的java/bin目录下还有很多工具
jstack
查看线程、死锁等情况
jstat
查看gc和内存情况
jmap
可以打印堆信息dump

31) JVM中哪个参数是用来控制线程的栈堆栈小的

     可以调整堆大小、gc算法、伊甸区空间大小等参数

32) Java中synchronized 和 ReentrantLock 有什么不同?

     前者是一个隐式的锁,比较重。后者是一个可重入锁,用户可以操控,决定什么时候加锁,什么时候解锁,更加灵活。
而且后者的实现方式是CAS的方式具有更好的性能。尤其提供的读写锁的分离,更加灵活高效。

33) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?

    这个其实就是考的是线程间的通信,所以你往线程的通信上去考虑就对了。比如我们的线程同步工具能不能?wait和notify能不能行?我能想到4中办法。
第一种,一个线程等待另外一个线程最简单的办法当时然join方法。在T3里start T2,并且调用T2的join方法可以保证T3等待T2完成。同样的办法在T2里start线程T1,T2就会等待T1完成。
第二种,线程间的通信,通过wait和notify实现。在T3中调用wait,并且在T2里完成了以后调用notify,通知T3线程醒来。同样的道理完成T2等待T1。不过这有个问题就是假唤醒的问题,需要特别处理下。
第三种,使用CountdownLatch。T3和T2公用一个,把计数器设置成1,并且在T2完成之后调用countdown的方法减一,保证T3能在T2完成之后被唤醒。
第四种,使用semaphore。 T3和T2公用一个,在T3里调用tryAcquire,在T2里调用release方法,保证T2完成之后可以唤醒T3线程。
第五种,歪门邪道。使用sleep方案。让T1,T2,T3能sleep不同的时间,当然还需要保证他们的sleep是同时开始的。如何保持同时开始呢,使用cyclicBarrier。

34) Thread类中的yield方法有什么作用?

调用这个会出让当前线程的cpu使用权,让大家在一起去竞争cpu资源,当然他自己有可能会再次抢到这个cpu资源,没有用过,不知道具体的使用场景。

35) Java中ConcurrentHashMap的并发度是什么?

    hashmap为什么不是线程安全的?这个要从他的实现讲起。hashmap的存储结构是一个数组结构,而当发生碰撞的时候,相同的槽位上的元素是以链表的形式组织的。线程不安全发生在扩容的时候(负载因子饱和之后),因为多个线程共享了数组的指针,导致了循环链表。当下一次读取的时候,因为循环链表就造成了死循环,cpu100%的问题。concurrentmap的解决办法是,使用了分段锁来保证线程安全和性能的问题。在jkd8里面更是采用了红黑树的存储结构来代替链表的数据结构。这个并发度其实就是初始的时候的这个数组的大小,默认是16.

36) Java中Semaphore是什么?

semaphore实际上就是并发量的控制。他有一个方法是tryAcquire和release。我们在执行代码之前,试着tryAcquire的话,如果当前的信号量过大,就会阻塞当前线程。知道其他线程调用了release,释放了名额,被阻塞的线程会被自动唤醒,继续自己的工作。实际上是多线程间的同步工具,其他工具如
CountdownLatch
await
Countdown
计数器用的,当计数器Countdown调用之后,-1, 当达到0的时候,就会唤醒所有调用了await的线程
CyclicBarrier
await 计数器用的,当达到他所设置的size之后,所有wait的线程会被唤醒
semaphore
tryAcquire
release
计数器用的,release之后-1,当达到限额之后,所有的tryAcquire会阻塞当前线程,知道有线程释放release之后,再唤醒大家来争抢。跟Countdownlatch的区别是他可以反复使用。
phaser
arriveAndAwaitAdvance
多阶段执行,可以定义n个阶段,使arriveAndAwaitAdvance这个方法可以让第一个阶段全部执行完成后,在同时进入下一个阶段

37)如果你提交任务时,线程池队列已满。会时发会生什么?

    在线程池执行的时候,如果任务来的过快过多,这么多线程都来不及处理这些任务,那么来不及处理的任务会进入一个等待队列,等待线程来执行他。如果线程池的队列满了,会抛出rejectexecutorExcption。

38) Java线程池中submit() 和 execute()方法有什么区别?

    这个比较容易,execute是Executor接口里定义的方法,他接受的参数是Runable的接口,而submit则是ExecutorService接口里提供的方法,他是有返回对象的,返回的是Future对象,他接受的参数是Callable的对象。Future可以判断当前的任务是否完成(isDone),并且调用ge会阻塞一直到结果返回。

39) 什么是阻塞式方法?

    阻塞方法是指方法调用之后,在该方法返回之前程序会一直等待知道有结果返回。对应的非阻塞方法是调用之后,不用等方法返回,该线程会接着做其他的事情。

40) Swing是线程安全的吗? 为什么?

    略。对swing的确了解不多,而且这个技术也过时了。

41) Java中invokeAndWait 和 invokeLater有什么区别?

 swing相关,暂无

42) Swing API中那些方法是线程安全的?

swing相关暂无

44) Java中的ReadWriteLock是什么?

Java的读写锁是为了读写分离使用的,先说如果没有读写锁,那么在一个读多写少的系统里,每一次读都要去竞争一把锁,但是实际上并没有人写,单纯的读是线程安全的。如果有了读写锁,在读的时候去拿读锁,再写的时候去拿读写锁。
    具体的使用是read的时候,getReadLock并且去lock,读完了以后unlock
    同理,write的时候,getWriteLock并且去lock,用完了unlock。
具体的实现是,read获取锁的前提是没有占用写锁,否则直接获取读锁,如果有写锁则进入等待队列。
如果这个时候,没有写锁,而队里里有等待写锁的线程,那么这个请求也会被放入等待队列,否则写锁永远得不到机会。
write获取锁的前提是,没有占用读锁,否则进入队尾等待。当读锁全部释放了以后,写锁获得,然后写数据。
Categories: Java学习
龙安_任天兵: 不忘初心,方得始终!