多线程

1.什么是死锁?如何预防?

  • 死锁的四个必要条件:
    1.互斥条件,同一时间只能有一个线程获得资源。
    2.不可剥夺,一个线程已经占有资源,释放之前不会被其他线程抢占。
    3.请求和保持,线程等待过程中不会释放已占有的资源。
    4.循环等待,多个线程互相等待对方释放资源。

2.线程安全问题的活跃状态?

1.饥饿:
读写锁,多个线程读的情况下写线程会阻塞,会造成写线程一直饥饿等待。

2.活锁:
有时线程没有发生阻塞的情况下,任然会存在执行不下去的情况,活锁不会阻塞线程,线程会一直重复某个相同的操作,并一直失败重试。

如开发中使用的异步消息队列可能会造成活锁的问题,在消息队列的消费端如果没有正确的ack消息,并在执行过程中报错了,就会放回消息头,再次拿出来执行,一直失败的循环往复,这个问题除了正确的ack外,还可以将失败的消息放到延时队列中,等待一定的时间失败重试。

3.sleep和wait的区别?

1.所属类:sleep是Thread的方法,wait是Object的方法,sleep是针对线程的,理应在Thread类里,wait在lock里面,锁相关,对象头相关,所以在Object里面。
2.释放锁,wait涉及到锁相关,所以会释放锁,sleep是针对线程相关,不涉及到锁,所以不会释放锁。

4.线程与进程的区别?

1.进程是系统进行分配和调度的最小单位,进程是指在系统中正在运行的恶一个应用程序,程序一旦运行就是进程。
2.线程是操作系统调度的最小单位。

  • 当a是一个进程,当a fork可一个进程和他共享资源时,a和b成为线程。

5.java线程的生命周期?

1.大致包含五个状态:

  • 新建状态,new 出来的线程;
  • 就绪状态,调用线程的start方法,线程会等待cpu分配资源的阶段,谁先抢到cpu的资源,谁开始执行;
  • 运行状态,当前线程被调度并获得cpu的资源,进入运行状态。
  • 阻塞状态,在线程运行的时候,由于某些原因导致线程变为阻塞状态,如调用sleep(),wait()方法等等,这时候需要其他机制将当前线程唤醒,如notify(),notifyall()等等,被唤醒的线程再次变为就绪状态,等待cpu的调度再次变为运行状态。
  • 销毁状态。

6.程序开多少线程合适?

1.区别应用是什么样的线程:

7.notify和notifyAll区别?

  • 锁池和等待池的概念?
    1.锁池:当线程A获得某个对象的锁,而其他线程想要调用这个对象的synchronize方法获取该对象的锁,由于A线程已经持有当前对象的锁,其他尝试获取锁的线程会进入对象的锁池。
    2.等待池:线程A调用了对象的wait方法,线程A就会释放当前线程的锁,因为wait方法必须出现在synchronize同步方法或代码块中,因此调用wait之前已经持有了当前对象的锁,wait释放锁之后线程会进入改对象的等待池中,如果另外一个线程调用该对象的notifyAll方法时,那么处于等待池中的线程会全部进入到锁池中,准备争夺锁的拥有权,如果另外一个线程调用了notify方法,边会有一个线程进入对象的锁池中。

1.如果调用的对象的wait方法,那么线程便会处于对象的等待池中,等待池中的对象不会去竞争锁。
2.当线程调用对象的notifyAll方法(唤醒所有线程)或者notify方法(唤醒一个线程),唤醒的线程便会进入对象的锁池中去竞争锁的拥有权。

8.synchronize和lock的区别?

  • sync是java的一个关键字,jvm级别,lock是一个接口。

  • sync会自动释放锁,lock需要在finally块中手动释放。

  • lock提供了更多的方法,可以相应中断,而sync不行。
    uploading-image-77364.png

  • sync是非公平锁,不能保证等待锁的执行顺序,而lock默认是非公平锁,可以传入一个构造参数变为公平锁。

  • sync无法判断是否已经获取到锁,而lock可以通过tryLock判断。

  • 底层实现不一样,sync是同步阻塞的悲观并发策略,lock是同步非阻塞(基于volatile和cas实现)。

