【Java多线程与并发库】4.传统线程同步通信技术

本文通过一道面试题探讨了线程同步通信的实现方法。介绍了如何利用共享对象和同步锁来控制主线程与子线程的交替执行,同时对比了几种不同的实现方案。
我们先通过一道面试题来了解传统的线程同步通信。

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

我没有看答案,先用自己的思路写了一段代码,有一些是借鉴传统的“生产者与消费者”的
多线程模型写出来的:
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)=_=。

下面看一下人家提供的一种答案吧:
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的“并发库”的方法来解决此问题:
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
{
    private static Lock lock = new ReentrantLock();
    private static Condition subThreadCondition = lock.newCondition();
    private static boolean bBhouldSubThread = false;
    public static void main(String [] args)
    {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.execute(new Runnable(){
            public void run()
            {
                for(int i=0;i<50;i++)
                {
                    lock.lock();                    
                    try
                    {                    
                        if(!bBhouldSubThread)
                            subThreadCondition.await();
                        for(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.println(Thread.currentThread().getName() + ",j=" + j);
                    }
                    bBhouldSubThread = true;
                    subThreadCondition.signal();                    
                }catch(Exception e)
                {                        
                }
                finally
                {
                    lock.unlock();
                }                    
        }
    }
}

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

来达到我们使用多线程的目的。

转载请注明出处:http://blog.csdn.net/acmman/article/details/52774603

提供了一个详细的MATLAB仿真程序,用于实现自回归(AR)模型的功率谱估计。该程序基于经典的数字信号处理教材——《数字信号处理理论、算法实现》第三版中的相关内容(第545-547页),旨在帮助学习者理解和实践AR模型在功率谱估计中的应用。 简介 AR模型是一种常用的时间序列分析方法,通过建立当前值其过去值之间的线性关系来描述时间序列的动态特性。功率谱估计是信号处理中的关键环节,用于揭示信号频率成分的分布。本仿真通过自相关方法实现AR模型参数的估计,并进而计算信号的功率谱。 特点 含详细注释:代码中添加了丰富的注释,便于初学者理解每一步的计算逻辑和目的。 参数可调:用户可根据需要调整AR模型的阶数(p值)、信号长度等参数,以适应不同的信号分析需求。 理论联系实际:通过将书本知识转化为实践操作,加深对AR模型及其在功率谱估计中应用的理解。 使用说明 环境要求:确保你的计算机上已安装MATLAB,并且版本适合运行提供的脚本。 加载脚本:将提供的MATLAB文件导入到MATLAB的工作环境中。 修改配置:根据需要修改代码中的参数配置,如AR模型的阶数等。 运行仿真:执行脚本,观察并分析输出结果,包括自回归模型的系数以及估算出的功率谱。 学习分析:通过对比不同参数下的结果,深入理解AR模型在功率谱估计中的行为。 注意事项 在使用过程中,可能需要基础的数字信号处理知识以便更好地理解代码背后的数学原理。 请确保你的MATLAB环境已正确设置,能够支持脚本中的所有函数和运算。 结论 此资源对于研究信号处理、通信工程或是进行相关学术研究的学生和科研人员来说是一个宝贵的工具。它不仅提供了理论知识的具体实现,也是提升实践技能的优秀案例。通过动手操作,你将更加熟练地掌握AR模型及其在功率谱估计中的应用技巧。 开始探索,深入了解AR模型的力量,解开信号隐藏的秘密吧!
提供了关于时间序列分析预测的宝贵资源,特别聚焦于**自回归积分滑动平均模型(ARIMA)**及其应用。对于那些希望深入理解并实践时间序列建模的学者、研究人员以及数据分析爱好者来说,这是一个不可或缺的学习材料。本资源不仅包括了详细的理论讲解,涵盖了时间序列分析的基础,如移动平均(MA)、自回归(AR)、指数平滑等关键概念,而且通过具体的ARIMA模型解析,搭配MATLAB编程实现实例,帮助用户从理论到实践全面掌握这一重要统计工具。 内容概览 理论讲解: 深入浅出地介绍了时间序列分析的基本原理,重点阐述ARIMA模型的构建步骤,包括如何识别模型的参数(p,d,q),以及其在处理非平稳数据中的作用。 MATLAB代码实现: 提供了多个ARIMA模型的MATLAB实现示例,这些代码覆盖了从数据准备、模型拟合、诊断检验到预测的全过程,是学习如何利用MATLAB进行时间序列分析的实用工具。 实例分析: 包括不同行业或领域的实际案例研究,展示如何应用ARIMA及其它时间序列方法解决真实世界的数据预测问题,增强理解和应用能力。 文件结构 时间序列模型ARIMA的讲解matlab代码实现(含多个实例).rar: 主要资源压缩包,解压后包含文档和MATLAB代码文件夹。 文档: 提供了理论知识讲解。 MATLAB代码: 实现了文中讨论的各种模型,附带注释,便于理解修改。 使用指南 下载资源: 点击下载“时间序列模型ARIMA的讲解matlab代码实现(含多个实例).rar”文件。 解压文件: 解压缩至本地,确保你可以访问文档和代码。 环境准备: 确保你的电脑上已安装MATLAB,并熟悉基本操作。 学习流程: 首先阅读文档理解时间序列分析的理论基础,然后逐步跟随MATLAB代码示例进行实践。 实践应用: 尝试将所学应用到自己的数据集上,调整参数以优化模型性能。 注意事项 请根据M
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光仔December

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值