JUC并发编程深入浅出!

一、JUC

java.util.concurrent

java.util.concurrent.atomic

java.util.concurrent.locks
在这里插入图片描述

二、进程与线程

进程: 一个程序,qq.exe Music.exe程序的集合

一个进程往往可以包含多个线程,至少包含一个

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。

Java默认有几个线程? 2个:main、GC

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

Java无法开启线程:调用本地方法:底层C++,Java无法直接操作硬件 -----Unsafe

并发、并行

并发:多线程操作同一个资源

  • CPU单核,模拟出多条线程。---->快速交替

并行:多个人一起行走

  • CPU多核,多个线程可以同时执行。
public class test1 {
    public static void main(String[] args) {
        // 获得CPU核数
        System.out.println(Runtime.getRuntime().availableProcessors());
        
    }
}

并发编程的本质:充分利用CPU的资源

线程的状态----6

NEW -新生

RUNNABLE -运行

BLOCKED -阻塞

WAITING -等待

TIMED_WAITING -超时等待

TERMINATED -终止

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

wait、sleep

  1. 来自不同的类
    1. sleep:Thread
    2. wait:Object
  2. 锁的释放
    1. wait会释放锁
    2. sleep抱着锁睡、不会释放
  3. 使用范围不同
    1. wait必须在同步代码块中
    2. sleep可在任何地方睡
  4. 捕获异常
    1. wait不需要捕获异常
    2. sleep必须捕获异常----InterruptedException

三、Lock锁

1、传统Synchronized

public class SaleTicketDemo01 {
    // 真正的多线程开发、公司的开发
    // 线程就是一个单独的资源了,没有任何附属的操作
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"C").start();
    }

}

// 资源类 OOP
class Ticket {
    // 属性、方法
    private int num = 60;

    // 卖票方式
    public synchronized void sale() {
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了" + num-- + "票");
            System.out.println("剩余:" + num);
        }
    }
}

2、Lock接口

ReentrantLock 可重入锁

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

ReentrantReadWriterLock.ReadLock

ReentrantReadWriterLock.WriteLock

在这里插入图片描述

公平锁: 先来后到

非公平锁:可插队(默认)

package com.erran.demo01;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SaleTicketDemo02 {
    // 真正的多线程开发、公司的开发
    // 线程就是一个单独的资源了,没有任何附属的操作
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();

        new Thread(()->{ for (int i = 0; i < 30; i++) {ticket.sale();}},"A").start();
        new Thread(()->{ for (int i = 0; i < 30; i++) {ticket.sale();}},"B").start();
        new Thread(()->{ for (int i = 0; i < 30; i++) {ticket.sale();}},"C").start();
    }

}

// 资源类 OOP
class Ticket2 {
    // 属性、方法
    private int num = 30;

    // 1、new ReentrantLock()
    // 2、lock.lock();加锁
    // 3、finally->lock.unlock(); 解锁

    Lock lock = new ReentrantLock();

