JAVA线程交互

第一节 JAVA线程中止的错误方法

stop方法,no stop 这不是正确的方法,会让咱们的程序戛然而止,会使咱们不知道哪些工做没作,完成了什么任务以及没有机会去作清理工做。使用的结果会形成程序忽然中止,强行关闭,有时一个循环可能都没作完。
JAVA中止线程的正确作法—设置退出旗标,使用退出标志来中止线程,如以前的程序先设置一个布尔类型的值,volatile类型来保证每次都能读取到它的值,赋值false来退出线程。
JAVA中止线程广为流传的错误方法—interrupt方法,interrupt初衷并非中止咱们的线程。
查询JAVA API文档,在java.lang包下,找到Thread,Ctrl+F找到interrupt(),找到三个。
interrupt() 中断线程
interrupted() 测试当前线程是否已经中断,注意这个方法是静态方法。
isInterrupted() 测试线程是否已经中断。后者两个方法返回的值都是布尔值。
在API中咱们看到:若是线程在调用Object类的wait()、wait(long)或wait(long, int)方法,或者该类的join()、join(long)、join(long, int)、sleep(long)或 sleep(long, int)方法过程当中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
在这里首先咱们看到API中interrupt()方法中断线程是有条件的,在API中提示若是之前的条件都没有保存,才会将该线程的中断状态设置。此时调用后面的interrupted()或者isInterrupted()将返回一个布尔值的变量true来表示线程被中断。
若是使用了join方法或者sleep方法使得线程阻塞中断的状况下,使用interrupet会使得线程的中断状态被清除,而且当前的线程将会收到一个InterruptedException,这表明如后面再调用interrupted或者isInterrupted方法将不会获得一个正确的值。这就是为何咱们在使用join方法或者sleep方法须要用try-catch语句包围来捕获这个InterruptedException异常的缘由。在使用join或者sleep方法时,一旦其它或当前线程调用了interrupted()方法,它将会收到一个异常。这些被阻塞的线程由于某些缘由须要被唤醒,好比外部发生了中断,它须要响应,这时它就经过抛出异常的方式来使咱们有机会作出一些响应。因此interrupt并不能正确的中止咱们的线程。java

下面使用代码来实际演示一下:编程

/**
 * Created by Administrator on 2017/4/7.
 */
