在美眉图片下载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

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

[java多线程] - 锁机制&同步代码块&信号量的更多相关文章

  1. Java的synchronized的同步代码块和同步方法的区别

    synchronized同步方法和同步代码块的区别 同步方法默认使用this或者当前类做为锁. 同步代码块可以选择以什么来加锁,比同步方法更精确,我们可以选择只有会在同步发生同步问题的代码加锁,而并不 ...

  2. java 多线程9 : synchronized锁机制 之 代码块锁

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  3. java 多线程: Thread 并发访问-代码块同步synchronized {};String作为被锁的对象

    方法同步的弊端 方法同步的时候,如果一个方法需要线程安全控制的代码速度其实很快,但是还有其他的业务逻辑代码耗时非常长(比如网络请求),这样所有的线程就在这一块就等待着了,这样造成了极大的资源浪费如果并 ...

  4. java的同步方法和同步代码块,对象锁,类锁区别

    /** * @author admin * @date 2018/1/12 9:48 * 作用在同一个实例对象上讨论 * synchronized同步方法的测试 * 两个线程,一个线程调用synchr ...

  5. java中的synchronized同步代码块和同步方法的区别

    下面这两段代码有什么区别? //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized ...

  6. Java基础8-多线程;同步代码块

    作业解析 利用白富美接口案例,土豪征婚使用匿名内部类对象实现. interface White{ public void white(); } interface Rich{ public void ...

  7. synchronized锁机制 之 代码块锁(转)

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  8. java线程基础巩固---同步代码块以及同步方法之间的区别和关系

    在上一次中[http://www.cnblogs.com/webor2006/p/8040369.html]采用同步代码块的方式来实现对线程的同步,如下: 对于同步方法我想都知道,就是将同步关键字声明 ...

  9. synchronized同步方法和同步代码块的区别

    同步方法默认使用this或者当前类做为锁. 同步代码块可以选择以什么来加锁,比同步方法更精确,我们可以选择只有会在同步发生同步问题的代码加锁,而并不是整个方法. 同步方法使用synchronized修 ...

随机推荐

  1. Zynq和microblaze的区别

    Zynq钩中PS端的外设之后不需要初始化过程,但是如果在microblaze中连接外设之后需要有初始化过程.

  2. [osg]osg显示中文信息

    转自:http://www.cnblogs.com/feixiang-peng/articles/3152754.html 写好了在osg中实时显示中文信息的效果.中间遇到两个问题,一个是中文显示,一 ...

  3. UVa 10400 - Game Show Math

    题目大意:给出n(n<100)个正整数和一个目标数,按照给出数的顺序,运用+.-.*./四则运算(不考虑优先级),判断能否得出所要的结果. 首先考虑的就是暴力枚举,不过时间复杂度为O(4n),会 ...

  4. UVa 10602 - Editor Nottoobad

    题目大意:有一个编辑器,它有两种命令,“重复上一个单词” 和 “删除前一个字母”,给出一系列字符串,求最少的敲击键盘的次数. 题目中强调第一个敲的单词必须是给的第一个单词,于是就考虑按照单词与第一个单 ...

  5. systemd学习

    http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html http://www.ruanyifeng.com/blog ...

  6. sql相关语言

    SQL 掌握一门编程语言: C C++ Java C# ... 数据库 数据结构/算法 链表 队列 栈 数组 面向对象 网络 (界面.业务逻辑) 关系型数据库: 以二维表的形式组织数据 表.索引.视图 ...

  7. --@angularjs--理解Angular中的$apply()以及$digest()

    $apply() 和 $digest() 在 AngularJS 中是两个核心概念,但是有时候它们又让人困惑.而为了了解 AngularJS 的工作方式,首先需要了解 $apply() 和 $dige ...

  8. HTML 样式- CSS

    如何使用CSS CSS 是在 HTML 4 开始使用的,是为了更好的渲染HTML元素而引入的. CSS 可以通过以下方式添加到HTML中: 内联样式- 在HTML元素中使用"style&qu ...

  9. 如何在windows xp下实现声音内录

    问题描述: 用屏幕录制软件录制一个视频,能够成功录制视频,但无法录制视频里面的声音. 问题原因: 因为现在的多数声卡,均无法直接通过声卡自身的功能实现内录和立体声混音. 这是由于声卡芯片厂商迫于RIA ...

  10. 触摸滑动插件 Swiper

    Swiper Swiper  是纯javascript打造的滑动特效插件,面向手机.平板电脑等移动终端. Swiper中文网里已有详细的使用介绍,我就不多做介绍了. http://www.swiper ...