我们先通过一道面试题来了解传统的线程同步通信。

题目:
子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
接着再回到主线程又循环100次,如此循环50次,请写出程序。

我没有看答案,先用自己的思路写了一段代码,有一些是借鉴传统的“生产者与消费者”的
多线程模型写出来的:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
package cn.edu.hpu.test;

/**
* 要求的操作:
* 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
* 接着再回到主线程又循环100次,如此循环50次。
* **/
public class ThreadTest3 {

public static void main(String[] args) {
Output out =new Output();//循环输出类
//注意:这里要保证子线程和主线程类引入的是同一个Output对象
//不然无法共用两把“钥匙”
MainRunnable main=new MainRunnable(out);//主线程
SonRunnable son=new SonRunnable(out);//子线程
new Thread(son).start();//主线程启动
new Thread(main).start();//子线程启动
}
}

class Output{

//两把“钥匙”
static boolean mbegin=false;
static boolean sbegin=true;//第一次子线程开始执行,所以这里默认为true

//主线程循环打印的方法(不可被打断)
public synchronized void doMainWhile(int num) throws InterruptedException {
int i=0;//每次i都要初始化为0,供循环使用
while(mbegin==false){//如果子线程在执行,主线程要等待,直到子线程恢复主线程的钥匙
this.wait();
}
for (i = 1; i <=100; i++) {//开始循环(每次在方法里循环100次)
System.out.println("主线程执行了第"+i+"次循环,总循环为第"+num+"次");
if(i==100){
break;
}
}
if(i==100){//主线程循环打印完毕之后,要让位给子线程执行
sbegin=true;//子线程开始工作
mbegin=false;//主线程停止工作
this.notify();//通知其他线程开始工作
}
}

//子线程循环打印的方法(不可被打断)
public synchronized void doSonWhile(int num) throws InterruptedException {
int j=0;//每次i都要初始化为0,供循环使用
while(sbegin==false){//如果主线程在执行,子线程要等待,直到主线程恢复子线程的钥匙
this.wait();
}
for (j = 1; j <=10; j++) {//开始循环(每次在方法里循环10次)
System.out.println("子线程执行了第"+j+"次循环,总循环为第"+num+"次");
if(j==10){
break;
}
}
if(j==10){//子线程循环打印完毕之后,要让位给主线程执行
sbegin=false;//子线程停止工作
mbegin=true;//主线程开始
this.notify();//通知其他线程开始工作
}
}

}

