尚硅谷JUC高并发编程学习笔记(2)线程通信与集合线程安全

一、线程间通信

线程间通信的模型有两种:共享内存和消息传递
线程间的通信具体步骤:(涉及上中下部)

1、创建资源类,在资源类中船舰属性和操作方法
2、在资源类操作方法:判断、操作、通知
3、创建多个线程,调用资源类的操作方法
4、防止虚拟唤醒问题

1、synchronized案例

操作线程的时候,等待线程使用wait()
通知另外的线程操作用notify()、notifyAll()
假设有两个线程,该线程在执行过程中,判断值(不是该值等待,让其他线程抢),操作值,通知另外一个线程的调度。

通过使用两个线程对0这个值操作,一个线程加1,一个线程减1,交替实现多次

//第一步 创建资源类,定义属性和操作方法
class Share {
    //初始值
    private int number = 0;
    //+1的方法
    public synchronized void incr() throws InterruptedException {
        //第二步 判断 干活 通知
       if(number != 0) { //判断number值是否是0,如果不是0,等待
            this.wait(); //在哪里睡,就在哪里醒
        }
        //如果number值是0,就+1操作
        number++;
        System.out.println(Thread.currentThread().getName()+" :: "+number);
        //通知其他线程
        this.notifyAll();
    }

    //-1的方法
    public synchronized void decr() throws InterruptedException {
        //判断
        if(number != 1) {
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName()+" :: "+number);
        //通知其他线程
        this.notifyAll();
    }
}

public class ThreadDemo1 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        //创建线程
        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.incr(); //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
    }
}

效果

AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0

Process finished with exit code 0

如果使用多个线程,添加额外两个线程,且操作要依次执行