9.多线程之间如何通信?

  • 根据操作系统理论:多进程之间的通讯是通过:信号量,信号,套接字,有名管道,无名管道,共享内存,mq等来实现的,由于多线程之间共享进程的地址空间,所以原生就支持数据共享,当一个线程修改了进程的变量,另一个线程自然就能看到,所以原生就支持通讯,由于线程之间的并发会引起互斥操作,所以就需要同步机制:volatile,sync,ReentrantLock,ReadWriteLock等。

具体实现:
1.通过共享变量,volatile来实现。
2.使用wait()和notify()方法来实现,但是由于使用同一把锁,所以必须通知线程释放锁,另一个线程才能获取到锁,这样导致通知不及时。
3.CountDownLatch实现,通知线程达到指定条件,调用countDownLatch.countDown(),被通知的线程调用countDownLatch.await()。
4.使用Condition的await()和signAll();

10.sync的底层实现?以及locak的区别?

11.synchronized修饰静态方法和实例方法的区别?

1.修饰静态方法,是对类加锁
2.修饰实例方法,是对实例进行加锁,锁的是实例对象的对象头。

12.countDownLatch的用法?

1.让主线程await,业务线程去处理逻辑,处理完成调用countDownLatch.countDown(),CountDownLatch实例化时需要根据业务去选择CountDownLatch的count;
2.让业务线程await,主线程处理完数据后调用CountDownLatch.countDown(),此时业务线程被唤醒,处理数据。

13.线程池问题

  • Executor提供了几种线程池?


  • 线程池参数

  • 拒绝策略
    1.由调用线程处理改任务。
    2.丢弃任务,抛出异常。
    3.丢弃任务,不抛出异常。
    4.抛弃进入队列最早的那个任务。

  • 任务放置的顺序过程

  • 任务结束后会不会回收线程?
    如果是非核心线程,等待一段时间没有拿到任务,就会超时回收,如果指定允许核心线程睡眠(allowCoreThreadTimeOut),一段时间后就会睡眠,线程在worker的一个HashSet里面,由于HashSet是非线程安全的,所以需要ReentrantLock锁住

14.线程池的生命周期几种状态

1.running:接受新提交的任务,并且也能处理阻塞队列中的任务。
2.shutdown:关闭转台,不接受新提交的任务,但是可以继续处理阻塞队列中的任务。
3.stop:不接受新任务,也不处理队列中的任务,会中断正在处理的线程。
4.tidying:所有任务已经终止,workcount=0
5.terminated:在terminated()方法执行后进入此状态。

15.如何在方法栈中传递数据?

1.通过方法参数。
2.通过共享变量。
3.如果咋同一个线程中,ThradLocal传递。

16.ThreadLocalMap


对于第二种情况,在调用get时会自动清理掉那些key为null的

17.锁的四种状态以及升级过程?

  • synchronized锁升级过程
    1.如果是偏向锁:
    先检查当前对象中的mark word中的记录是否是当前线程id,如果是,则获取偏向锁执行同步代码块。如果不是,则通过cas操作替换线程id,替换成功获得偏向锁,失败则撤销替换升级轻量级锁。
    2.升级轻量级锁:
    参考:
    https://www.cnblogs.com/shan333/p/16736163.html

18.ThreadLocal相关

  • ThreadLocal的理解:
    ThreadLocal提供了线程的本地变量,保证访问的变量属于当前线程,每个线程都有一个变量副本,线程之间是隔离的。适用于在多线程下可以实现传递数据,实现线程隔离。

  • ThreadLocal应用场景:
    1.Spring事务模板类。
    2.SpringMvc获取httprequest,将httpRequest对象缓存到当前线程中。

  • ThreadLocal底层原理:
    1.每个线程都有自己独立的ThreadLocalMap对象。
    2.获取当前线程。
    3.获取当前线程的ThreadLocalMap对象。
    4.设定当前线程的ThreadlocalMap的entrty对象,key为ThreadLocal。

  • 为什么线程缓存的是ThreadLocalMap对象?
    ThreadLocalMap对象可以缓存多个ThreadLocal对象,每个ThreadLocal对象只能缓存一个变量值。当从ThreadLocal对象get时,会先获取当前线程的ThreadLocalMap,再从map中取出ThreadLocal对应的值。

  • 强,弱,软引用之间的区别?
    强引用:当内存不足时,jvm会进行gc垃圾回收,对于强引用对象,就算出现了OOM,强引用对象也不会被回收。

