• [java多线程]


      在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突。冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突。按照我的理解在java中实现同步的方式分为三种,分别是:同步代码块机制,锁机制,信号量机制。


    一、同步代码块

      在java的多线程并发开发过程中,我们最常用的方式就是使用同步代码关键字(synchronized)。这种方式的使用不是特别复杂,需要注意的只是你需要明确到底同步的是那个对象,只有当同步的对象一致的情况下,才能够控制互斥的操作。一般情况下,我们会同步this或者是当前class对象。同步this对当前实例有效,同步class对当前所有class的对象有效。下面这个demo的功能是,启动十个线程,最终结果是每个线程都将共享的变量加上1.

    private static final java.util.Random random = new java.util.Random(System.currentTimeMillis());
    
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            private int count = 0; // 资源对象
    
            @Override
            public void run() {
                try {
                    int oldCount = count;
                    Thread.sleep(random.nextInt(1000) + 10); // 处理
                    count = oldCount + 1;
                    System.out.println(Thread.currentThread().getName() + ", 原有资源:" + oldCount + ", 现在预期资源:" + (oldCount + 1) + ",现在实际资源:" + count);
                } catch (InterruptedException e) {
                }
            }
        };
    
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }

      我们可以发现结果如下图所示,明显可以看出在是个线程访问一个变量的情况下,导致最终的结果不对。

      加同步锁的代码和上述代码差不多,区别只是在获取资源和修改资源的时候进行同步块处理。

    int oldCount = 0;
    synchronized (this) {
        oldCount = count;
        Thread.sleep(random.nextInt(1000) + 10); // 处理
        count = oldCount + 1;
    }

    二、锁机制

      在Java中的锁机制是通过java.util.concurrent.locks.Lock来实现的,这个接口主要有三个实现类,分别是ReentrantLock,ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。锁机制和同步代码块相比,我们可以明显的发现使用lock机制会降低同步粒度,提高性能。特别是在一些情况下,使用lock是一种非常不错的选择,比如说在读远远高于写的状况下,使用读写锁那是一种非常不错的选择。下面直接来一个cachedemo

     1 static class CacheDemo {
     2         private static final int maxSize = 100000; // 最大存储量
     3         private static CacheDemo demo;
     4         private Map<String, String> cache = new LinkedHashMap<String, String>() {
     5             private static final long serialVersionUID = -7259602073057254864L;
     6 
     7             protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
     8                 return maxSize > this.size(); // 超过就移除
     9             };
    10         };
    11         private ReentrantReadWriteLock rrel = new ReentrantReadWriteLock();
    12         private Lock writeLock = rrel.writeLock(); // 写锁
    13         private Lock readLock = rrel.readLock(); // 读锁
    14 
    15         /**
    16          * 获取cache对象
    17          * 
    18          * @return
    19          */
    20         public static CacheDemo instance() {
    21             if (demo == null) {
    22                 synchronized (CacheDemo.class) {
    23                     if (demo == null) {
    24                         demo = new CacheDemo();
    25                     }
    26                 }
    27             }
    28             return demo;
    29         }
    30 
    31         /**
    32          * 添加
    33          * 
    34          * @param key
    35          * @param value
    36          */
    37         public void put(String key, String value) {
    38             this.writeLock.lock(); // 加锁
    39             try {
    40                 this.cache.put(key, value);
    41             } finally {
    42                 // 防止在操作过程中出现异常,使用try-finally保证解锁一定执行。
    43                 this.writeLock.unlock(); // 解锁
    44             }
    45         }
    46 
    47         /**
    48          * 获取这个对象
    49          * 
    50          * @param key
    51          * @return
    52          */
    53         public String get(String key) {
    54             this.readLock.lock(); // 加锁
    55             try {
    56                 return this.cache.get(key);
    57             } finally {
    58                 this.readLock.unlock(); // 解锁
    59             }
    60         }
    61 
    62         /**
    63          * 移除key
    64          * 
    65          * @param key
    66          */
    67         public void remove(String key) {
    68             this.writeLock.lock();
    69             try {
    70                 this.cache.remove(key);
    71             } finally {
    72                 this.writeLock.unlock();
    73             }
    74         }
    75 
    76         /**
    77          * 清空
    78          */
    79         public void clean() {
    80             this.writeLock.lock();
    81             try {
    82                 this.cache.clear();
    83             } finally {
    84                 this.writeLock.unlock();
    85             }
    86         }
    87     }
    CacheDemo

    三、信号量

      Java中的信号量主要有三种:Semaphore、CountDownLatch和CyclicBarrier。Semaphore可以维护访问自身的线程数,从而达到控制线程同步的需求;CountDownLatch主要作用是当计数器为0的时候,所有在该对象上等待的线程获得继续执行的权利;CyclicBarrier主要作用是当所有的线程准备好后,再允许线程执行。

     1 /**
     2  * {@link Semaphore}
     3  * 可以维护当前访问自身的线程数,并提供同步机制,使用Semahore可以控制同时访问资源的线程个数,例如:实现一个地下停车库。<br/>
     4  * 单个信号变量semphore对象可以实现互斥锁的功能,并且可以是其中一个线程获得锁,另外一个线程释放锁,那么可应用于死锁恢复的一些场所。
     5  * 
     6  * @author jsliuming
     7  * 
     8  */
     9 public class SemaphoreDemo {
    10     public static void main(String[] args) {
    11         ExecutorService service = Executors.newCachedThreadPool();
    12         try {
    13             final Semaphore semaphore = new Semaphore(3); // 3个同步变量
    14             for (int i = 0; i < 10; i++) {
    15                 Runnable runnable = new Runnable() {
    16 
    17                     @Override
    18                     public void run() {
    19                         String name = Thread.currentThread().getName();
    20                         try {
    21                             System.out.println("线程[" + name + "]开始获取资源....");
    22                             semaphore.acquire(); // 请求资源,有阻塞效果
    23                             System.out.println("线程[" + name + "]需要的资源获取到.");
    24                         } catch (InterruptedException e) {
    25                             e.printStackTrace();
    26                         }
    27 
    28                         long time = (long) (Math.random() * 2000);
    29                         System.out.println("线程[" + name + "]已经进入,当前有:" + (3 - semaphore.availablePermits()) + "个线程运行.准备停留:" + time);
    30 
    31                         try {
    32                             Thread.sleep(time);
    33                         } catch (InterruptedException e) {
    34                             e.printStackTrace();
    35                         } finally {
    36                             semaphore.release(); // 放回
    37                         }
    38                         System.out.println("线程[" + name + "]运行完成!");
    39                     }
    40                 };
    41                 service.execute(runnable);
    42             }
    43         } finally {
    44             service.shutdown();
    45         }
    46 
    47     }
    48 }
    /**
     * {@link CountDownLatch}
     * 倒计时计时器,调用对象的countDown方法将计时器数减少一,那么直到0的时候,就会让所有等待的线程开始运行。
     * 
     * @author jsliuming
     * 
     */
    public class CountDownLatchDemo {
        static final Random random = new Random(System.currentTimeMillis());
    
        public static void main(String[] args) {
            ExecutorService service = Executors.newCachedThreadPool();
            final CountDownLatch cdOrder = new CountDownLatch(1);
            int n = 3;
            final CountDownLatch cdAnswer = new CountDownLatch(n);
            for (int i = 0; i < n; i++) {
                Runnable runnable = new Runnable() {
    
                    @Override
                    public void run() {
                        String name = Thread.currentThread().getName();
                        try {
                            System.out.println("线程[" + name + "]准备接受命令");
                            cdOrder.await();
                            long t1 = Math.abs(random.nextLong()) % 20000;
                            System.out.println("线程[" + name + "]已经接受到命令,处理时间需要" + t1);
                            Thread.sleep(t1);
                            System.out.println("线程[" + name + "]回应命令处理结束");
                            cdAnswer.countDown();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                };
    
                service.execute(runnable);
            }
    
            try {
                Thread.sleep(Math.abs(random.nextLong()) % 3000);
                String name = Thread.currentThread().getName();
                System.out.println("线程[" + name + "]即将发布命令");
                cdOrder.countDown();
                System.out.println("线程[" + name + "]已发布命令,等待结果响应");
                cdAnswer.await();
                System.out.println("线程[" + name + "]收到所有的响应结果");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                service.shutdown();
            }
        }
    }
    /**
     * {@link CyclicBarrier}表示请大家等待,等所有集合都准备好了,那么就开始运行,这个过程可以循环。<br/>
     * 比如:公司部门的周末准备一起出去游玩,先等到所有的人到达汽车才开始启动车辆到目的地去,到后自由玩,然后到1点在一起吃饭。
     * 
     * @author jsliuming
     * 
     */
    public class CyclicBarrierDemo {
        final static Random random = new Random(System.currentTimeMillis());
    
        public static void main(String[] args) {
            ExecutorService service = Executors.newCachedThreadPool();
            int n = 3;
            final CyclicBarrier barrier = new CyclicBarrier(n); // 总共十个人
            for (int i = 0; i < n; i++) {
                Runnable runnable = new Runnable() {
    
                    @Override
                    public void run() {
                        try {
                            String name = Thread.currentThread().getName();
                            long t1 = Math.abs(random.nextLong()) % 2000;
                            System.out.println("线程[" + name + "] " + t1 + " 后到达集合地点1,现在已经有" + (barrier.getNumberWaiting()) + "人到达!");
                            Thread.sleep(t1);
                            System.out.println("线程[" + name + "]已经到达集合地点1,现在已经有" + (barrier.getNumberWaiting() + 1) + "人到达!");
                            barrier.await(); // 等待
    
                            System.out.println("线程[" + name + "]在车上...自由活动....");
    
                            t1 = Math.abs(random.nextLong()) % 2000;
                            System.out.println("线程[" + name + "] " + t1 + " 后到达集合地点2,现在已经有" + (barrier.getNumberWaiting()) + "人到达!");
                            Thread.sleep(t1);
                            System.out.println("线程[" + name + "]已经到达集合地点2,现在已经有" + (barrier.getNumberWaiting()) + "人到达!");
                            barrier.await(); // 等待
                            System.out.println("线程[" + name + "]觉得是美好的一天.");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                };
    
                service.execute(runnable);
            }
            service.shutdown();
        }
    }
  • 相关阅读:
    反思二
    安装Electron时卡在install.js不动的解决方案
    解决npm 下载速度慢的问题
    覆盖第三方jar包中的某一个类。妙!!
    关于拦截器是用注解方便,还是用配置文件写死方便的总结。
    yapi 启动后,老是自动关闭的问题。
    BaseResponse公共响应类,与我的设计一模一样,靠、ApiResponse
    HashMap 的 7 种遍历方式与性能分析!(强烈推荐)、forEach
    Jackson objectMapper.readValue 方法 详解
    yapi tag的问题,暂时只保留一个tag
  • 原文地址:https://www.cnblogs.com/liuming1992/p/4766024.html
Copyright © 2020-2023  润新知