    // 卖票方式
    public synchronized void sale() {

        lock.lock();
        try {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了" + num-- + "票");
                System.out.println("剩余:" + num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

3、Synchronized与Lock区别

  • Synchronized是内置的Java关键字,Lock是一个Java类

  • Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁

  • Synchronized会自动释放锁,Lock必须手动释放锁否则会死锁

  • Synchronized 线程1(获得锁、阻塞)、线程2(等待、等待…) Lock不一定等待下去

    • lock.tryLock();
      
  • Synchronized 可重入锁,不可中断、非公平;Lock 可重入锁,可判断锁、非公平(可自己设置)

  • Synchronized适合锁少量的代码同步问题; Lock适合锁大量的同步代码

四、生产者和消费者问题

1、Synchronized版

package com.erran.pc;

/*
* 线程之间的通信问题: 生产者和消费者问题!     等待唤醒、通知唤醒
* 线程交替执行    A B 操作同一个变量 num=0
* A num+1
* B num-1
* */

public class A {
    public static void main(String[] args) {

        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

class Data {
    private int num = 0;
    public synchronized void increment() throws InterruptedException {
        if (num != 0) {
            // 等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        // 通知其他线程,+1完毕
        this.notifyAll();

    }
    public synchronized void decrement() throws InterruptedException {
        if (num == 0) {
            // 等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        // 通知其他线程,-1完毕
        this.notifyAll();
    }
}

存在的问题:多于两个线程时出现虚假唤醒 ----> if 改成while

package com.erran.pc;

/*
* 线程之间的通信问题: 生产者和消费者问题!     等待唤醒、通知唤醒
* 线程交替执行    A B 操作同一个变量 num=0
* A num+1
* B num-1
* */

public class A {
    public static void main(String[] args) {

        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Data {
    private int num = 0;
    public synchronized void increment() throws InterruptedException {
        while (num != 0) {
            // 等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        // 通知其他线程,+1完毕
        this.notifyAll();

    }
    public synchronized void decrement() throws InterruptedException {
        while (num == 0) {
            // 等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        // 通知其他线程,-1完毕
        this.notifyAll();
    }
}

2、JUC版

  1. Synchronized
    1. wait
    2. notif
  2. Lock(Lock)
    1. await(Condition)
    2. signal
package com.erran.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class B {
    public static void main(String[] args) {

        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Data2 {
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public synchronized void increment() throws InterruptedException {

        lock.lock();
        try {
            while (num != 0) {
                // 等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程,+1完毕
            // 唤醒全部
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();

        }
    }

    public synchronized void decrement() throws InterruptedException {

        lock.lock();
        try {
            while (num == 0) {
                // 等待
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程,+1完毕
            // 唤醒全部
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();

        }
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOMXyetX-1616568189423)(JUC%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B.assets/image-20210317112325062.png)]

并非按照ABCD的顺序进行

Condition 精准地通知和唤醒线程

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术

package com.erran.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printB();
        }},"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printC();
        }},"C").start();
    }
}

class Data3 {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    // 标志: 1->A 2->B 3->C
    private int num = 1;

    public void printA() {
        lock.lock();
        try {
            while (num != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAA");
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            while (num != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBB");
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            while (num != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCC");
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

五、八锁现象

package com.erran.lock8;

import java.util.concurrent.TimeUnit;

/*
* 8锁,关于锁的八个问题
* 1.标准情况下,两个线程先打印 发短信or打电话        先sms后call
* 2.sendSms延迟4秒  两个线程先打印 发短信or打电话   先sms后call
* */



public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{phone.sendSms();},"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{phone.call();},"B").start();
    }
}

class Phone {

    // Synchronized 锁的对象的方法的调用者
    // 两个方法用的是用一个锁,谁先拿到谁先执行

    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call() {
        System.out.println("打电话");
    }

}
package com.erran.lock8;

import java.util.concurrent.TimeUnit;

/*
* 3. 增加了一个普通方法后 先发短信还是hello   先hello后sms
* 4. 两个对象,两个同步方法  先发短信还是打电话  先call后sms
* */

public class test2 {
    public static void main(String[] args) {
        // 两个对象
        Phone2 phone = new Phone2();
        Phone2 phone2 = new Phone2();

        new Thread(()->{phone.sendSms();},"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{phone2.call();},"B").start();
    }
}

class Phone2 {

    // Synchronized 锁的对象是 ***方法的调用者*** -->对象!
    // 两个方法用的是用一个锁,谁先拿到谁先执行

    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call() {
        System.out.println("打电话");
    }

    // 这里没有锁,不是同步方法,不受锁的影响
    public void hello() {
        System.out.println("hello");
    }
}
package com.erran.lock8;

import java.util.concurrent.TimeUnit;

/*
* 5. 增加两个静态的同步方法,只有一个对象 先发短信还是打电话  先sms后call
* 6. 两个对象  两个静态的同步方法        先发短信还是打电话  先sms后call
* */

public class test3 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,   static锁的是Class
        Phone3 phone = new Phone3();
        Phone3 phone2 = new Phone3();

        new Thread(()->{phone.sendSms();},"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{phone2.call();},"B").start();
    }
}


class Phone3 {

    // Synchronized 锁的对象是 ***方法的调用者*** -->对象!
    // 两个方法用的是用一个锁,谁先拿到谁先执行
    // static 静态方法, 类一加载就有了 Class模板
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call() {
        System.out.println("打电话");
    }

}
package com.erran.lock8;

import java.util.concurrent.TimeUnit;

/*
* 7. 1个静态同步方法、1个普通同步方法 一个对象 先发短信还是打电话 先call后sms
* 8. 1个静态同步方法、1个普通同步方法 两个对象 先发短信还是打电话 先call后sms
* */

public class test4 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,   static锁的是Class
        Phone4 phone = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{phone.sendSms();},"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{phone2.call();},"B").start();
    }
}
class Phone4 {

    // 静态的同步方法 锁的是Class模板
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    // 普通的同步方法 锁的是 方法的调用者
    public synchronized void call() {
        System.out.println("打电话");
    }

}

小结:

new this 具体的一个手机

static Class 唯一的一个模板

类是对象的模板,对象是类的实例

六、集合类不安全–并发容器

1、List 不安全

package com.erran.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

// java.util.ConcurrentModificationException 并发修改异常
public class listTest {
    public static void main(String[] args) {
//         并发下ArrayList不安全的
//        List<String> list = new ArrayList<>();

        /*
        * 解决方案:
        * 1. List<String> list = new Vector<>();
        * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
        * 3. List<String> list = new CopyOnWriteArrayList<>();
        * */
        // CopyOnWrite 写入时复制 cow 计算机程序设计领域的一种优化策略
        // 在写入的时候避免覆盖、造成数据问题
        
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }).start();

        }
    }
}

所谓CopyOnWrite 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。

2、set 不安全

package com.erran.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

// 同理ConcurrentModificationException
public class setTest {
    public static void main(String[] args) {
//        HashSet<String> set = new HashSet<>();

        // 解决方案
        Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
        Set<String> set2 = new CopyOnWriteArraySet<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                set1.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set1);
            }).start();
        }
    }
}

HashSet的底层:

public HashSet() {
    map = new HashMap<>();
}
// add源码  set本质就是map  key是无法重复的
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

3、HashMap 不安全

在这里插入图片描述

package com.erran.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class maptest {
    public static void main(String[] args) {

        Map<String, String> map = new HashMap<>();
//        Map<String, String> map = new HashMap<>(16,0.75);
        // 加载因子、初始化容量

        Map<String, String> map2 = new ConcurrentHashMap<>();

        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                map2.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map2);
            }).start();
        }

    }
}
4、ConcurrentLinkedQueue、BlockingQueue、ConcurrentSkipListMap

