读《java并发编程的艺术》心得1


最近在看《Java并发编程的艺术》,看到书里的一些程序片段,发现了一些之前没有仔细思考过的东西,现在记录下来。

wait和notify

wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放),调用wait方法的一个或多个线程就会解除wait状态,重新参与竞争对象锁,程序如果可以再次得到锁,就可以继续向下运行。比如下面这个例子:

package xyz.dzh.ArtJavaConcurrency.chapter04;

import java.sql.Connection;
import java.util.LinkedList;

import lombok.extern.slf4j.Slf4j;

/**
 * 6-16
 */
@Slf4j
public class ConnectionPool {

    private final LinkedList<Connection> pool = new LinkedList<Connection>();

    public ConnectionPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(ConnectionDriver.createConnection());
            }
        }
    }

    public void releaseConnection(Connection connection) throws InterruptedException {
        if (connection != null) {
            synchronized (pool) {
                pool.addLast(connection);
                pool.notifyAll();
                // Thread.sleep(1000);
                log.info("notify之后不会立即释放锁");
            }
        }
    }

    // �ȴ�
    public Connection fetchConnection(long mills) throws InterruptedException {
        synchronized (pool) {
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    // 执行锁所属对象的wait方法,此线程会释放锁,进入线程等待池中等待被唤醒
                    pool.wait();
                }

                return pool.removeFirst();
            } else {
                long future = System.currentTimeMillis() + mills;
                long remaining = mills;
                while (pool.isEmpty() && remaining > 0) {
                    pool.wait(remaining);
                    remaining = future - System.currentTimeMillis();
                }

                Connection result = null;
                if (!pool.isEmpty()) {
                    result = pool.removeFirst();
                }
                return result;
            }
        }
    }
}

注意:如果线程调用了wait方法,此线程会在这里等待,其他线程调用notify之后,该线程参与到锁的竞争中来,如果该线程获取了锁,则会从wait方法后面开始执行

package xyz.dzh.ArtJavaConcurrency.chapter04;

import java.sql.Connection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 6-18
 */
@SuppressWarnings("checkstyle:VisibilityModifier")
public class ConnectionPoolTest {
    static ConnectionPool pool = new ConnectionPool(10);
    // 保证所有的ConnectionPool能够同时开始
    static CountDownLatch start = new CountDownLatch(1);
    // main线程将会等待所有ConnectionRunner结束之后才能继续操作
    static CountDownLatch end;

    @SuppressWarnings("checkstyle:MagicNumber")
    public static void main(String[] args) throws Exception {
        // 线程数量
        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        int count = 20;
        AtomicInteger got = new AtomicInteger();
        AtomicInteger notGot = new AtomicInteger();
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new ConnetionRunner(count, got, notGot), "ConnectionRunnerThread");
            thread.start();
        }
        start.countDown();
        end.await(); // 主线程等待其他线程执行完毕。
        System.out.println("total invoke: " + (threadCount * count));
        System.out.println("got connection:  " + got);
        System.out.println("not got connection " + notGot);
    }

    static class ConnetionRunner implements Runnable {
        int count;
        AtomicInteger got;
        AtomicInteger notGot;

        public ConnetionRunner(int count, AtomicInteger got, AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }

        public void run() {
            try {
                //表示在这个线程里面等待main方法执行完连接池的初始化之后再统一去竞争连接池。main初始化完了之后通过countDown通知这里继续往下执行。
                start.await();
            } catch (Exception ex) {
                //
            }
            while (count > 0) {
                try {
                    // 从线程池中获取连接,如果一秒内获取不到,将会返回null
                    // 分别统计连接获取的数量got和为获取到的数量notGot
                    Connection connection = pool.fetchConnection(1000);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                    }
                } catch (Exception ex) {
                    //
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}

上面这段代码中除了实现了竞争连接池的功能以外,还有用到了CountDownLatch,可以看这篇文章:CountDownLatch

CountDownLatch

CDL通过一个计数器来实现了线程之间的同步。
其中几个主要方法:

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

其中实现的功能和join有类似的地方。

CountDownLatch和join的区别

调用join方法需要等待thread执行完毕才能继续向下执行,而CountDownLatch只需要检查计数器的值为零就可以继续向下执行,相比之下,CountDownLatch更加灵活一些,可以实现一些更加复杂的业务场景。
区别示例

Semaphore

Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

信号量源码分析

队列同步器AbstractQueuedSynchronizer

AbstractQueuedSynchronizer
https://juejin.im/post/5afb9ab3f265da0b736dd1e1
aqs设计的妙处:
1、自旋锁
当我们执行一个有确定结果的操作,同时又需要并发正确执行,通常可以采用自旋锁实现。在AQS中,自旋锁采用 死循环 + CAS 实现。
2、模板方法
在AQS中,模板方法设计模式体现在其acquire()、release()方法上,我们先来看下源码:

 public final void acquire(int arg) {
        // 首先尝试获取共享状态,如果获取成功,则tryAcquire()返回true
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

复制代码其中调用tryAcquire()方法的默认实现是抛出一个异常,也就是说tryAcquire()方法留给子类去实现,acquire()方法定义了一个模板,一套处理逻辑,相关具体执行方法留给子类去实现。

自己做一个锁

https://zhuanlan.zhihu.com/p/32883297 这个写的也很好

package xyz.dzh.ArtJavaConcurrency.chapter05;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 10-2
 */
public class Mutex implements Lock {
    // 静态内部类,自定义同步器
    // ReentrantLock里面也有类似的一个Sync
    private static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -4387327721959839431L;

        // 是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 当状态为0的时候获取锁
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            // 如果经过cas设置成功,则表示获取了同步状态。
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 释放锁,将状态职位0
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // 返回一个condition每一个condition包含一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    // 仅需要将操作代理到Sync即可
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

用一个经典的对一个数字累加的程序测试一下这个锁。

package xyz.dzh.ArtJavaConcurrency.chapter05;

/**
 * @author dongzhonghua <dongzhonghua03@kuaishou.com>
 * Created on 2020-04-18
 */
@SuppressWarnings("checkstyle:MagicNumber")

public class MutexTest {
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        Mutex lock = new Mutex();
        Runnable task = new Runnable() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    try {
                        lock.lock();
                        num++;
                    } finally {
                        lock.unlock();
                    }
                }
            }
        };
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(num);
    }
}

可重入锁

上边这个锁是不可重入锁,因为我们每次只把state更新为1。如果要支持可重入也很简单,获取锁时检测锁是不是被当前线程占有着,如果是就把state的值加1,释放锁时每次减1即可,减为0时表示锁已释放。

Condition

条件变量Condition
条件变量很大一个程度上是为了解决Object.wait/notify/notifyAll难以使用的问题。
Condition

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值