Java并发编程JUC(二) 集合 锁

一 ArrayList集合的线程安全

引入线程不安全问题

package new_course.chp3.list_demo;

import java.util.ArrayList;
import java.util.UUID;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/1 21:33
 * List集合线程不安全
 */
public class ErrorList {
    
    
    public static void main(String[] args) {
    
    
        //创建ArrayList集合
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                //往集合添加内容
                list.add(UUID.randomUUID().toString().substring(0, 8));
                //从集合取出内容
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}
//结果:
java.util.ConcurrentModificationException
程序会报错,修改异常

解决方法Vector Collections集合

package new_course.chp3.list_demo;

import java.util.*;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/1 21:33
 * List集合线程不安全
 */
public class ErrorList {
    
    
    public static void main(String[] args) {
    
    
        
        //Vector解决
//        List<String> list = new Vector<>();

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

        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                //往集合添加内容
                list.add(UUID.randomUUID().toString().substring(0, 8));
                //从集合取出内容
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

解决方法 CopyOnWriteArrayList

package new_course.chp3.list_demo;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/1 22:38
 */
public class Solution2 {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                //存入集合
                list.add(UUID.randomUUID().toString().substring(0,8));
                //从集合当中取出
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList原理

  1. 支持并发读操作
  2. 写的时候,先讲当前容器复制一份,然后在新副本上执行写的操作
  3. 结束之后再将原容器的引用指向新的容器
    在这里插入图片描述
//CopyOnWriteArrayList 的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);//调用下面的setArray方法
            return true;
        } finally {
    
    
            lock.unlock();
        }
    }


final void setArray(Object[] a) {
    
    
        array = a;//将原本的引用指向新的数组
    }

二 HashSet集合的线程安全

引入线程不安全问题

package new_course.chp3.hashset_demo;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 0:21
 * 演示HashSet线程不安全
 */
public class ErrorHashSet {
    
    
    public static void main(String[] args) {
    
    
        Set<String> set = new HashSet<>();
        for (int i = 0; i < 30; i++) {
    
    
            new Thread(() -> {
    
    
                //放入set集合
                set.add(UUID.randomUUID().toString().substring(0,8));
                //从set集合取出
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
//程序报错

解决方法 CopyOnWriteArraySet

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 0:21
 * 演示HashSet线程不安全
 */
public class Solution1{
    
    
    public static void main(String[] args) {
    
    

        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
    
    
            new Thread(() -> {
    
    
                //放入set集合
                set.add(UUID.randomUUID().toString().substring(0,8));
                //从set集合取出
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

三 HashMap集合的线程安全

引入线程不安全问题


package new_course.chp3.hashmap_demo;

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

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 0:27
 * 演示HashMap线程不安全问题
 */
public class ErrorHashMap {
    
    
    public static void main(String[] args) {
    
    
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < 10; i++) {
    
    
            String key = String.valueOf(i);
            new Thread(() -> {
    
    
                //放入集合
                map.put(String.valueOf(key),UUID.randomUUID().toString().substring(0,8));
                //取出集合
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}
//程序报错

解决方法 ConcurrentHashMap

package new_course.chp3.hashmap_demo;

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

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 0:36
 */
public class Solution1 {
    
    

    public static void main(String[] args) {
    
    
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 50; i++) {
    
    
            String key = String.valueOf(i);
            new Thread(() -> {
    
    
                //放入集合
                map.put(String.valueOf(key), UUID.randomUUID().toString().substring(0, 8));
                //取出集合
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }

}

四 多线程锁

八锁现象

package new_course.chp3;

import java.util.concurrent.TimeUnit;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 0:47
 */
class Phone {
    
    

    public synchronized void sendSMS() {
    
    
        //停留4秒
        try {
    
    
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("------sendSMS");
    }

    public static synchronized void sendSMS1() {
    
    
        //停留4秒
        try {
    
    
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("------sendSMS1");
    }

    public synchronized void sendEmail() {
    
    

        System.out.println("------sendEmail");
    }

    public static synchronized void sendEmail1() {
    
    

        System.out.println("------sendEmail1");
    }

    public void getHello() {
    
    

        System.out.println("------getHello");
    }
}


public class Lock8 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
    
    
//            phone1.sendSMS();
            phone1.sendSMS1();
        }, "线程1").start();
        Thread.sleep(100);
        new Thread(() -> {
    
    
//            phone1.getHello();
            phone2.sendEmail();
//            phone1.sendEmail();
//            phone2.sendEmail1();
        }, "线程2").start();
    }
}


/**
 * @Description 8锁
 * <p>
 * 1、标准访问,先打印短信还是邮件
 * ------sendSMS
 * ------sendEmail
 * <p>
 * 2、停4秒在短信方法内,先打印短信还是邮件
 * ------sendSMS
 * ------sendEmail
 * -------------------------synchronized锁的是当前对象this,所以上面两个都是一样的结果----------------------
 * <p>
 * 3、新增普通的hello方法,先打印短信(4s)还是hello
 * ------getHello
 * ------sendSMS
 * -------------------------sendSMS被锁住了,但是普通方法没有被锁----------------------
 * <p>
 * 4、现在有两部手机,先打印短信(4s)还是邮件
 * ------sendEmail
 * ------sendSMS
 * -------------------------synchronized锁的是当前对象this,两部手机分别为两个不同对象,所以被锁的是phone1,但是phone2没有被锁----------------------
 * 5、两个静态同步方法,1部手机,先打印短信还是邮件
 * ------sendSMS1
 * ------sendEmail1
 *
 * 6、两件静态同步方法,2部手机,先打印短信还是邮件
 * ------sendSMS1
 * ------sendEmail1
 * -------------------------以上两个方法,static+synchronized,锁的是类对象Class--------------
 * 7、1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
 * ------sendEmail
 * ------sendSMS1
 * <p>
 * 8、1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
 * ------sendEmail
 * ------sendSMS1
 * 
 */

在这里插入图片描述

公平锁与非公平锁

针对于之前卖票问题,假设此时有四个线程一起卖票(AA,BB,CC,DD),如果是非公平锁(默认),就可能出现一个线程卖完所有的票,而其他线程没有票卖的情况。而公平锁则是每个资源都能有机会一起卖票,减少出现旱的旱死涝的涝死情景。

如何声明公平锁和非公平锁呢

//公平锁
private final ReentrantLock lock = new ReentrantLock(true);
//非公平锁
private final ReentrantLock lock = new ReentrantLock(true);

非公平锁:效率高、线程饿死

公平锁:效率低、人人有份

可重入锁(递归锁)

可重复进入的锁

synchronized(隐式锁)

package new_course.chp3;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 11:21
 * 可重入锁
 */
public class SyncLockDemo {
    
    
    public synchronized void test() {
    
    
        test(); //Exception in thread "main" java.lang.StackOverflowError,如果不是可重入锁,就不会栈溢出,因为被锁住了
    }
    public static void main(String[] args) {
    
    
        SyncLockDemo syncLockDemo = new SyncLockDemo();
        syncLockDemo.test();
//        Object o = new Object();
//        new Thread(() -> {
    
    
//            synchronized (o) {
    
    
//                System.out.println(Thread.currentThread().getName() + "外层");
//                synchronized (o) {
    
    
//                    System.out.println(Thread.currentThread().getName() + "中层");
//                    synchronized (o) {
    
    
//                        System.out.println(Thread.currentThread().getName() + "内层");
//                        synchronized (o) {
    
    
//                            System.out.println(Thread.currentThread().getName() + "底层");
//                        }
//                    }
//                }
//            }
//        }, "aa").start();
        //结果
        aa外层
        aa中层
        aa内层
        aa底层
    }
}

Lock锁(显示)

package new_course.chp3;

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

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 11:21
 * 可重入锁
 */
public class SyncLockDemo02 {
    
    

    public static void main(String[] args) {
    
    
        //Lock演示可重入锁
        Lock lock = new ReentrantLock();
        new Thread(() -> {
    
    
            lock.lock();
            try {
    
    
                System.out.println(Thread.currentThread().getName() + "外层");

                new Thread(() -> {
    
    
                    lock.lock();
                    try {
    
    
                        System.out.println(Thread.currentThread().getName() + "外层");

                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    } finally {
    
    
                        lock.unlock();
                    }
                },"a").start();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                 lock.unlock();
            }
        },"a").start();
    }
}

死锁

死锁的概念

在这里插入图片描述

产生死锁的原因

  1. 系统资源不足
  2. 进程运行推进顺序不合适
  3. 资源分配不当

引入死锁场景

package new_course.chp4;

import java.util.concurrent.TimeUnit;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 13:02
 * 展示死锁
 */
public class DeadLock {
    
    
    //创建两个对象
    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            synchronized (a) {
    
    
                System.out.println(Thread.currentThread().getName() + "持有锁a,试图获取锁b");
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                synchronized (b) {
    
    
                    System.out.println(Thread.currentThread().getName() + "获取锁b");
                }
            }
        },"A").start();

        new Thread(() -> {
    
    
            synchronized (b) {
    
    
                System.out.println(Thread.currentThread().getName() + "持有锁b,试图获取锁a");
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                synchronized (a) {
    
    
                    System.out.println(Thread.currentThread().getName() + "获取锁a");
                }
            }
        },"B").start();
    }
}

验证是否是死锁

  • jps 类似linux的 ps -ef 展示java进程

在这里插入图片描述

  • jstack jvm自带,能直接查看到堆栈信息,堆栈跟踪工具

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_48244108/article/details/125342557
今日推荐