new Thread(()->{
    for (int i = 1; i <=10; i++) {
        try {
            share.incr(); //+1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
},"CC").start();

new Thread(()->{
    for (int i = 1; i <=10; i++) {
        try {
            share.decr(); //-1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
},"DD").start();

就会出现这样的情况

AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
CC :: 1
AA :: 2
CC :: 3
BB :: 2
CC :: 3
AA :: 4
CC :: 5
DD :: 4
BB :: 3
DD :: 2
CC :: 3
AA :: 4
CC :: 5
DD :: 4
BB :: 3
DD :: 2
CC :: 3
AA :: 4
CC :: 5
DD :: 4
BB :: 3
DD :: 2
CC :: 3
AA :: 4
CC :: 5
DD :: 4
BB :: 3
DD :: 2
AA :: 3
DD :: 2
BB :: 1
DD :: 0

Process finished with exit code 0

主要是虚拟唤醒导致:如果一个线程执行完毕后,通知其他线程,该线程又进入等待睡眠,可能会因为某些原因被唤醒后,if结构的语句就不会判断了,一直往下执行,所以需要将if换成while结构,每次都判断。因为wait在哪里睡眠就在哪里被唤醒,结果被某个异常唤醒了后回不去了,if结构不会在判断了,需要更改为while

while(number != 0) { //判断number值是否是0,如果不是0,等待
    this.wait(); //在哪里睡,就在哪里醒
}

实现中断和虚假唤醒是可能的,需要将其while方法用在循环中。

最后的结果:

AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
AA :: 1
BB :: 0
CC :: 1
BB :: 0
AA :: 1
BB :: 0
CC :: 1
BB :: 0
AA :: 1
DD :: 0
CC :: 1
BB :: 0
AA :: 1
DD :: 0
CC :: 1
BB :: 0
AA :: 1
DD :: 0
CC :: 1
DD :: 0
AA :: 1
DD :: 0
CC :: 1
DD :: 0
CC :: 1
DD :: 0
CC :: 1
DD :: 0
CC :: 1
DD :: 0
CC :: 1
DD :: 0

Process finished with exit code 0
2、Lock案例

使用lock先要创建锁的对象以及通知的对象
放置在资源类中

//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

上锁lock.lock();
解锁lock.unlock();
以下都为condition类:
唤醒所有等待的线程signalAll(),带上类名condition.signalAll();
唤醒一个等待线程signal(),带上类名,condition.signal();
造成当前线程在接到信号或者被中断之前一直处于等待状态await(),带上类名,condition.await();

同样是上面的案例题目换成lock

//第一步 创建资源类,定义属性和操作方法
class Share {
    private int number = 0;

    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //+1
    public void incr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while (number != 0) {
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName()+" :: "+number);
            //通知
            condition.signalAll();
        }finally {
            //解锁
            lock.unlock();
        }
    }

    //-1
    public void decr() throws InterruptedException {
        lock.lock();
        try {
            while(number != 1) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+" :: "+number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

public class ThreadDemo2 {

    public static void main(String[] args) {
        Share share = new Share();
        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();
        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }

}

实现的结果是一样的。

二、线程间定制化通信

所谓定制化通信,需要让线程进行一定的顺序操作

案列:启动三个线程,按照如下要求:
AA打印5此,BB打印10次,CC打印15次,一共进行10轮

具体思路
每个线程添加一个标志位,是该标志位则执行操作,并且修改为下一个标志位,通知下一个标志位的线程
在这里插入图片描述
代码

//第一步 创建资源类
class ShareResource {
    //定义标志位
    private int flag = 1;  // 1 AA     2 BB     3 CC

    //创建Lock锁
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    //打印5次,参数第几轮
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while(flag != 1) {
                //等待
                c1.await();
            }
            //干活
            for (int i = 1; i <=5; i++) {
                System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
            }
            //通知
            flag = 2; //修改标志位 2
            c2.signal(); //通知BB线程
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    //打印10次,参数第几轮
    public void print10(int loop) throws InterruptedException {
        lock.lock();
        try {
            while(flag != 2) {
                c2.await();
            }
            for (int i = 1; i <=10; i++) {
                System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
            }
            //修改标志位
            flag = 3;
            //通知CC线程
            c3.signal();
        }finally {
            lock.unlock();
        }
    }

    //打印15次,参数第几轮
    public void print15(int loop) throws InterruptedException {
        lock.lock();
        try {
            while(flag != 3) {
                c3.await();
            }
            for (int i = 1; i <=15; i++) {
                System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
            }
            //修改标志位
            flag = 1;
            //通知AA线程
            c1.signal();
        }finally {
            lock.unlock();
        }
    }
}

public class ThreadDemo3 {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    shareResource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    shareResource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    shareResource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}

结果

AA :: 1 :轮数:1
AA :: 2 :轮数:1
AA :: 3 :轮数:1
AA :: 4 :轮数:1
AA :: 5 :轮数:1
BB :: 1 :轮数:1
BB :: 2 :轮数:1
BB :: 3 :轮数:1
BB :: 4 :轮数:1
BB :: 5 :轮数:1
BB :: 6 :轮数:1
BB :: 7 :轮数:1
BB :: 8 :轮数:1
BB :: 9 :轮数:1
BB :: 10 :轮数:1
CC :: 1 :轮数:1
CC :: 2 :轮数:1
CC :: 3 :轮数:1
CC :: 4 :轮数:1
...
...
CC :: 9 :轮数:10
CC :: 10 :轮数:10
CC :: 11 :轮数:10
CC :: 12 :轮数:10
CC :: 13 :轮数:10
CC :: 14 :轮数:10
CC :: 15 :轮数:10

Process finished with exit code 0

三、集合的线程安全

创建集合使用string的泛型

//创建ArrayList集合
List<String> list = new ArrayList<>();

并且在集合中添加元素获取元素

for (int i = 0; i <30; i++) {

    new Thread(()->{
        //向集合添加内容
       list.add(UUID.randomUUID().toString().substring(0,8));
        //从集合获取内容
        System.out.println(list);
    },String.valueOf(i)).start();
}

查看源码,主要牵扯这个添加的方法是因为该方法在列表中是不安全的,没有synchronized声明

boolean add(E e);

运行之后报错:

Exception in thread "1" Exception in thread "3" Exception in thread "18" Exception in thread "16" Exception in thread "13" Exception in thread "15" Exception in thread "24" Exception in thread "21" Exception in thread "29" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.zxl.threadTest.ThreadDemo3.lambda$main$0(ThreadDemo3.java:19)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.zxl.threadTest.ThreadDemo3.lambda$main$0(ThreadDemo3.java:19)
	at java.lang.Thread.run(Thread.java:748)

java.util.ConcurrentModificationException为并发修改问题
List集合的解决方案有3种:
在这里插入图片描述

1、Vector

通过list下的实现类Vector
因为在Vector下的add普遍都是线程安全
查看源代码

public synchronized boolean add(E e) {
    modCount++;
    add(e, elementData, elementCount);
    return true;
}

在改变代码时,只需要将其修改为List<String> list = new Vector<>();
但此方法用的比较少,因为在jdk 1.0的版本适用

2、Collections

Collections类中的很多方法都是static静态方法
其中有一个方法是返回指定列表支持的同步(线程安全的)列表为synchronizedList(List list)。
具体代码为

List<String> list = Collections.synchronizedList(new ArrayList<>());

此方法也比较古老,很少使用。

3、CopyOnWriteArrayList

将其代码修改为

List<String> list = new CopyOnWriteArrayList<>();

涉及的底层原理为写时复制技术:
①、读的时候并发(多个线程操作)
②、写的时候独立,先复制相同的空间到某个区域,将其写到新区域,旧新合并,并且读新区域(每次加新内容都写到新区域,覆盖合并之前的旧区域,读取新区域添加的内容)

4、CopyOnWriteArraySet

该类是HashSet的实现类
同样使用HashSet类,也会出现线程不安全。

java Set<String> set = new HashSet<>();

需要将上面的代码改为

Set<String> set = new CopyOnWriteArraySet<>();

具体操作代码为:

for (int i = 0; i <30; i++) {
    new Thread(()->{
        //向集合添加内容
        set.add(UUID.randomUUID().toString().substring(0,8));
        //从集合获取内容
        System.out.println(set);
    },String.valueOf(i)).start();
}
5、ConcurrentHashMap

HashMap不安全线程也同理Map<String,String> map = new HashMap<>();
具体实现代码是

for (int i = 0; i <30; i++) {
    String key = String.valueOf(i);
    new Thread(()->{
        //向集合添加内容
        map.put(key,UUID.randomUUID().toString().substring(0,8));
        //从集合获取内容
        System.out.println(map);
    },String.valueOf(i)).start();
}

将其代码修改为

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

通过这行代码可以编程线程安全。
ConcurrentHashMap类是HashMap的实现类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

exodus3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值