5、集合线程安全
-
集合的线程不安全演示
- ArrayList
package com.codetip.codejuc.juc.conllections; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class ThreadArrayList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // 向线程中添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); // 从里面取出内容 System.out.println(list); // 产生集合不安全的地方,取的时候,有线程还在放入内容,出现了并发修改问题 }, String.valueOf(i)).start(); } } }
运行结果如下:
-
解决方案:
- 使用vector(不经常使用-效率不高)
public class ThreadArrayListOnVector { public static void main(String[] args) { List<String> list = new Vector<>(); // 使用vector for (int i = 0; i <= 100; i++) { new Thread(() -> { // 向线程中添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); // 从里面取出内容 System.out.println(list); }, String.valueOf(i)).start(); } } }
- 使用Collections
public class ThreadArrayListOnCollections { public static void main(String[] args) { // 通过Collections工具包的工具类 List<String> list = Collections.synchronizedList(new ArrayList<>()); for (int i = 0; i <= 100; i++) { new Thread(() -> { // 向线程中添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); // 从里面取出内容 System.out.println(list); }, String.valueOf(i)).start(); } } }
-
使用写时复制技术(JUC中的CopyOnWriteArrayList)
- 概述:多个线程读的时候读取同一个内容,写的时候复制之前一个相同的内容往里面写,写完之后在覆盖或合并之前的内容。代码如下:
public static void main(String[] args) { // 使用Juc中的写时复制技术 List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // 向线程中添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); // 从里面取出内容 System.out.println(list); }, String.valueOf(i)).start(); } }
源码如下:
// 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); // (重新赋值)合并之前的内容 return true; } finally { lock.unlock(); // 解锁 } }
-
HashSet
-
线程不安全演示
// 演示HashSet线程不安全问题 public static void main(String[] args) { Set<String> list = new HashSet<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // 向线程中添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); // 从里面取出内容 System.out.println(list); }, String.valueOf(i)).start(); } }
-
解决方案
- 使用Juc中的CopyOnWriteArraySet解决
public static void main(String[] args) { Set<String> list = new CopyOnWriteArraySet<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // 向线程中添加内容 list.add(UUID.randomUUID().toString().substring(0, 8)); // 从里面取出内容 System.out.println(list); }, String.valueOf(i)).start(); } }
-
HashSet底层其实就是一个HashMap ,HashMap 的就是HashSet的值。HashMap的Key是不可重复,切无序的。
源码如下:
private transient HashMap<E,Object> map; public boolean add(E e) { return map.put(e, PRESENT)==null; }
-
-
HashMap
-
线程不安全演示
// HashMap线程不安全演示 public static void main(String[] args) { Map<String, String> map = new HashMap<>(); for (int i = 0; i <= 100; 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(); } }
-
解决方案,使用Juc包中的,ConcurrentHashMap
public static void main(String[] args) { Map<String, String> map = new ConcurrentHashMap<>(); // JUC 包中的ConcurrentHashMap for (int i = 0; i <= 100; 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(); } }
-
6、多线程锁
-
八种锁演示
package com.codetip.codejuc.juc; import java.util.concurrent.TimeUnit; class Phone { public synchronized void sendSms() throws InterruptedException { // 2.停留四秒 先打印短信还是邮件 // 停留四秒钟 TimeUnit.SECONDS.sleep(4); System.out.println("发送短信-----send_SMS"); } public synchronized void sendEmail() { System.out.println("发送邮件-----send_Email"); } public static synchronized void sendSmsS() throws InterruptedException { // 2.停留四秒 先打印短信还是邮件 // 停留四秒钟 TimeUnit.SECONDS.sleep(4); System.out.println("发送短信-----send_SMS"); } public static synchronized void sendEmailS() { System.out.println("发送邮件-----send_Email"); } public void getHello() { System.out.println("-------getHello"); } } /** * 8锁 * 1。一部手机,标准访问,先打印短信还是邮件 停留先时间注释掉,后面全部打开 * 发送短信-----send_SMS * 发送邮件-----send_Email * * 2.一部手机,停留四秒 先打印短信还是邮件 * 发送短信-----send_SMS * 发送邮件-----send_Email * 原因分析:1和2。执行顺序,第一个sms执行方法上有synchronized方法已经锁住当前对象,Email等待获取锁 * synchronized 锁的是当前对象 * * 3.新增普通的hello方法,是先打印短信还是hello * -------getHello * 发送短信-----send_SMS * * 原因:普通方法没有加锁,和锁没有关系,所以就直接执行了 * * 4.现在有两部手机,先打印短信还是邮件 * 发送邮件-----send_Email * 发送短信-----send_SMS (等了四秒钟) * 原因分析:有两个对象,synchronized锁定只是当前对象,用的不是同一把锁, * * 5.两个静态同步方法 1部手机,先打印短信还是邮件 * 发送短信-----send_SMS * 发送邮件-----send_Email * * 6.两个静态同步方法 2部手机,先打印短信还是邮件 * 发送短信-----send_SMS * 发送邮件-----send_Email * * 原因分析 5和6 static synchronized 锁定的范围发送了变化 ,不是当前对象this ,而是当前类的Class对象(类的字节码对象) * * 7。一个静态同步方法,1个普通方法,1部手机,先打印短信还是邮件 * 发送邮件-----send_Email * 发送短信-----send_SMS * * 8.一个静态同步方法,1个普通方法,2部手机,先打印短信还是邮件 * 发送邮件-----send_Email * 发送短信-----send_SMS(停留四秒) * * 原因分析:7和8 还是锁的作用范围不一样, static synchronized 锁定的是 类的Class对象, 不加static 锁定的是当前对象this * */ public class EightLock { public static void main(String[] args) throws InterruptedException { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { try { // 1.标准访问,先打印短信还是邮件 // 2.停留四秒 先打印短信还是邮件 // 3.新增普通的hello方法,是先打印短信还是hello //phone1.sendSms(); // 5.两个静态同步方法 1部手机,先打印短信还是邮件 // 6.两个静态同步方法 2部手机,先打印短信还是邮件 // 7.一个静态同步方法,1个普通方法,1部手机,先打印短信还是邮件 // 8.一个静态同步方法,1个普通方法,2部手机,先打印短信还是邮件 phone1.sendSmsS(); } catch (Exception e) { e.printStackTrace(); } }, "AA").start(); Thread.sleep(100); new Thread(() -> { try { // 1.标准访问,先打印短信还是邮件 // 2.停留四秒 先打印短信还是邮件 // phone1.sendEmail(); // 3.新增普通的hello方法,是先打印短信还是hello // phone1.getHello(); // 4.现在有两部手机,先打印短信还是邮件 // phone2.sendEmail(); // 5.两个静态同步方法 1部手机,先打印短信还是邮件 // phone1.sendEmailS(); // 6.两个静态同步方法 2部手机,先打印短信还是邮件 // phone2.sendEmailS(); // 7.一个静态同步方法,1个普通方法,1部手机,先打印短信还是邮件 // phone1.sendEmail(); // 8.一个静态同步方法,1个普通方法,2部手机,先打印短信还是邮件 phone2.sendEmail(); } catch (Exception e) { e.printStackTrace(); } }, "BB").start(); } }
总结:synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为一下三种情况
1、对于普通方法:锁的当前实例对象(通常说的This) 2、对于静态同步方法:锁的是当前类的Class对象 3、对于同步方法块,锁定的是synchronized括号里面的配置的对象 -
公平锁和非公平锁
-
具体代码如下
// 默认是非公平锁 private final Lock lock = new ReentrantLock(); // 源码如下 public ReentrantLock() { sync = new NonfairSync(); } // 如何使用公平锁 创建类的时候,在构造参数中传入 true 来指明使用公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } // 源码如下 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; } }
-
特点:
- 非公平锁特点:其他线程被饿死,效率高。(可以参考卖票的例子)
- 公平锁特点:每个线程都有可能获得多,效率稍低一点。(可以参考卖票的例子)
-
-
可重入锁(递归锁)
- synchronized 隐式的可重入锁(上锁和解锁都是自动完成的)
- lock:显示的可重入锁
代码演示:synchronized
// 案例一 同步代码块 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() + "内层"); } } } }, "aa").start(); // 案例二 同步方法 public class ReSyn { public synchronized void add() { add(); } // 使用synchronized关键字演示可重入锁 public static void main(String[] args) { new ReLock().add(); } } // 案例二运行出现:内存溢出,原因:synchronized 是一个可重入锁,锁住的add方法,因为是同一个对象,所以还可以进入调用add方法(可重入锁(递归锁))
代码演示:Lock
Lock lock = new ReentrantLock(); // 创建线程 new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "外层"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "内层"); } finally { lock.unlock(); } } finally { lock.unlock(); } }, "aa").start(); }
演示锁不关闭:
// 使用Lock关键字 锁不关闭 public static void main(String[] args) { Lock lock = new ReentrantLock(); // 创建线程 new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "外层"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "内层"); } finally { // 如果里面的锁不关闭 // lock.unlock(); } } finally { lock.unlock(); } }, "AA").start(); // 创建一个新的线程 new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + ""); } finally { lock.unlock(); } }, "BB").start(); } }
结果如下:出现死锁状态,原因:上面的所没有释放,下面的线程无法获取不到锁。
-
死锁两个或两个以上的线程在执行的过程,因为争夺资源而造成一种互相等待现象,如果没有哦外力的干涉,他没无法再执行下去。
-
产生死锁的原因:
- 系统资源不足
- 线程运行推进顺序不对
- 资源分配不当
-
代码演示:
package com.codetip.codejuc.juc.deallock; import java.util.concurrent.TimeUnit; public class DealLock { static Object a = new Object(); static Object b = new Object(); public static void main(String[] args) { // 持有锁a 等待获取锁b 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"); } } }, "aa").start(); // 持有锁b,等待获取锁a 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"); } } }, "bb").start(); } }
运行截图如下:
-
验证死锁
-
命令:使用jps(类似Linux中的 ps -ef)
-
jstack jvm中自带的堆栈跟踪命令
具体演示:
-
命令输入:jps -l 查看要观察类的进程ID ,此处id 为:3880
-
使用 jstack 3880 可以看到:死锁的具体信息
-
-
-