[java多线程] - 锁机制&同步代码块&信号量
2015-08-28 11:33
639 查看
在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突。冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突。按照我的理解在java中实现同步的方式分为三种,分别是:同步代码块机制,锁机制,信号量机制。
一、同步代码块
在java的多线程并发开发过程中,我们最常用的方式就是使用同步代码关键字(synchronized)。这种方式的使用不是特别复杂,需要注意的只是你需要明确到底同步的是那个对象,只有当同步的对象一致的情况下,才能够控制互斥的操作。一般情况下,我们会同步this或者是当前class对象。同步this对当前实例有效,同步class对当前所有class的对象有效。下面这个demo的功能是,启动十个线程,最终结果是每个线程都将共享的变量加上1.
我们可以发现结果如下图所示,明显可以看出在是个线程访问一个变量的情况下,导致最终的结果不对。
CacheDemo
三、信号量
Java中的信号量主要有三种:Semaphore、CountDownLatch和CyclicBarrier。Semaphore可以维护访问自身的线程数,从而达到控制线程同步的需求;CountDownLatch主要作用是当计数器为0的时候,所有在该对象上等待的线程获得继续执行的权利;CyclicBarrier主要作用是当所有的线程准备好后,再允许线程执行。
一、同步代码块
在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(); } }
我们可以发现结果如下图所示,明显可以看出在是个线程访问一个变量的情况下,导致最终的结果不对。
static class CacheDemo { private static final int maxSize = 100000; // 最大存储量 private static CacheDemo demo; private Map<String, String> cache = new LinkedHashMap<String, String>() { private static final long serialVersionUID = -7259602073057254864L; protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { return maxSize > this.size(); // 超过就移除 }; }; private ReentrantReadWriteLock rrel = new ReentrantReadWriteLock(); private Lock writeLock = rrel.writeLock(); // 写锁 private Lock readLock = rrel.readLock(); // 读锁 /** * 获取cache对象 * * @return */ public static CacheDemo instance() { if (demo == null) { synchronized (CacheDemo.class) { if (demo == null) { demo = new CacheDemo(); } } } return demo; } /** * 添加 * * @param key * @param value */ public void put(String key, String value) { this.writeLock.lock(); // 加锁 try { this.cache.put(key, value); } finally { // 防止在操作过程中出现异常,使用try-finally保证解锁一定执行。 this.writeLock.unlock(); // 解锁 } } /** * 获取这个对象 * * @param key * @return */ public String get(String key) { this.readLock.lock(); // 加锁 try { return this.cache.get(key); } finally { this.readLock.unlock(); // 解锁 } } /** * 移除key * * @param key */ public void remove(String key) { this.writeLock.lock(); try { this.cache.remove(key); } finally { this.writeLock.unlock(); } } /** * 清空 */ public void clean() { this.writeLock.lock(); try { this.cache.clear(); } finally { this.writeLock.unlock(); } } }
CacheDemo
三、信号量
Java中的信号量主要有三种:Semaphore、CountDownLatch和CyclicBarrier。Semaphore可以维护访问自身的线程数,从而达到控制线程同步的需求;CountDownLatch主要作用是当计数器为0的时候,所有在该对象上等待的线程获得继续执行的权利;CyclicBarrier主要作用是当所有的线程准备好后,再允许线程执行。
/** * {@link Semaphore} * 可以维护当前访问自身的线程数,并提供同步机制,使用Semahore可以控制同时访问资源的线程个数,例如:实现一个地下停车库。<br/> * 单个信号变量semphore对象可以实现互斥锁的功能,并且可以是其中一个线程获得锁,另外一个线程释放锁,那么可应用于死锁恢复的一些场所。 * * @author jsliuming * */ public class SemaphoreDemo { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); try { final Semaphore semaphore = new Semaphore(3); // 3个同步变量 for (int i = 0; i < 10; i++) { Runnable runnable = new Runnable() { @Override public void run() { String name = Thread.currentThread().getName(); try { System.out.println("线程[" + name + "]开始获取资源...."); semaphore.acquire(); // 请求资源,有阻塞效果 System.out.println("线程[" + name + "]需要的资源获取到."); } catch (InterruptedException e) { e.printStackTrace(); } long time = (long) (Math.random() * 2000); System.out.println("线程[" + name + "]已经进入,当前有:" + (3 - semaphore.availablePermits()) + "个线程运行.准备停留:" + time); try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 放回 } System.out.println("线程[" + name + "]运行完成!"); } }; service.execute(runnable); } } finally { service.shutdown(); } } }
/** * {@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(); } }
相关文章推荐
- spring mvc上传文件服务器配置
- 监控和管理生产环境spring boot actuator
- java碎片
- Java集合—Collection篇
- java基础-IO-File类、Properties类、打印流、序列流、字符编码
- java学习之继承、重载、重写(覆盖)
- java中的包
- JAVA多线程和并发性知识点总结
- springMVC Spring MVC 异常处理
- Struts2中的配置问题
- Spring4新特性:泛型限定式依赖注入
- Jdk用native2ascii命令做unicode编码转换
- Could not initialize JavaVM
- java基础-IO-字符流、字节流
- springmvc常用注解以及参数传递
- spring mvc json乱码问题
- myeclipse开发jar包not find总结(更新中)
- java+Struts2生成验证码
- 转 Java内存管理原理及内存区域详解
- JAVA非空条件三元运算符