JUC高并发编程二

4 篇文章 0 订阅
4 篇文章 0 订阅

5、集合线程安全

  1. 集合的线程不安全演示

    1. 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();
            }
        }
    }
    
    

    运行结果如下:

     

    1. 解决方案:

      1. 使用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();
              }
          }
      }
      
      1. 使用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();
              }
          }
      }
      
      

      1. 使用写时复制技术(JUC中的CopyOnWriteArrayList)

        1. 概述:多个线程读的时候读取同一个内容,写的时候复制之前一个相同的内容往里面写,写完之后在覆盖或合并之前的内容。代码如下:
          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(); // 解锁
              }
          }
      
  2. HashSet

    1. 线程不安全演示

      // 演示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();
              }
          }
      

       

    2. 解决方案

      1. 使用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;
        }
        
  3. HashMap

    1. 线程不安全演示

          // 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();
              }
          }
      

       

    2. 解决方案,使用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、多线程锁

  1. 八种锁演示

    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括号里面的配置的对象
  2. 公平锁和非公平锁

    1. 具体代码如下

      // 默认是非公平锁
      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;
              }
          }
      
    2. 特点:

      1. 非公平锁特点:其他线程被饿死,效率高。(可以参考卖票的例子)
      2. 公平锁特点:每个线程都有可能获得多,效率稍低一点。(可以参考卖票的例子)
  3. 可重入锁(递归锁)

    1. synchronized 隐式的可重入锁(上锁和解锁都是自动完成的)
    2. 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();
      }
    }
    

    结果如下:出现死锁状态,原因:上面的所没有释放,下面的线程无法获取不到锁。

     

     

  4. 死锁两个或两个以上的线程在执行的过程,因为争夺资源而造成一种互相等待现象,如果没有哦外力的干涉,他没无法再执行下去。

    1. 产生死锁的原因:

      1. 系统资源不足
      2. 线程运行推进顺序不对
      3. 资源分配不当
    2. 代码演示:

      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();
          }
      }
      
      

      运行截图如下:

       

       

    3. 验证死锁

      1. 命令:使用jps(类似Linux中的 ps -ef)

      2. jstack jvm中自带的堆栈跟踪命令

        具体演示:

        • 命令输入:jps -l 查看要观察类的进程ID ,此处id 为:3880

           

        • 使用 jstack 3880 可以看到:死锁的具体信息

           

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值