[java多线程] - 锁机制&同步代码块&信号量

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

}

}

以上是 [java多线程] - 锁机制&同步代码块&信号量 的全部内容, 来源链接: utcz.com/z/393259.html

回到顶部