public class WrongWayStopThread extends Thread {
    public void run(){
        while(!this.isInterrupted()){
            //!this.isInterrupted() 一开始使用的是true,线程中止不了,更换后
            //正确退出线程,此方法和设置退出标志法同样,只是退出旗标比较特殊
            //线程是否被中断的一个状态
            System.out.println("Thread is running...");
            //获取当前系统时间的一个毫秒值
            long time=System.currentTimeMillis();
            //long time1=System.nanoTime();这个精度到纳秒,
            //比上面currentTimeMillis()毫秒精度高
            //while((System.currentTimeMillis()-time)<1000){
                //减小咱们的屏幕输出,大概等于sleep休眠1000毫秒
                //也就是每秒中输出一条信息("Thread is running..."
                // 这里为何没有使用sleep呢?
            //}
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //使用sleep方法运行结果,抛出异常,程序没有正确结束,
            // sleep方法使线程进入一种阻塞状态之时,此时若是这个//
            // 线程再被调用interrupt方法,它会产生两个结果,
            // 1是它的中断状态被清除,线程的isInterrupted就不能
            // 返回一个表示是否被中断的正确状态,那么这里的while
            // 就不能正确的退出了。
            //2第二个结果就是收到一个InterruptedException异常代表
            // 它被中断了
        }
    }
    public static void main(String[] args) {
        WrongWayStopThread thread=new WrongWayStopThread();
        //新建一个线程 thread。这里父类子类引用效果应该是同样的吧?先照
        //示例演示一遍以后再更换成 Thread类型尝试下
        System.out.println("Starting thread...");
        //启动线程
        thread.start();
        //休眠3秒钟而且用try-catch语句块包围
        try {
           Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Interrupting thread...");
        //用 interrupt方法中断线程,注意前面调用了sleep方法阻塞中断线程3秒钟
        thread.interrupt();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Stopping application");
    }
}

第二节 JAVA线程交互之汽车人之忧:消失的能量

程序的任务是创建一个能量盒子的数组,初始值里数组里每一个盒子是一个固定的初始值总能量也固定,而后互相传递能量。在线程实现后有部分状况下能量出现丢失。
先创建一个宇宙的能量系统
1.定义了一个私有的数组常量,而且在初始化构造方法中,先用一个参数n表示数组的长度,再利用for循环把数组每一个值都设置为统一固定的参数值。
2.定义了一个能量传递的方法,参数为(传递初的位置下标from,传递后的终点位置的下标to,传递的能量值),方法体第一句表示若是最初的这个盒子能量不知足本次传递则终止本次转出,if(条件),return;而后是方法体内的公式,数组[from]-=传递的能量值,能量转出后对方要数组[to]+=传递的能量值。中间插入输出语句,用printf格式占位输出。注意只是传递了一次。
System.out.printf("从%d转移到%10.2f单位能量到%d",from,amount,to);%d对应咱们参数中的from,%d表明输出一个整数值,%10.2f表明咱们输出一个浮点数,而且它的小数部分是两位,整数点以前的部分应该有十位。System.out.printf("能量总和:%10.2f%n",getTotalEnergies());咱们这里的%n则相似咱们的转义字符反斜杠n /n 表明了一个回车换行符。
3.定义一个方法这里咱们须要获取这个能量世界的总和。利用for循环遍历其中的数组元素并设立一个变量初始值为0,累计相加最后得出并输出一个总和
4.设置一个方法直接返回当前数组的长度.length数组

/**
 * Created by Administrator on 2017/4/7.
 * 宇宙的能量系统
 * 遵循能量守恒的定律
 * 能量不会凭空消失,只会从一处转移到另外一处
 */
public class EnergySystem {
    //能量盒子的总和,贮存能量的地方,每一个盒子存储了必定的能量
    //全部的盒子能量之和表明宇宙的总能量
    private final double[] energyBoxes;

    /**
     *
     * @param n 表明了能量盒子的数量
     * @param initialEnergy 每一个能量盒子初始含有的能量值
     */
    public EnergySystem(int n,double initialEnergy) {
        this.energyBoxes = new double[n];
        for(int i=0;i<energyBoxes.length;i++){
            energyBoxes[i]=initialEnergy;
        }
    }
    //这个构造方法会使这个double数组一开始全部的元素值相等,
    // 总和=n*initialEnergy,数组构造仍是第一次见

