【Java多线程】浅谈多线程机制(三)之互斥与同步
2017-03-15 21:42
585 查看
一、概述
在说明线程的互斥和同步之前,先看一个叫做竞争条件的名词。和操作系统中进程间的通信一样,竞争条件是指:两个或者两个以上线程同时读写某些共享数据时,最后的执行结果取决于线程运行的精确时序的情况。 竞争条件的出现可能导致共享数据被破坏,即可能出现数据不一致性的情况。
一个基本事实:在任意时刻,CPU上最多只能运行一个线程(进程)实例。有了这样一个基本事实,可以举这样一个小的例子来说明:假设有一片共享数据区域存储了一个整数50,在某个时刻,线程A尝试读取出该共享区域的数据并使其增加1,但是还没等到A把加1后的数据写回,它的执行时间已经到了,这时调度程序选择线程B执行,B读出共享数据区域数据,仍然为整数50,这时调度程序又转而选择A执行,A把51写回共享数据区,A线程执行完毕,停止。这时B又执行,当它再从共享数据区域读出数据用于验证时,发现51≠50,数据不一致了。对于B线程来说,它感觉自己是连续不断执行的,可事实上中断了一次。
二、实例演示
下面用一个实际例子来说明这种不一致情况:
一个封闭的空间中有若干盒子,每个盒子中装有若干个小球
小球可以从一个盒子转移到另外一个盒子,但是不能凭空产生或者消失
利用多线程来模拟小球在不同盒子间转移的情景。
基于上述条件,可以用如下程序实现:
(1)BallSystem.java:表示这个小球的封闭系统
(2)BallTransferTask.java:小球转移的线程
(3)BallSystemTest.java:主方法在此类中定义
运行上述程序可以得到结果:
结果分析:可以看到,程序中并没有设置生成小球或者丢失小球的相关操作,但是不同时刻,小球的总数不总是等于100,000的。这就是因为前文所说的竞争条件引起的。
三、解决办法
那么通过什么样的方法来避免出现竞争条件呢?这就引出了本篇文章的主题,互斥与同步。简单来说,互斥与同步有如下意义:
互斥表示控制不允许两个或多个线程同时进入临界区。(把对共享内存进行访问的程序片段称之为临界区)
同步表示线程间的一种通信机制。好比在同一家公司的两个项目搭档,其中某个人完成了他负责的工作,应该告诉另外一个人他已经完成的事实。
1、实现互斥:增加一个锁变量(仅当某个线程获得锁对象后才能进入临界区,即操作共享数据区域数据)。在Java中可以通过synchronized块儿来实现操作锁变量从而实现线程间的互斥。我们将BallSystem.java做如下修改:
2、线程同步:如上所示,在某个转移操作执行完毕之后,盒子中小球的数量将发生变化,我们通过notifyAll()这个函数唤醒所有等待的线程,它们或许将再下一次执行中满足条件而可以顺利执行完毕,即实现了同步。
此时运行结果如下:
结果分析: (上述仅截取运行结果的一部分,有兴趣的读者可以运行程序来亲自观察)这时我们发现小球总数一直维持在100,000不再改变,当然这也是我们期望的结果。
以上关于通过加锁实现线程互斥,通过线程等待与唤醒实现同步,以及synchronized块的具体实现,由于篇幅有限这里不再详细介绍,这里仅介绍在Java中如何通过编程实现互斥与同步。
关于Java的并发编程、线程(进程)间的通信、线程(进程)的调度是个庞大的知识体系,涉及到Java语言机制、死锁、线程(进程)的几种状态及转换、互斥与同步等等方方面面的知识,如果对此理解有困难的读者可以去查阅有关方面的资料,可以先从实例的入手,比如几个经典的IPC问题(读者-写者问题、哲学家就餐问题)等。
在说明线程的互斥和同步之前,先看一个叫做竞争条件的名词。和操作系统中进程间的通信一样,竞争条件是指:两个或者两个以上线程同时读写某些共享数据时,最后的执行结果取决于线程运行的精确时序的情况。 竞争条件的出现可能导致共享数据被破坏,即可能出现数据不一致性的情况。
一个基本事实:在任意时刻,CPU上最多只能运行一个线程(进程)实例。有了这样一个基本事实,可以举这样一个小的例子来说明:假设有一片共享数据区域存储了一个整数50,在某个时刻,线程A尝试读取出该共享区域的数据并使其增加1,但是还没等到A把加1后的数据写回,它的执行时间已经到了,这时调度程序选择线程B执行,B读出共享数据区域数据,仍然为整数50,这时调度程序又转而选择A执行,A把51写回共享数据区,A线程执行完毕,停止。这时B又执行,当它再从共享数据区域读出数据用于验证时,发现51≠50,数据不一致了。对于B线程来说,它感觉自己是连续不断执行的,可事实上中断了一次。
二、实例演示
下面用一个实际例子来说明这种不一致情况:
一个封闭的空间中有若干盒子,每个盒子中装有若干个小球
小球可以从一个盒子转移到另外一个盒子,但是不能凭空产生或者消失
利用多线程来模拟小球在不同盒子间转移的情景。
基于上述条件,可以用如下程序实现:
(1)BallSystem.java:表示这个小球的封闭系统
package com.bebdong.ipc; public class BallSystem { private final int[] ballBoxs; //表示装小球的盒子 /** * @param n 盒子数量 4000 * @param initialBallNum 盒子初始小球的数量 */ public BallSystem(int n,int initialBallNum) { ballBoxs=new int ; for(int i=0;i<ballBoxs.length;i++) ballBoxs[i]=initialBallNum; } //返回当前小球总数 public int getTotalBalls() { int temp=0; for(int count:ballBoxs) temp+=count; return temp; } //获取盒子数量 public int getBoxAmount() { return ballBoxs.length; } /** * @param from 转出盒子 * @param to 转入盒子 * @param count 转移数量 */ public void transfer(int from,int to,int count) { if(ballBoxs[from]<count) //此盒子已不足以转出count数量小球 return; System.out.print(Thread.currentThread().getName()); ballBoxs[from]-=count; //转出count数量的小球 System.out.printf("从%d转移%d个小球到%d", from,count,to); ballBoxs[to]+=count; //转入count数量的小球 System.out.print(" 当前小球总数:"+getTotalBalls()); System.out.println(); } }
(2)BallTransferTask.java:小球转移的线程
package com.bebdong.ipc; public class BallTranferTask implements Runnable { //共享的小球系统 private BallSystem ballSystem; //小球转移的源下标 private int fromBox; //单次小球转移最大数目 private int maxCount; //最大休眠时间(毫秒) private int DELAY=10; public BallTranferTask(BallSystem ballSystem,int from,int max) { this.ballSystem=ballSystem; this.fromBox=from; this.maxCount=max; } @Override public void run() { while(true) { int toBox=(int)(ballSystem.getBoxAmount()*Math.random()); //随机指定转移目的地 int count=(int)(maxCount*Math.random()); ballSystem.transfer(fromBox, toBox, count); try { Thread.sleep((long) ((int)(DELAY)*Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } } } }
(3)BallSystemTest.java:主方法在此类中定义
package com.bebdong.ipc; public class BallSystemTest { //总数为100*1000个小球 public static final int BOX_AMOUNT=100; public static final int INITIAL_BALL_NUM=1000; public static void main(String[] args) { BallSystem ball=new BallSystem(BOX_AMOUNT, INITIAL_BALL_NUM); //对每个盒子构造一个转移线程,使其成为小球转出方 for(int i=0;i<BOX_AMOUNT;i++) { BallTranferTask task=new BallTranferTask(ball, i, INITIAL_BALL_NUM); Thread thread=new Thread(task,"转移线程_"+i); thread.start(); } } }
运行上述程序可以得到结果:
结果分析:可以看到,程序中并没有设置生成小球或者丢失小球的相关操作,但是不同时刻,小球的总数不总是等于100,000的。这就是因为前文所说的竞争条件引起的。
三、解决办法
那么通过什么样的方法来避免出现竞争条件呢?这就引出了本篇文章的主题,互斥与同步。简单来说,互斥与同步有如下意义:
互斥表示控制不允许两个或多个线程同时进入临界区。(把对共享内存进行访问的程序片段称之为临界区)
同步表示线程间的一种通信机制。好比在同一家公司的两个项目搭档,其中某个人完成了他负责的工作,应该告诉另外一个人他已经完成的事实。
1、实现互斥:增加一个锁变量(仅当某个线程获得锁对象后才能进入临界区,即操作共享数据区域数据)。在Java中可以通过synchronized块儿来实现操作锁变量从而实现线程间的互斥。我们将BallSystem.java做如下修改:
package com.bebdong.ipc; public class BallSystem { private final int[] ballBoxs; //表示装小球的盒子 private final Object lock=new Object(); //锁变量 /** * @param n 盒子数量 * @param initialBallNum 盒子初始小球的数量 */ public BallSystem(int n,int initialBallNum) { ballBoxs=new int ; for(int i=0;i<ballBoxs.length;i++) ballBoxs[i]=initialBallNum; } //返回当前小球总数 public int getTotalBalls() { int temp=0; for(int count:ballBoxs) temp+=count; return temp; } //获取盒子数量 public int getBoxAmount() { return ballBoxs.length; } /** * @param from 转出盒子 * @param to 转入盒子 * @param count 转移数量 */ public void transfer(int from,int to,int count) { //对lock加锁实现互斥行为 synchronized (lock) { //if(ballBoxs[from]<count) //此盒子已不足以转出count数量小球 //return; //为了避免出现此种不合理情况的线程仍然申请锁资源,降低系统效率的情况,我们做如下改进 //如下循环保证了不满足条件的情况下不会再次竞争CPU资源 while(ballBoxs[from]<count) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print(Thread.currentThread().getName()); ballBoxs[from]-=count; //转出count数量的小球 System.out.printf("从%d转移%d个小球到%d", from,count,to); ballBoxs[to]+=count; //转入count数量的小球 System.out.print(" 当前小球总数:"+getTotalBalls()); System.out.println(); //转移后,条件变化,唤醒lock上等待的线程,即线程同步过程 lock.notifyAll(); } } }
2、线程同步:如上所示,在某个转移操作执行完毕之后,盒子中小球的数量将发生变化,我们通过notifyAll()这个函数唤醒所有等待的线程,它们或许将再下一次执行中满足条件而可以顺利执行完毕,即实现了同步。
此时运行结果如下:
结果分析: (上述仅截取运行结果的一部分,有兴趣的读者可以运行程序来亲自观察)这时我们发现小球总数一直维持在100,000不再改变,当然这也是我们期望的结果。
以上关于通过加锁实现线程互斥,通过线程等待与唤醒实现同步,以及synchronized块的具体实现,由于篇幅有限这里不再详细介绍,这里仅介绍在Java中如何通过编程实现互斥与同步。
关于Java的并发编程、线程(进程)间的通信、线程(进程)的调度是个庞大的知识体系,涉及到Java语言机制、死锁、线程(进程)的几种状态及转换、互斥与同步等等方方面面的知识,如果对此理解有困难的读者可以去查阅有关方面的资料,可以先从实例的入手,比如几个经典的IPC问题(读者-写者问题、哲学家就餐问题)等。
相关文章推荐
- 第二十六篇:JAVA多线程机制之同步与互斥
- Java同步机制浅谈――synchronized对代码作何影响?
- Java同步机制浅谈――synchronized对代码作何影响?
- 浅谈Java多线程的同步问题
- 【转】浅谈Java多线程的同步问题
- Java 同步机制浅谈
- Java同步机制浅谈 synchronized
- 浅谈Java同步机制synchronized对代码作何影响?
- 浅谈Java多线程的同步问题
- 浅谈Java多线程的同步问题
- 浅谈Java多线程的同步问题
- java 同步机制浅谈-----synchronized对代码作何影响?
- 转载:Java同步机制浅谈――synchronized对代码作何影响?
- Java同步机制浅谈――synchronized对代码作何影响?
- 浅谈Java多线程的同步问题
- Java中的同步与互斥机制--synchornized学习
- Java同步机制浅谈――synchronized对代码作何影响?
- Java同步机制浅谈――synchronized对代码作何影响? (转载)
- Java同步机制浅谈―synchronized
- Java同步机制浅谈――synchronized对代码作何影响?