七、常用的辅助类

1、CountDownLatch

减法计数器

package com.erran.add;

import java.util.concurrent.CountDownLatch;

public class CountdownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        // 总数为6
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                // 数量-1
                System.out.println(Thread.currentThread().getName() + " go out !");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }

        // 等待计数器归零,然后再向下执行
        countDownLatch.await();

        System.out.println("Close the door");
    }
}

原理:

  • countDownLatch.countDown(); 数量-1
  • countDownLatch.await(); 等待计数器归零,然后再向下执行

每次有现车调用countDown()时 数量-1,当计数器为0的时候,countDownLatch.await()就会被唤醒,继续执行

2、CyclicBarrier

加法计数器

package com.erran.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /*
        * 集齐七颗龙珠召唤神龙
        * */

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!!");
        });

        for (int i = 1; i <= 7; i++) {
            // lambda 拿不到外面的i
            final int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3、Semaphore

semaphore:信号量

抢车位

package com.erran.add;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量----停车位
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                // acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    // release() 释放
                    semaphore.release();
                    
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:

  • acquire() 获得,若已满则等待至被释放为止
  • release() 释放,会将按当前的信号量释放+1,然后唤醒等待的线程

作用:多个共享资源互斥的使用! 并发限流,控制最大的线程数!!

八、读写锁

package com.erran.rw;

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

/*
*
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
*
* ReadWriteLock
* 读-读   可以共存
* 读-写   不可共存
* 写-写   不可共存
* */

public class ReadWriteLockDemo {
    public static void main(String[] args) {

        MyCacheLock myCache = new MyCacheLock();

        // 写入
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }

        // 读取
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

/*
* 自定义缓存
*/

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    // 存,写
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完毕");
    }

    // 取,读
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完毕");
    }
}

