Java JUC

一、 volatile关键字

作用:当多个线程操作共享数据时,可以保证内存中的数据是可见的。相较于synchronized是一种比较轻量级的同步策略。
注意:
1、volatile不具备“互斥性”
2、valatile不能保证变量的“原子性”

问题引入:看下面这段代码

public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true){
            if (td.isFlag()){
                System.out.println("------------------");
                break;
            }
        }
    }
}

class ThreadDemo implements Runnable{
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag="+flag);
    }
}

运行结果:
在这里插入图片描述

内存可见性问题是,当多个线程操作共享数据时,彼此不可见

二、原子变量与CAS算法

i++的原子性问题:i++的操作实际上分为三个步骤“读、改、写”

原子变量:jdk1.5后java.util.concurrent.atomic包下提供了常用的原子变量:
1、volatile保证内存可见性
2、CAS(compare and swap)算法保证数据的原子性
CAS算法是硬件对于并发操作共享数据的支持。
CAS包含三个操作数:
内存值V、预估值(旧值)A、更新值B,当前仅当V == A 时,V=B,否则,将不做任何操作。

三、同步容器

ConcurrentHashMap:
采用“锁分段”机制

四、Lock接口

4.1 复习Synchrinized

多线程编程模板上

1、先写资源类
2、再写操作(操作是资源类里面的)
3、再写线程,调用资源类里面的方法
(线程 操作 资源类高内聚低耦合)

4.2 Lock

是什么

Lock接口的实现

ReentrantLock()

创建线程方式

public class SaleTicket {
    public static void main(String[] args) {
        final Ticket t = new Ticket();
        // lambda表达式
        new Thread(()->{for (int i=0;i<20;i++){t.sale();}},"AA").start();
        new Thread(()->{for (int i=0;i<20;i++){t.sale();}},"BB").start();
        new Thread(()->{for (int i=0;i<20;i++){t.sale();}},"CC").start();
//        new Thread(new Runnable() {
//            public void run() {
//                for (int i=0;i<20;i++){
//                    t.sale();
//                }
//            }
//        },"窗口1:").start();
//        new Thread(new Runnable() {
//            public void run() {
//                for (int i=0;i<20;i++){
//                    t.sale();
//                }
//            }
//        },"窗口2:").start();
//        new Thread(new Runnable() {
//            public void run() {
//                for (int i=0;i<20;i++){
//                    t.sale();
//                }
//            }
//        },"窗口3:").start();
    }
}

/**
 * 资源类
 */
