Loading

JAVA 多线程---面经

线程与进程

提到进程那就要说程序,程序有指令和数据,程序从磁盘加载到内存,cpu获得指令进行执行,其中还会用到各种资源如网络资源,磁盘等。一个程序从磁盘进入内存,就是进程实例的创建。

一个程序可以有多个进程实例,比如浏览器,一个浏览器有网络进程,存储进程,gpu进程,各个标签页也有进程,浏览器插件等,很多进程。

一个程序也可也可能只有一个进程实例,比如网易云音乐,360安全等

一个进程内可以创建多个线程,同个进程内的线程可以共享进程的某些内存,线程的上下文切换消耗资源更少,更轻量。

JVM进程启动会启动哪些线程? - 将王相 - 博客园 (cnblogs.com)

yield

只是让出cpu,从running到就绪状态,如果得到cpu时间片后,继续执行。

sleep,join,wait,park

join:比如在main线程,创建t1线程,那么t1.join需要等待t1中的代码执行完成后,main线程继续向下执行

wait,notify:调用wait方法,线程进入等待池。调用notify则等待池中随机唤醒一个进入锁池。调用notifyall则唤醒等待池中所用线程进入锁池。

sleep和wait比较:

  • sleep不释放锁,wait让锁进入等待池
  • sleep不需要sychronized来先获得锁
  • sleep时Thread类,wait时object类

LockSupport.park() ,也是可以阻塞线程,通过其他线程unpark来继续向下走。底层是通过unsafe类的park方法实现。

park

park,unpark执行顺序,与次数问题

先执行unpark,后执行park,也是可以的!!

先执行多次unpark,再执行一次park也是可以的,但是再执行一次park,那么就会阻塞了。原因是多次执行unpark仅仅是计数器赋值为1,并不是加1操作。

synchronized

对象头

markword:gc年龄,hash值,锁状态(无锁,偏向锁,轻量级锁,重量级锁)

classpoint:指向该对象所属的类

synchronized关键字,使用反编译可以看到监视器的enter和exit,moniter的实现由虚拟机的c++代码实现,这个类为moniterobject,其中又count,owner(获得了锁的当前线程),entrylist(阻塞中的线程,获取不到锁的线程),waitset(等待的线程,调用wait方法等待)。

锁升级

吊打Java面试官-Java锁升级详解 - 云+社区 - 腾讯云 (tencent.com)

Java锁升级_zycxnanwang的博客-CSDN博客

markword记录了锁状态,当一个线程第一次获得这个对象锁时,那么也就从无锁状态进入偏向锁状态

偏向锁:第一次使用CAS操作将线程id放到MarkWord头中,之后再获取对象锁时,如果发现这个线程id是自己的就不用重新CAS。否则CAS竞争,成功则重新设置线程id,失败则升级为轻量级锁。

轻量级锁:将MarkWord通过CAS放到线程的锁记录空间中,然后对象头的Markword指向锁记录地址。如果只有一个线程尝试获取轻量级锁,会进行自旋等待,一旦有两条以及以上的线程抢占该锁,轻量级锁会升级为重量级锁。

锁标志位置为10,Mark Word存储的就是指向重量级锁的指针

ReentrantLock

  • 可重入
  • 可打断
  • trylock设置超时
  • 公平锁或非公平
  • condition,这个的实现是一个conditionObject,链表数据结构,每个node节点存储当前等待的线程。

Reentrantlock类比MonitorObject,那么CHL队列就是EntryList,存放阻塞的线程。condition就是waitSet,等待的线程存放到condition中。(对于锁的控制,还以深入,比如设计一个同步器,需要的数据结构,这个数据结构中,要有计数器,阻塞队列,等待队列,当前执行中的线程,等等这些都是一通百通的)

活跃性

  • 死锁
  • 活锁:比如一个线程对count++,一个线程对count--,这样永远执行下去
  • 饥饿锁:由于优先级太低,导致的得不到执行的线程

volatile

  1. 可见性
  2. 防止指令重排

Fork/Join

可以用来计算斐波那契,1-n的和。它可以分线程来计算。

ForkJoinPool为线程池。需要实现RecursiveTask或RecursiveAction作为任务

ConcurrentHashMap

BlockingQueue

比如ArrayBlockingQueue,LinkedBlockingQueue原理是通过reentrantlock,condition进行锁控制。

ArrayBlockingQueue

    /**
     * Inserts the specified element at the tail of this queue, waiting
     * for space to become available if the queue is full.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

如果队列满了,调用notFull.await();方法,所有调用put方法的线程,都放入notFull这个condition中。也就是说当队列满的时候,当前所有想要put的线程,都将进入notFull这个condition中等待。

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

可以看到take,这里的如果队列为空,那么所有想要获得队列的线程,则都进入notEmpty这个condition等待!

lock.lockInterruptibly();这个方法是,如果当前线程是中断状态,则直接抛出异常。否则去获得锁(也就是lock方法)。

在ArrayBlockingQueue,linkedBlockingQueue,都是通过reentranlock加锁,通过notEmpty,notFull这两个condition来限制队列为空,为满。

posted @ 2022-02-09 17:28  KeBoom  阅读(74)  评论(0编辑  收藏  举报