class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();
    // 读写锁 更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();




    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();

        }

    }

    // 取,读
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();

        }

    }
}

九、双重校验锁实现对象单例(线程安全)

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

十五、JMM

请你谈谈对volatile的理解

Volatile是java虚拟机提供轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM

JMM Java内存模型,不存在的东西–概念、约定

关于Jmm的一些同步的约定:

  1. 线程解锁前,必须把共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存中最新值到工作内存中
  3. 加锁和解锁是同一把锁

十六、Volatile

1、保证可见性

线程之间的通信可见,保证了主存中flag变量变化时其他线程能第一时间发现,从而做出响应

2、不保证原子性

原子性:不可分割

线程A在执行任务时,不能被打扰、不能被分割。要么都成功、要么都失败

3、禁止指令重排

指令重排:所写的程序, 计算机并不是按照所写的那样去执行

源代码---->编译器优化的重排---->指令并行也可能会重排---->内存系统也会重排---->执行

int x = 1;	// 1
int y = 2;	// 2
x = x + 5;	// 3
y = x * x;	// 4
可能出现的结果: 1234 2134 1324

可能造成影响的结果:

线程A线程B
x = ay = b
b = 1a = 2

正常结果: x =0;y =0;

但是可能由于指令重排,导致结果: x =2;y =1

线程A线程B
b = 1a = 2
x = ay = b

Volatile避免指令重排:内存屏障。 ----> 单例模式 DCL懒汉式

  1. 保证特定的操作的执行顺序
  2. 保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

十七、CAS

底层AtomicInteger调用Unsafe

    // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
package com.erran.CAS;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomic = new AtomicInteger(2020);

        // 期望、更新
        // public final boolean compareAndSet(int expect,int update)
        // 若期望的值达到了,则更新  否则不更新   CAS是CPU的并发原语
        atomic.compareAndSet(2020, 2021);
        System.out.println(atomic.compareAndSet(2020, 2021));
        System.out.println(atomic.get());

        atomic.getAndIncrement();
        

    }
}

getAndIncrement()方法调用了unsafe.getAndAddInt(this, valueOffset, 1),getAndAddInt()方法则是一个自旋锁

在这里插入图片描述

CAS:比较当前工作内存中的值和主内存中的值,若这个值是期望的则执行操作,否则就一直循环

缺点:

  • 循环(自旋)耗时
  • 一次性只能保证一个共享变量的原子性
  • ABA问题

CAS: ABA问题(狸猫换太子)<----原子引用

带版本号的原子操作

对应思想:乐观锁

十八、锁的理解

1、公平锁、非公平锁

  • 公平锁: 公平,不能插队,先来后到
  • 非公平锁: 不公平,可插队(默认)

2、可重入锁

递归锁,见 三、2

3、自旋锁

spinlock

在这里插入图片描述

十九、AQS原理

在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于 AQS 原理的理解”。下面给大家一个示例供大家参考,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

AQS 定义两种资源共享方式

  • Exclusive(独占)
    • 只有一个线程能执行,如 ReentrantLock。
  • Share(共享)
    • 多个线程可同时执行,如 Semaphore/CountDownLatch。

+三辅助类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值