class Ticket{
    Ticket(){}
    private int number = 10; // 剩余票数
    private Lock lock = new ReentrantLock();
    /**
     * 操作
     */
    public void sale(){
            try {
                lock.lock();
                if(number>0){
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+ "卖票:"+number);
                    number--;
                }else {
                    System.out.println("卖完了");;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
    }
}

五、java8特性

lambda表达式

什么是lambda表达式:左边小括号里写参数,右边写具体实现
写法:拷贝小括号(),写死右箭头->,落地大括号{…}
要求:接口只有一个方法时,方法名可省略
函数式接口:@FunctionalInterface:接口内只允许写一个方法

六、线程间通信

面试题

两个线程,一个线程打印1-52,另一个打印字母A-Z,打印顺序为12A34B…5152Z。要求用线程间通信

/**
 * 两个线程,一个线程打印1-52,另一个打印字母A-Z,打印顺序为12A34B…5152Z。要求用线程间通信
 */
public class T01 {
    public static void main(String[] args) {
        ShareDataOne shareDataOne = new ShareDataOne();
        new Thread(()->{
            try {
                shareDataOne.printNumber();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                shareDataOne.printWord();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
class ShareDataOne{
    public synchronized void printNumber() throws InterruptedException {
        for(int i=1;i<=52;i++){
            System.out.println(i);
            while(i % 2 == 0){
                notify();
                wait();
            }
        }
    }

    public synchronized void printWord() throws InterruptedException{
            for(char i = 'A';i<='Z';i++){
                System.out.println(i);
                notify();
                wait();
            }
    }
}

下面我们用Lock接口,对标实现
在这里插入图片描述

public class T02 {
    public static void main(String[] args) {
        ShareDataOne2 shareDataOne2 = new ShareDataOne2();
        new Thread(()->{shareDataOne2.printNumber();}).start();
        new Thread(()->{shareDataOne2.printWord();}).start();
    }
}

class ShareDataOne2{
    final Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void printNumber(){
        lock.lock();
        try {
            for(int i=1;i<=52;i++){
                System.out.print(i);
                if(i % 2 == 0){
                    condition.signalAll();
                    condition.await();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printWord(){
        lock.lock();
        try {
            for (char i = 'A';i<='Z';i++){
                System.out.print(i);
                condition.signalAll();
                condition.await();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

多线程编程模板中

1、判断
2、干活
3、通知

多线程编程模板下

注意多线程之间的虚假唤醒

线程间定制化通讯

题目:
多线程之间按顺序调用,实现A->B->C
三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次;接着
AA打印5次,BB打印10次,CC打印15次;
…来10轮
实现思路:
1、有顺序的通知,需要有标识位!!!
2、有一把锁Lock,3把钥匙Condition
3、判断标志位
4、输出线程名+第几轮+第几次
5、修改标志位,通知下一个

/**
 * 多线程之间按顺序调用,实现A->B->C
 * 三个线程启动,要求如下:
 * AA打印5次,BB打印10次,CC打印15次;接着
 * AA打印5次,BB打印10次,CC打印15次;
 * ......来10轮
 */
public class T03 {
    public static void main(String[] args) {
        ShareDataOne3 shareDataOne3 = new ShareDataOne3();
        new Thread(()->{
            for (int i=1;i<=10;i++){
                shareDataOne3.print5(i);
            }
        },"AA").start();
        new Thread(()->{
            for (int i=1;i<=10;i++){
                shareDataOne3.print10(i);
            }
        },"BB").start();
        new Thread(()->{
            for (int i=1;i<=10;i++){
                shareDataOne3.print15(i);
            }
        },"CC").start();
    }
}
class ShareDataOne3{
    private Lock lock = new ReentrantLock();
    private int num = 1;    //1:AA 2:BB 3:CC
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    private Condition conditionC = lock.newCondition();

    public void print5(int j){
        lock.lock();
        try{
            // 判断
            while (num != 1){
                conditionA.await();
            }
            // 干活
            for(int i=1;i<=5;i++){
                System.out.println("轮次:"+j+";"+Thread.currentThread().getName()+":"+i);
            }
            // 通知
            num = 2;
            conditionB.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print10(int j){
        lock.lock();
        try{
            // 判断
            while (num != 2){
                conditionB.await();
            }
            // 干活
            for(int i=1;i<=10;i++){
                System.out.println("轮次:"+j+";"+Thread.currentThread().getName()+":"+i);
            }
            // 通知
            num = 3;
            conditionC.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print15(int j){
        lock.lock();
        try{
            // 判断
            while (num != 3){
                conditionC.await();
            }
            // 干活
            for(int i=1;i<=15;i++){
                System.out.println("轮次:"+j+";"+Thread.currentThread().getName()+":"+i);
            }
            // 通知
            num = 1;
            conditionA.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

我们按这个思路,来实现下刚才的面试题,代码如下:

/**
 * 两个线程,一个线程打印1-52,另一个打印字母A-Z,打印顺序为12A34B…5152Z。要求用线程间通信
 */
public class T04 {
    public static void main(String[] args) {
        ShareDataOne4 shareDataOne4 = new ShareDataOne4();
        new Thread(()->{shareDataOne4.printNumber();}).start();
        new Thread(()->{shareDataOne4.printWord();}).start();
    }
}
class ShareDataOne4{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private int flag = 1; // 1:打印数字 2:打印字母

    public void printNumber(){
        lock.lock();
        try {
            // 判断
            while ( flag != 1){
                condition1.await();
            }
            // 干活
            for(int i=1;i<=52;i++){
                System.out.print(i);
                if(i % 2 == 0){
                    // 通知
                    flag = 2;
                    condition2.signal();
                    condition1.await();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printWord(){
        lock.lock();
        try {
            // 判断
            while ( flag != 2){
                condition2.await();
            }
            // 干活
            for(char w = 'A';w<='Z';w++){
                System.out.print(w);
                // 通知
                flag = 1;
                condition1.signal();
                condition2.await();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

七、NoSafeDemo

7.1 NoSafeList

7.1.1 举例说明ArrayList不安全

证明方式一:看源码,add方法中没有加synchronized关键字
证明方式二:代码模拟多线程读写过程

public class T05 {
    public static void main(String[] args) {
        List<String> array = new ArrayList<>();
                // new CopyOnWriteArrayList();
                //new ArrayList<>();
        for(int i=0;i<20;i++){
            new Thread(()->{
                array.add(UUID.randomUUID().toString());
                System.out.println(array);
            }).start();
        }
    }
}

运行结果:
在这里插入图片描述

7.1.2 解决方案(CopyOnWriteArrayList)

1、Vector效率低
2、Collections效率低
3、用juc的CopyOnWriteArrayList;写时拷贝技术。
CopyOnWriteArrayList是arrayList的一种线程安全变体,其中所有可变操作(add,set等)都是通过生成底层数组的新副本来实现的。
原理:
我们都知道,集合框架中的ArrayList是非线程安全的,Vector虽是线程安全的,但由于简单粗暴的锁同步机制,性能较差。而CopyOnWriteArrayList则提供了另一种不同的并发处理策略(当然是针对特定的并发场景)。

很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
在这里插入图片描述
源码:
下面看看CopyOnWriteArrayList的add(E e)方法源码

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

7.2 NoSafeMap

7.2.1 举例说明HashMap不安全

public class T05 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap();
        for(int i=0;i<20;i++){
            new Thread(()->{
                map.put(UUID.randomUUID().toString(),Thread.currentThread().getName());
                System.out.println(map);
            }).start();
        }
    }
}

运行结果:
在这里插入图片描述

7.2.2 解决方案(ConcurrentHashMap)

8、八锁问题

1、标准访问,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);
        
        new Thread(()->{
            try {
                phone.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public synchronized void sendSMS(){
        System.out.println("---sendSMS---");
    }
    public synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
}

运行结果:
在这里插入图片描述
分析: 因为这里都是phone这个对象去调用的sendSMS和sendEmail方法,所以同步监视器都是当前实例对象this,也就是phone这个对像。由于调用线程AA之后,主线程sleep了0.1秒,所以肯定是线程AA先拿到锁,执行完sendSMS方法之后,才会释放锁让线程BB拿到锁去执行sendEmail方法。所以先打印sendSMS后打印sendEmail。

2、停4秒在短信方法内,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
}

运行结果:
在这里插入图片描述
分析: 分析同1,因为线程AA先拿到锁

3、新增普通的hello方法,是先打印短信还是hello

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
//                phone.sendEmail();
                phone.getHello();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
    public void getHello(){
        System.out.println("---Hello---");
    }
}

运行结果:
在这里插入图片描述
分析:
因为hello是普通方法,没有锁,所以即使线程AA先拿到锁了,也不影响线程BB执行getHello方法。虽然线程BB晚执行getHello方法0.1秒,但是打印sendSMS前sleep了4秒,所以先打印Hello,后打印sendSMS。

4、现在有两部手机,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone2.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
    public void getHello(){
        System.out.println("---Hello---");
    }
}

运行结果:
在这里插入图片描述
分析:
因为sendSMS和sendEmail是被两个不同的Phone对象调用的,所以这两个方法是上个两个不同的锁,所以两个线程分别执行这两个方法不需要抢锁。因为打印sendSMS之前需要sleep 4秒,所以虽然sendEmail方法比sendSMS方法晚执行0.1秒,但显然也是先打印sendEmail后打印sendSMS

5、两个静态同步方法,1部手机,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
//        Phone phone2 = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public static synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public static synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
}

运行结果:
在这里插入图片描述
分析:
static方法,是类方法,所以其同步监视器是这个类.class这个对象,即Phone.class这个对象,所以这里只有一把锁,因为线程BB晚执行0.1秒,所以线程AA先拿到锁,先打印sendSMS,线程BB后拿到锁,后打印sendEmail。

6、两个静态同步方法,2部手机,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone2.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public static synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public static synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
}

结果:
在这里插入图片描述

分析:
static方法,是类方法,同步监视器是类.class,即Phone.class,所以phone和phone2是同一把锁,因为phone先拿到锁,phone2后拿到锁,所以先打印sendSMS,后打印sendEmail。
7、1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
//        Phone phone2 = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public static synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
}

结果:
在这里插入图片描述

分析:
不是同一把锁
8、1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            try {
                phone2.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();
    }
}
class Phone{
    public static synchronized void sendSMS() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }
    public synchronized void sendEmail(){
        System.out.println("---sendEmail---");
    }
}

结果:
在这里插入图片描述
分析:
不是同一把锁

获得多线程的方法有几种?
传统的是继承Thread类和实现Runnable接口;java5以后又有实现callable接口和java的线程池获得

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值