    /**
     *能量的转移,从一个盒子到另外一个盒子
     * @param from 能量源
     * @param to 能量终点
     * @param amount 转移的能量值
     */
    public void transfer(int from,int to,double amount){
        //能量转出的值不足以知足本次能量转出时,咱们则会终止本次转出
        if(energyBoxes[from]<amount)
            return;
        //这个return指的是符合条件则跳出当前方法
        System.out.print(Thread.currentThread().getName());
        energyBoxes[from]-=amount;
        System.out.printf("从%d转移到%10.2f单位能量到%d",from,amount,to);
        //printf格式输出,它利用特别的字符来占位,而后在咱们后面的输出列表
        // 之中,对应其真实的值,例如%d对应咱们参数中的from,%d表明输出一个
        // 整数值,%10.2f表明咱们输出一个浮点数,而且它的小数部分是两位,
        // 整数点以前的部分应该有十位。
        energyBoxes[to]+=amount;
        System.out.printf("能量总和:%10.2f%n",getTotalEnergies());
        //咱们这里的%n则相似咱们的转义字符反斜杠n /n 表明了一个回车换行符
    }
    /**
     * 获取能量世界的能量总和
     */
    public double getTotalEnergies(){
        double sum=0;
        for (double amount:energyBoxes){
            sum+=amount;
        }
        return sum;
    }
    /**
     * 返回能量合子的长度
     */
    public int getBoxAmount(){
        return energyBoxes.length;
    }
}

上一个类只是一个宇宙能量系统的类, 提供了能量传递的方法和计算总能量和返回数组长度的方法,定义了能量盒子数组。第二个类是咱们的线程任务类,能量传递任务,一共设置了四个属性,第一个是EnergySsytem,由于只使用一个能量世界,定义了一个EnergySystem能量系统的属性,而且把对象当作构造方法参数传递进去。
定义了一个能量转移的盒子下标 fromBox,这个应该不能超过数组的长度,由于下标从0开始,0-长度-1,还有每次能够转移的最大能量值,设置了一个休眠时间,输入一个带参构造方法,复写run方法,能量传递的值和toBox,目的盒子的下标,二者都是随机数。但不能超过最大值,故选择最大值Math.random()方法,等于或大于0.0,小于1.0之间的随机数,调取传递方法后休眠Math.random()随机时常,whiletrue循环,注意try块把try放在循环体外安全

package energysystem;

/**
 * Created by Administrator on 2017/4/7.
 */
public class EnergyTransferTask implements Runnable{
    //共享的能量世界
    private EnergySystem energySystem;
    //能量转移的源能量盒子下标
    private int fromBox;
    //单词能量转移最大单元
    private double maxAmount;
    //最大休眠时间毫秒
    private int DELAY=10;

    public EnergyTransferTask(EnergySystem energySystem,int from,double max){
        this.energySystem=energySystem;
        this.fromBox=from;
        this.maxAmount=max;
    }
    @Override
    public void run() {
        try {
            while(true){
                int toBox=(int)(energySystem.getBoxAmount()*Math.random());
                //Math.random()是令系统随机选取大于等于 0.0 
且小于 1.0 的伪随机 double 值
                //长度假如说是10*0.9=9,由于不会等于1,因此不管如何也不会超过限度。
                double amount=maxAmount*Math.random();
                energySystem.transfer(fromBox,toBox,amount);
                Thread.sleep((int)(DELAY*Math.random()));
                }
            }catch (InterruptedException e) {
            //注意把try块try的开头移到循环体以外,在内部的话若是有异常会在循环后来回
            // 抛出,容易形成内存溢出,在外面美观好用
                e.printStackTrace();
            }
        }
    }

运行类多线程

package energysystem;

/**
 * Created by Administrator on 2017/4/7.
 */
public class EnergySystemTest {
    //将要构建的能量世界中能量盒子数量
    public static final int BOX_AMOUNT=100;
    //每一个盒子的初始能量
    public static final double INITIAL_ENERGY=1000;

