并发编程从入门到放弃系列开始和结束(一)

发布于 2022-6-13 17:42
浏览
0收藏

 

对于 Java 部分的面试来说,突然想到并发这一块的内容是不太完整的,这篇文章会通篇把多线程和并发都大致阐述一遍,至少能够达到了解原理和使用的目的,内容会比较多,从最基本的线程到我们常用的类会统一说一遍,慢慢看。

并发编程从入门到放弃系列开始和结束(一)-开源基础软件社区

 并发编程

 

进程&线程

对于基本的概念,大家应该都很熟悉了,进程是资源分配的单位,线程是CPU调度的单位,线程是进程中的一个实体。

对于我们的Java程序来说,天生就是多线程的,我们通过main方法启动,就是启动了一个JVM的进程,同时创建一个名为main的线程,main就是JVM进程中的一个实体线程。

并发编程从入门到放弃系列开始和结束(一)-开源基础软件社区

 进程&线程

线程生命周期
线程几种基本状态:

  1. New,初始状态,就是New了一个线程,但是还没有调用start方法
  2. Runnable,可运行Ready或者运行Running状态,线程的就绪和运行中状态我们统称为Runnable运行状态
  3. Blocked/Wating/Timed_Wating,这些状态统一就叫做休眠状态
  4. Terminated,终止状态
    几个状态之间的转换我们分别来说。

 并发编程从入门到放弃系列开始和结束(一)-开源基础软件社区
New:我们创建一个线程,但是线程没有调用start方法,就是初始化状态。

Runnable:调用start()启动线程进入Ready可运行状态,等待CPU调度之后进入到Running状态。

Blocked:阻塞状态,当线程在等待进入synchronized锁的时候,进入阻塞状态。

Waiting:等待状态需要被显示的唤醒,进入该状态分为三种情况,在synchonized中调用Object.wait(),调用Thread.join(),调用LockSupport.park()。

Timed_Waiting:和Waiting的区别就是多了超时时间,不需要显示唤醒,达到超时时间之后自动唤醒,调用图中的一些带有超时参数的方法则会进入该状态。

Terminated:终止状态,线程执行完毕。

守护线程&用户线程
Java中的线程分为守护线程和用户线程,上面我们提到的main线程其实就是一个用户线程。

他们最主要的区别就在于,只要有非守护线程没有结束,JVM就不会正常退出,而守护线程则不会影响JVM的退出。

可以通过简单的方法设置一个线程为守护线程。

 Thread t = new Thread();
 t.setDaemon(true);

锁是控制多线程并发访问共享资源的方式,为了更简单快速的了解Java中的锁,我们可以按照显示锁和隐式锁来做一个大致的区分。

并发编程从入门到放弃系列开始和结束(一)-开源基础软件社区

 锁

隐式锁
在没有Lock接口之前,加锁通过synchronzied实现,在之前的Java基础系列中我已经说过了,就不在这里过多的阐述,此处引用之前写过的,更多详细可以看《我想进大厂》之Java基础夺命连环16问。

synchronized是java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁,使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现,主要作用就是实现原子性操作和解决共享变量的内存可见性问题。

执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。

执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。

如果再深入到源码来说,synchronized实际上有两个队列waitSet和entryList。

  1. 当多个线程进入同步代码块时,首先进入entryList
  2. 有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1
  3. 如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁
  4. 如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null
     并发编程从入门到放弃系列开始和结束(一)-开源基础软件社区
    显示锁
    虽然synchronized使用简单,但是也使得加锁的流程固化了,显示锁在Java1.5版本之后加入了Lock接口,可以通过声明式显示的加锁和解锁。
Lock lock = new ReentrantLock();
lock.lock(); //加锁
lock.unlock(); //解锁

独占锁
在上述的伪代码中,我们使用到了ReentrantLock,它其实就是独占锁,独占锁保证任何时候都只有一个线程能获得锁,当然了,synchronized也是独占锁。

这里我们看ReentrantLock的几个加锁接口。

void lock(); //阻塞加锁
void lockInterruptibly() throws InterruptedException; //可中断
boolean tryLock(); //非阻塞
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //超时加锁

这几个加锁接口,向我们明白地展示了他和synchronized的区别。

  1. 可中断加锁lockInterruptibly,synchronized可能会有死锁的问题,那么解决方案就是能响应中断。当前线程加锁时,如果其他线程调用当前线程的中断方法,则会抛出异常。
  2. 非阻塞加锁tryLock,调用后立刻返回,获取锁则返回true,否则返回false
  3. 支持超时加锁tryLock(long time, TimeUnit unit),超时时间内获取锁返回true,否则返回false
  4. 支持公平和非公平锁,公平指的是获取锁按照请求锁的时间顺序决定,先到先得,非公平则是直接竞争锁,先到不一定先得
  5. 支持Condition

如果你看过阻塞队列的源码,那么你对 Condition 应该挺了解了,我们举个栗子来看看,我们需要实现:

  1. 如果队列满了,那么写入阻塞
  2. 如果队列空了,那么删除(取元素)阻塞
    我们给阻塞队列提供一个 put 写入元素和 take 删除元素的方法。

put 时候加锁且响应中断,如果队列满了,notFull.await 释放锁,进入阻塞状态,反之,则把元素添加到队列中,notEmpty.signal 唤醒阻塞在删除元素的线程。

take 的时候一样加锁且响应中断,如果队列空了,notEmpty.await 进入释放锁,进入阻塞状态,反之,则删除元素,notFull.signal 唤醒阻塞在添加元素的线程。

public class ConditionTest {

    public static void main(String[] args) throws Exception {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(10);
    }

    static class ArrayBlockingQueue<E> {
        private Object[] items;
        int takeIndex;
        int putIndex;
        int count;
        private ReentrantLock lock;
        private Condition notEmpty;
        private Condition notFull;

        public ArrayBlockingQueue(int capacity) {
            this.items = new Object[capacity];
            lock = new ReentrantLock();
            notEmpty = lock.newCondition();
            notFull = lock.newCondition();
        }

        public void put(E e) throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                while (count == items.length) {
                    notFull.await();
                }
                enqueue(e);
            } finally {
                lock.unlock();
            }
        }

        private void enqueue(E x) {
            final Object[] items = this.items;
            items[putIndex] = x;
            if (++putIndex == items.length){
                putIndex = 0;
            }
            count++;
            notEmpty.signal();
        }

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

        private E dequeue() {
            final Object[] items = this.items;
            E x = (E) items[takeIndex];
            items[takeIndex] = null;
            if (++takeIndex == items.length){
                takeIndex = 0;
            }
            count--;
            notFull.signal();
            return x;
        }
    }
}

 

文章转自公众号:艾小仙

分类
标签
已于2022-6-13 17:42:11修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