juc并发编程学习笔记下(尚硅谷)

Posted 今夜月色很美

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了juc并发编程学习笔记下(尚硅谷)相关的知识,希望对你有一定的参考价值。

5 多线程锁

5.1锁的八个问题演示

class Phone { 
    public static synchronized void sendSMS() throws Exception { 
        //停留4秒 
        TimeUnit.SECONDS.sleep(4); 
        System.out.println("------sendSMS"); 
    } 
    
    public synchronized void sendEmail() throws Exception { 
        System.out.println("------sendEmail"); 
    } 
        public void getHello() { 
        System.out.println("------getHello"); 
    }
}
1 标准访问,先打印短信还是邮件 
------sendSMS 
------sendEmail 
2 停4秒在短信方法内,先打印短信还是邮件 
------sendSMS 
------sendEmail 
3 新增普通的hello方法,是先打短信还是hello 
------getHello 
------sendSMS 
4 现在有两部手机,先打印短信还是邮件 
------sendEmail 
------sendSMS 
5 两个静态同步方法,1部手机,先打印短信还是邮件 
------sendSMS 
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件 
------sendSMS 
------sendEmail 
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件 
------sendEmail 
------sendSMS 
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件 
------sendEmail 
------sendSMS

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象

5.2公平锁和非公平锁

ReentrantLock构造方法根据传入参数创建公平锁或非公平锁,默认为非公平锁。非公平锁可能会有线程饿死,效率高;公平锁阳光普照,效率相对低。

NonfairSync源码:

	static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

FairSync源码:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

5.3可重入锁(递归锁)

synchronized和lock都是可重入锁

5.4死锁

查看进程jps、jstack

deadlock测试代码:

public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        new Thread(() -> {
            synchronized (t1){
                System.out.println("拿到锁t1,准备获取锁t2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (t2){
                    System.out.println("获取锁t2");
                }
            }
        }, "aa").start();

        new Thread(() -> {
            synchronized (t2){
                System.out.println("拿到锁t2,准备获取锁t1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (t1){
                    System.out.println("获取锁t1");
                }
            }
        }, "bb").start();
    }

6、callable接口

使用callable接口创建线程

public static void main(String[] args) throws Exception {
    FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
        System.out.println(Thread.currentThread().getName() + "==callable线程执行。。。");
        return 1111;
    });
    new Thread(futureTask, "AA").start();
    System.out.println(futureTask.get());
}

7、JUC 三大辅助类

7.1CountDownLatch

CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。

  • CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
  • 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
  • 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

测试代码:

public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(6);
    for (int i = 0; i < 6; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "同学离开了教室。");
            countDownLatch.countDown();
        }, String.valueOf(i)).start();
    }
    countDownLatch.await();
    System.out.println(Thread.currentThread().getName() + "班长锁门了。");
}

7.2循环栅栏CyclicBarrier

CyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作

测试代码:

	private final static int num = 7;

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
            System.out.println("七颗龙珠已收集,可以召唤神龙");
        });
        for (int i = 1; i <= num; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "颗龙珠已收集");
                try {
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }

7.3信号量Semaphore

	public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到了停车位");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(6));
                    System.out.println(Thread.currentThread().getName() + "离开了停车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }

8、读写锁

8.1读写锁介绍

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。

  1. 线程进入读锁的前提条件:
    • 没有其他线程的写锁
    • 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
  2. 线程进入写锁的前提条件:
    • 没有其他线程的读锁
    • 没有其他线程的写锁
    而读写锁有以下三个重要的特性:
    (1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
    (2)重进入:读锁和写锁都支持线程重进入。
    (3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

8.2读写锁案例实现

资源类

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author lyz
 * @Title: CustomCache
 * @Description:
 * @date 2021/10/8 15:03
 */
public class CustomCache {

    private volatile Map<String, String> cacheMap = new HashMap<>();

    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key, String value){
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "正在写入数据。。" + key);
            TimeUnit.SECONDS.sleep(1);
            cacheMap.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    public String get(String key){
        rwLock.readLock().lock();
        String value = null;
        try {
            System.out.println(Thread.currentThread().getName() + "正在读数据。。" + key);
            TimeUnit.SECONDS.sleep(1);
            value = cacheMap.get(key);
            System.out.println(Thread.currentThread().getName() + "读完了key=" + key + ",value=" + value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
        return value;
    }
}

测试代码

public static void main(String[] args) {
        CustomCache cache = new CustomCache();
        for (int i = 0; i < 5; i++) {
            final int num = i;
            new Thread(() -> {
                cache.put(String.valueOf(num), String.valueOf(num));
            }, String.valueOf(i)).start();
        }

        for (int i = 0; i < 5; i++) {
            final int num = i;
            new Thread(() -> {
                cache.get(String.valueOf(num));
            }, String.valueOf(i)).start();
        }
    }

8.3锁降级演示demo

	public static void main(String[] args) {
        ReadWriteLock rwLock = new ReentrantReadWriteLock();
        rwLock.writeLock().lock();
        System.out.println("写入数据");
        rwLock.readLock().lock();
        System.out.println("读取数据");
        rwLock.readLock().unlock();
        rwLock.writeLock().unlock();
    }

9、阻塞队列

常用的队列主要有以下两种:
• 先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性
• 后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件(栈)
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

9.1BlockingQueue核心方法


drainTo(): 一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

9.2测试代码

public static void main(String[] args) throws Exception {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        //第一组 抛出异常
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
//        System.out.println(blockingQueue.add("d"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
//        System.out.println(blockingQueue.remove());
        System.out.println("======================================================");

        //第二组 特殊值
        System.out.println(blockingQueue.offer("1"));
        System.out.println(blockingQueue.offer("2"));
        System.out.println(blockingQueue.offer("3"));
        System.out.println(blockingQueue.offer("4"));
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println("======================================================");

        //第三组 阻塞
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
//        blockingQueue.put("d");
        System.out.println(blockingQueue.take());
        以上是关于juc并发编程学习笔记下(尚硅谷)的主要内容,如果未能解决你的问题,请参考以下文章

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类

尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类

尚硅谷JUC高并发编程学习笔记Callable,FutureTask,JUC辅助类

尚硅谷JUC高并发编程学习笔记