软引用(SoftReference):当内存充足情况下,不会被回收,当内存不足情况下,他会被回收。软引用通常会存在对内存比较敏感的系统中,如寄存器高速缓存。
(未设置虚拟机堆内存大小,软引用一直存在)

(-Xms5m-Xmx5m -XX:PrintGCDetails 设置堆内存大小)

弱引用(WeakReference):他比软引用的生存周期更短,对于弱引用的对象来说,只要有垃圾回收,不管内存空间是否充足,都会回收掉该对象占用的内存空间。
虚引用(PhantomReference):与其他几种引用不同,虚引用并不会决定对象的生命周期。

  • ThreadLocal内存泄漏问题
    内存泄漏:申请的内存空间无法释放。
    内存溢出:申请内存时内存不足。

    由图可以看出,ThreadLocalMap的entrty的key为指向堆内存的ThreadLocal的一个弱引用,当垃圾回收时,若ThreadLocal没有其他地方强引用时,key指向的引用就会断开,被垃圾回收掉,这是就出现了ThreadLocalMap中key为null的entrty,value还通过强引用指向其他的对象,只要当前线程不结束,ThreadLocalMap对象就会一直存在,因为这时还存在一个强引用的引用链:Current Thread Reference --> Current Thread --> ThreadLocalMap --> EntryValue --> Object,这时就造成内存泄漏。

  • 如何解决内存泄漏?
    1.ThreadLocal使用完毕是,调用remove方法清除数据。
    2.在set方法之前,会清除当前map中的key为null的entrty。

19.AQS相关

  • Lock锁底层实现:
    Aqs+cas+LockSupport实现。

  • LocalSupport用法:
    阻塞当前线程:LockSupport.park()
    唤醒阻塞的线程:LockSupport.unpark()

  • CAS:
    见上文

  • lock的实现

    1.非公平锁的实现:

当执行lock操作时,会先进行cas操作尝试更新state状态,若更新失败,则会重试,如果重试多次失败,则会把当前线程阻塞,放入队列中

  • AQS的实现:

1.state状态:0代表锁没有被其他线程获取,1代表锁被其他线程占有,大于1表示当前线程重入获取锁。
当多个线程通过cas操作去获取锁,此时只有一个线程更改锁状态成功,获取到锁,其他未获取到锁的线程会存放到一个链表(双向链表)里面,当T1线程执行完毕释放锁之后,通过cas操作更改锁的状态,并唤醒正在阻塞的线程重新参与到竞争锁的状态。

20.SemaPhore信号量原理

1.seamPhore可以用于控制访问某些资源的线程数目,他维护了一个许可证集合,有多少资源需要限制就要维护多少个许可证集合,假设有N个资源,就对应N个许可证,一个线程获取许可证就调用acquire方法,用完释放就调用release方法。
2.可以实现用于接口限流,底层基于AQS来实现,原理更改state状态,调用acquire方法将state减一,release方法加一。
例:

@Test
    void test7() throws Exception{
        Semaphore semaphore = new Semaphore(5);
        for (int i=0;i<10;i++){
            int finalI = i;
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+","+ finalI);
                } catch (InterruptedException e) {
                }
            }).start();
        }

21.CountDownLatch原理

它允许一个或多个线程等待直到其他线程中的一组操作完成,基于AQS来实现的。
CountDownLatch countDownLatch = new CountDownLatch(2);
AQS的state变量值为2,调用countDownLatch.countDown();state的值会减一,当AQS的state状态为0时,唤醒其他等待的线程。

@Test
    void test8(){
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(()->{
            try {
                System.out.println("t1开始执行");
                //子线程阻塞,想被唤醒调用countDown方法将aqs状态变为0
                countDownLatch.await();
                System.out.println("t1结束");
            } catch (InterruptedException e) {
            }
        }).start();
        countDownLatch.countDown();
        countDownLatch.countDown();
    }

posted @ 2023-03-31 22:15  StudyHardWork  阅读(20)  评论(0编辑  收藏  举报