    public static void main(String[] args) {
        EnergySystem eng=new EnergySystem(BOX_AMOUNT,INITIAL_ENERGY);
        for(int i=0;i<BOX_AMOUNT;i++){
            EnergyTransferTask task=new 
EnergyTransferTask(eng,i,INITIAL_ENERGY);
            Thread t=new Thread(task,"TransferThread_"+i);
            t.start();
        }
    }
}

第三节 JAVA线程之争用条件

上一节程序最后的结果是在不断的传递能量过程当中,总能量出现减小,而出现这种状况就是因为咱们的争用条件问题形成的。
什么是争用条件呢?
利用一个生活化的例子,女神每每受到不少追求者青睐,而女神最后只能选择一个,当她同时答应多我的的追求时,将难免发生流血事件。同理,把女神比做数据(内存区域)
当多个线程同时共享访问同一数据(内存区域)时,每一个线程都尝试操做该数据,从而致使数据被破坏,这种现象咱们就称之为争用条件。
以上面的代码为例,线程1和线程2共享了同一个能量转移目标,同时咱们知道,在同一时间只能有一个线程在cpu上运行,而线程之间的调度则是经过分时和抢度完成的。线程一转入500,线程二转入900,因为争用条件最后赋值仍是线程1转入的5500
并发

第四节 能量守恒:互斥和同步

互斥:相互排斥,在同一时间里只能有一条线程去对咱们的关键数据或者临界区进行操做,同步:归根结底,就是线程之间的一种通讯机制。一条线程执行完了某项任务,它会以某种方式告知其它线程,我作完了。在这里咱们要提到关键字 synchronized ,还有对象的.wait 和notifyAll()。新增添一个常量类型是Object对象,做为咱们的线程锁app

private final Object lockObj=new Object();
//定义了一个常量,这个是咱们的锁对象

再去程序中修改一下能量转移的方法dom

public void transfer(int from,int to,double amount){
        //能量转出的值不足以知足本次能量转出时,咱们则会终止本次转出
        //synchronized既能够出现于咱们的方法体之上也能够出如今咱们的方法体之中
        //把这个不知足能量退出的方法加入synchronized修改一下。
        //if(energyBoxes[from]<amount)
        // return;
        //这个return指的是符合条件则跳出当前方法
        synchronized(lockObj){
            //这里的this一开始填的lockObj,经过对对象的加锁来实现咱们的互斥行为,
            //已知加锁必须是一个共享且惟一的,后面咱们把该对象做为参数传递
            //入线程,此时是共享一个对象了
            //if(energyBoxes[from]<amount)
                //return;
            //上面这个方法表示不知足转出则程序退出,如今想一想退出以后咱们的这段
            // 线程能然有机会去获取CPU资源,从而再次要求进行加锁,而咱们的加锁
            // 操做是有开销的,(以前的话不须要加锁,大不了多试几回退出几回)
            // 这样会下降咱们系统的性能。那么好的办法是什么呢?
            // 当咱们发现条件不知足时,这时咱们应该让线程去等待某些条件的发生
            // 从而下降这个线程去获取锁的开销,提升咱们总体的性能。
            //while循环,保证条件不知足时任务都会被条件阻挡,而不是去竞争咱们
            // 的CPU资源
            while(energyBoxes[from]<amount){
                //当咱们发现线程不知足某些条件时,应该将线程阻挡在咱们业务逻辑
                // 以前
                try {
                    lockObj.wait();
                    //wait Set等待集合
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //这会使咱们的线程进入等待状态,而避免了咱们的线程去持续的申请锁
            }

            System.out.print(Thread.currentThread().getName());
            energyBoxes[from]-=amount;
            System.out.printf("从%d转移到%10.2f单位能量到%d",from,amount,to);
        //printf格式输出,它利用特别的字符来占位,而后在咱们后面的输出列表
        // 之中,对应其真实的值,例如%d对应咱们参数中的from,%d表明输出一个
        // 整数值,%10.2f表明咱们输出一个浮点数,而且它的小数部分是两位,
        // 整数点以前的部分应该有十位。
            energyBoxes[to]+=amount;
            System.out.printf("能量总和:%10.2f%n",getTotalEnergies());
            lockObj.notifyAll();
        //咱们这里的%n则相似咱们的转义字符反斜杠n /n 表明了一个回车换行符

        //这里咱们应该通知被阻挡的线程,告诉被等待的线程,条件发生了变化,他们
        //有可能能被执行了
        //唤醒全部lockObj对象上等待的线程。
        }
    }

此时再运行咱们的程序,能量就守恒了。ide

第五节 深度剖析

什么是互斥,互斥是怎么实现的?互斥也就是说关键数据在同一时间只能被一个线程所访问。互斥的实现 synchronized(intrinsic lock) 单词含义固有的锁,synchronized(this)至关于给咱们的代码加一把锁,使得其余线程不能进咱们这个关键区域去访问关键资源。只有得到了(this)锁的线程可以进入这个核心区域。java的语法保证了同一时间,只能有一条线程得到咱们的线程锁。上面的例子就是新建了一个Object对象做为锁,感受换成this也彻底能够实行,由于共享了一个能量世界,锁住的方法也是这个类的方法。
同步的实现 wait() notify() notifyAll();这三个方法属于object对象而不是thread对象,object对象的成员函数。如以前的代码:函数

lockObj.wait();
lockObj.notifyAll();

当能量不知足不能转移时,调用对象的wait方法让该线程进入wait set等待的集合,而后继续下一个线程,当其余线程执行完毕唤醒全部等待线程。(当有线程执行完,使以前的线程能量获得知足,拥有足够的能量能够知足本次转移时,继续执行。)注意这里的wait和notifyAll不是同一个线程哦。同步是两个线程之间的交互。wait set,是线程休息室。critical section 咱们的共享资源共享数据又被称为临界区,当有一个线程访问咱们的资源时,首先它须要得到一把锁,当它得到了锁它将进入咱们的临界区操做,操做过程当中它发现某些状况不被知足,好比能量不知足转移。它将调用咱们锁对象上的wait方法,此时这个线程首先释放掉咱们的锁资源,而后进入咱们的锁对象上的wait set。因为这个线程释放掉了咱们的锁资源,可让其余线程来竞争咱们的锁资源。因此咱们看到其余线程得到锁并进入了咱们的临界区,同时咱们看到waitset中有多条线程在等待条件的知足。当咱们当前线程执行完某些操做,须要通知等待的线程时,调用咱们的notify()方法 将会唤醒咱们锁资源所持有的等待区域中的1条线程,使这条线程有机会去竞争cpu资源。notifyAll()将唤醒全部等待的线程,去竞争临界区的锁对象。

第六节 总结及展望

在这里咱们学习了:
1.如何建立线程及线程的基本操做
2.可见性及volatile关键字实现线程的可见性编程
3.争用条件
4.线程的互斥synchronized
5.线程的同步wait/notifyAll
扩展建议
如何扩展java并发的知识:
1.Java Memory Mode
JMM描述了java线程如何经过内存进行交互,经过这部分知识咱们能够了解什么是happens-before原则,为何要使用happens-before原则,java是如何经过synchronized,volatile&final关键字来实现这一原则的。
2.另外能够看看Lockc&Condition这两个对象
这两个是java5.0后引入的是对咱们java锁机制和等待条件的高层实现,经过它咱们能够了解咱们如何对程序实现加锁以及同步的通讯,结合咱们的synchronized和wait以及notifyAll方法,你们能够更好的理解咱们的互斥与同步是如何实现的。 java.util.concurrent.locks
3.另外一部分咱们能够了解下线程的安全性
什么是原子性与可见性的问题,如何经过atomic包来避免咱们原子性编程的问题,java.util.concurrent.atomic,同时当咱们的一个原子操做由多个操做语句构成时,咱们又是如何经过synchronized的方法来实现咱们的原子型操做,相似咱们如何经过synchronized&volatile 来实现咱们的可见性编程,最后你们须要了解什么是死锁,死锁产生的条件是什么,进而能够书写出一些避免死锁发生的线程DeadLocks
4.另一方面多线程编程经常使用的交互模型
Producer-Consumer模型 经典的生产者消费者模型
Read-write Lock模型 读写锁模型
Future模型
Worker Thread模型
在了解这些模型的基础之上,你们能够考虑在咱们的java并发实现当中,有哪些类是实现了这些模型能够供咱们直接调用的。
5.最后一方面就是java5引入的一些并发编程工具
java.util.concurrent,都在这个包之下
线程池 ExecutorService
Callable&Future对象
BlockingQueue对象
大大简化咱们咱们以前的线程编程模型,使咱们能够更加方便的使用一种面向于任务,更加接近于实际应用需求的一种抽象方式来书写咱们的线程,使咱们线程有了更大的利用空间。
最后推荐两本书 core-java 第九版。
java编程的圣经 java concurrency in practice

相关文章
相关标签/搜索