class MainRunnable implements Runnable{

Output out =null;

MainRunnable(Output out){//将Output对象引入进来
this.out=out;
}

public void run() {
try {
//因为要执行50次要求的操作,每次操作主线程要执行2次,一共50*2=100次
for(int i=1;i<=100;i++){
out.doMainWhile(i%2==0?i/2:(i+1)/2);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

class SonRunnable implements Runnable{

Output out =null;

SonRunnable(Output out){
this.out=out;
}

public void run() {
try {
//因为要执行50次要求的操作,每次操作子线程要执行2次,一共50*2=100次
for(int i=1;i<=100;i++){
out.doSonWhile(i%2==0?i/2:(i+1)/2);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

首先我是创建了一个循环类,然后使用这个循环类进行循环打印操作,而且针对子线程和主线程使用不同的方法去执行,
其中主线程的循环打印方法中,让其打印100次,然后子线程的循环打印方法中,让其打印50次,而且使用关键字synchronized
保证各自的方法不会被打断。
给主线程和子线程传入共同的Output对象,可以保证共用统一的静态全部变量(就是里面的两把“钥匙”)。

线程的执行规则就是,一开始先让子线程运行(主线程其实也运行了,只是mbegin变量为false让其阻塞了),运行完其循环10次的
方法之后,改变sbegin和mbegin的值,并通知其它线程。此时子线程由于sbegin变量为false而阻塞,主线程由于mbegin变量为true
而开始运行,主线程执行完自己的100次循环之后,改变sbegin和mbegin的值,并通知其它线程。此时主线程由于mbegin变量为false
而阻塞,而子线程由于sbegin变量为true而开始运行...如此这般就完成了一轮循环。
保证种循环执行50次的控制,就在于每个线程自己的for循环,他们其实每次大循环中,各自执行了两遍自己的循环打印方法,所以
他们每个线程执行50*2=100次循环打印方法就可以了。

结果(图片太长只截取了一次循环的某三步):

后来,我用参数计数得出一共循环了50次的大循环,结果是正确的。

但是我个人认为我只是单纯的实现了这种效果,代码拓展新不好,写法有点屌丝,
控制线程运行和阻塞的方法也不是太好(用了两个全局变量mbegin和sbegin)=_=。

下面看一下人家提供的一种答案吧:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
package cn.edu.hpu.test;

public class ThreadTest4 {

public static void main(String[] args) {
new ThreadTest4().init();
}

public void init()
{
final Business business = new Business();
new Thread(
new Runnable()
{

public void run() {
for(int i=0;i<50;i++)
{
business.SubThread(i);
}
}

}

).start();

for(int i=0;i<50;i++)
{
business.MainThread(i);
}
}

private class Business
{
boolean bShouldSub = true;//这里相当于定义了控制该谁执行的一个信号灯
public synchronized void MainThread(int i)
{
if(bShouldSub)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

for(int j=1;j<=100;j++)
{
System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
}
bShouldSub = true;
this.notify();

}

public synchronized void SubThread(int i)
{
if(!bShouldSub)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

for(int j=1;j<=10;j++)
{
System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
}
bShouldSub = false;
this.notify();
}
}
}

结果(也是图片太长只截取了一次循环的某三步):

实际上答案给出的方法和我自己写的差不多,也是创建一个共用的类去循环打印数据,并分别给
子线程和主线程提供一个循环打印的方法,和我不同的是,他们使用的是同一个共用变量去控制
线程的启动和阻塞,而我使用了两个,而且答案的代码格式和命名都比较规范,这一点要改进。

另外,除了使用以上方法外,还提供了一个使用JDK5的“并法库”的方法来解决此问题:
[java] view plain copy 在CODE上查看代码片派生到我的代码片
package cn.edu.hpu.test;

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

public class ThreadTest5 www.zhenlyule.cn
{
private static Lock lock = new ReentrantLock();
private static Condition subThreadCondition = lock.newCondition();
private static boolean bBhouldSubThread = www.yyzx66.cn/ false;
public static void main(String [] args)
{
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.execute(new Runnable(){
public void www.egouyuLe.cn run()
{
for(int i=0;i<50;i++)
{
lock.lock();
try
{
if(!bBhouldSubThread)
subThreadCondition.await();
forwww.yinb666.cn (int j=0;j<10;j++)
{
System.out.println(Thread.currentThread().getName() + ",j=" + j);
}
bBhouldSubThread = false;
subThreadCondition.signal();
}catch(Exception e)
{
}
finally
{
lock.unlock();
}
}
}

});
threadPool.shutdown();
for(int i=0;i<50;i++)
{
lock.lock();
try
{
if(bBhouldSubThread)
subThreadCondition.await();
for(int j=0;j<10;j++)
{
System.out.www.huarenc88.cn/ println(Thread.currentThread().getName() + ",j=" + j);
}
bBhouldSubThread = true;
subThreadCondition.signal();
}catch(Exception e)
{
}
finally
{
lock.unlock();
}
}
}
}

至此,我们通过完成该面试题,可以充分理解什么是线程同步通信,其实就是
线程在同一时间运行,然后通过一些变量或手段去让线程之间进行相互交替的运行,
来达到我们使用多线程的目的。

【Java多线程与并发库】4.传统线程同步通信技术的更多相关文章

  1. Java并发基础05. 传统线程同步通信技术

    先看一个问题: 有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次--如此往返执行50次. 看完这个问题,很明显要用到线程间的通信了, 先分析一下思路:首 ...

  2. Java多线程与并发库高级应用-线程池

    线程池 线程池的思想  线程池的概念与Executors类的应用 > 创建固定大小的线程池 > 创建缓存线程池 > 创建单一线程池(如何实现线程死掉后重新启动?) 关闭线程池 > ...

  3. Java多线程与并发库高级应用-传统线程机制回顾

    1.传统线程机制的回顾 1.1创建线程的两种传统方式 在Thread子类覆盖的run方法中编写运行代码 // 1.使用子类,把代码放到子类的run()中运行 Thread thread = new T ...

  4. Java多线程与并发库高级应用-传统线程同步通信技术

    面试题: 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着又 主线程循环100次,如此循环50次,请写出程序 /** * 子线程循环10次,接着主线程循环100次,接着又回到 ...

  5. Java多线程与并发库高级应用-java5线程并发库

    java5 中的线程并发库 主要在java.util.concurrent包中 还有 java.util.concurrent.atomic子包和java.util.concurrent.lock子包 ...

  6. Java多线程与并发库高级应用-工具类介绍

    java.util.concurrent.Lock 1.Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互 ...

  7. Java多线程与并发库高级应用-Callable与Future的应用

    Callable这种任务可以返回结果,返回的结果可以由Future去拿 >Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的. >Completion ...

  8. Java多线程与并发库高级应用-传统线程互斥技术

     线程安全问题: 多个线程操作同一份数据的时候,有可能会出现线程安全问题.可以用银行转账来解释. 模拟线程安全问题 /** * 启动两个线程分别打印两个名字,名字按照字符一个一个打印 * * @aut ...

  9. Java多线程与并发库高级应用-传统定时器技术回顾

    传统定时器技术回顾(jdk1.5以前) public class TraditionalTimerTest { static int count = 0; public static void mai ...

随机推荐

  1. 常用js效果:选项卡切换

    js选项卡,很多网站都会用到,我这里用jquery给整了一个简单但是却很实用的js选项卡,废话不多说,直接上代码: <style> .txtadsblk01{ width:200px;} ...

  2. 第十一章、认识与学习 BASH Bash Shell 的操作环境

    bash中的变量动不动就说环境变量,真是奇怪,bash只是一个c语言编写的程序而已,跟环境变量有什么关系?如果知道dos的历史的话就知道有个时代是只有命令行界面而没有图形用户界面,这只小小的程序就包揽 ...

  3. LigerUi中的Grid中不分页显示(local)!

    LigerUi中的Grid中不分页显示! grid为local usePager: true,                         //是否分页

  4. 常用对象API、附加:集合补充

    基本数据类型对象包装类: 为了方便操作基本数据类型值,将其封装成了对象,在对象中定义了属性和行为丰富了该数据的操作. 用于描述该对象的类就称为基本数据类型对象包装类. byte——Byte short ...

  5. composer 常用命令

    composer list 列出所有可用的命令 composer init 初始化 composer.json文件(就不劳我们自己费力创建啦),会要求输入一些信息来描述我们当前的项目,还会要求输入依赖 ...

  6. Android解析qq聊天记录表情

    偶然在一个需求中需要解析qq聊天记录表情,表情的格式是以/开始,比如:你好啊?/微笑,在网上找了半天,也没能找到一个比较合适的,所以准备自己实现一下,首先要将表情图片和表情字符对上号,我想了几种解决方 ...

  7. 遮罩层的实现(纯js兼容版)

    这个代码是我以前测试时候的代码了,主要用到的知识点是opacity和filer分别实现 “标准浏览器”和IE浏览器下的半透明,使用js的document.body.offsetwidth 和scree ...

  8. HTML5实践之歌词同步播放器

    歌曲播放我们会发现他的兼容性不是很好,譬如IE上能播放的flash播放器,再firfox或者chrome上就不是很好的应用了,因为有插件的阻碍!HTML5的出现让这一切成为了可能,但是播放器虽然播放了 ...

  9. svn 上传 过滤

    代码上传过程中发现.so文件不能上传,查了一下,发现是svn服务器要设置上传过滤:很多文件,会被过滤掉,不能正常上传.设置如下: 通过终端打开配置文件: open ~/.subversion/conf ...

  10. POJ 3614 Sunscreen 优先队列 贪心

    题意 有C个奶牛去晒太阳 (1 <=C <= 2500),每个奶牛各自能够忍受的阳光强度有一个最小值和一个最大值,太大就晒伤了,太小奶牛没感觉. 而刚开始的阳光的强度非常大,奶牛都承受不住 ...