java毕向东学习笔记——day12

来源:互联网 发布:淘宝一淘怎么没有了 编辑:程序博客网 时间:2024/05/18 01:21

一 多线程通讯

  1. 多线程通讯的意思其实很简单,就是不同于以往的两个线程做同样的事情操作同一个对象——比如day11的火车售票,而是多个线程执行对于同一个对象执行不同的事情,比如说今天的对同一个对象中的数据,一个线程负责输入,一个线程负责输出;
  2. 多线程通讯由于不同线程做的事情不同,因此需要多个对象实现Runnable接口并重写run方法;
  3. 多线程应当注意的仍然是安全问题,因此在需要同步的地方慎重判断,本例由于操作的还是同一个对象中的数据,因此两个run方法所在的Input和Output的同步函数可以用操作对象当做锁,从而实现同一把锁,避免安全隐患;
  4. 今天的代码中要实现的是输入数据,而后立刻输出,因此引入了flag进行判断输入的数据是否已经输出,以及wait方法和notify方法用于暂停/唤醒——输入/输出线程;
  5. 如果数据已经输出则输入数据,如果数据未输出则暂停当前的输入线程,唤醒输出线程,转到输出线程进行输出,在输出线程中判断数据是否已经输出,如果没有,则输出数据,如果已经输出,则暂停输出线程,唤醒输入线程进行输入;
  6. 以上的话过于繁琐,还是看两个版本的代码来的快。。。
/*    输入一个姓名和性别,然后用另一个线程输出*/class Resource{    String name;    String sex;    boolean flag = false; //用来作为判定输入是否输出的标识}class Input implements Runnable{    private Resource res;    Input(Resource res){        this.res = res;    }    public void run(){        int x = 0;        while(true){            //两个线程用同一个对象锁            synchronized(res){                if (res.flag)                    try{                        res.wait();                    }catch(InterruptedException e){                    }                if(x==0){                    res.name = "张三";                    res.sex = "男";                }else {                    res.name = "WWW";                    res.sex = "woman";                }                x = (x+1)%2;                res.flag = true;                res.notify();            }           }    }}class Output implements Runnable{    private Resource res;    Output(Resource res){        this.res = res;    }    public void run(){        while(true){            //两个线程用同一个对象锁            synchronized(res){                if(!res.flag)                    try{                        res.wait();                    }catch(InterruptedException e){                    }                System.out.println(res.name+"......"+res.sex);                res.flag = false;                res.notify();            }        }    }}class ThreadCommunicationDemo{    public static void main(String[] args){        Resource r = new Resource();        Input in = new Input(r);        Output out = new Output(r);        Thread t1 = new Thread(in);        Thread t2 = new Thread(out);        t1.start();        t2.start();    }}

下面的是优化版本

/**    用一个线程输入姓名和性别,用另一个线程输出姓名和性别的    优化版本!    @author zck;    @version V1.1;*/class Resource{    private String name;    private String sex;    private boolean flag = false; //用来作为判定输入是否已经输出的标识    public synchronized void setNameSex(String name,String sex){        if (flag){            try{                this.wait();            }catch(InterruptedException e){            }        }           this.name = name;        this.sex = sex;        flag = true;        notify();    }    public synchronized void getNameSex(){        if (!flag){            try{                this.wait();            }catch(InterruptedException e){            }        }        System.out.println(this.name+"......"+this.sex);        flag = false;        notify();    }}class Input implements Runnable{    private Resource res;    Input(Resource res){        this.res = res;    }    public void run(){        int x = 0;        while(true){                if(x==0){                    res.setNameSex("张三","男");                }else {                    res.setNameSex("WWW","woman");                }                x = (x+1)%2;        }       }}class Output implements Runnable{    private Resource res;    Output(Resource res){        this.res = res;    }    public void run(){        while(true){                res.getNameSex();        }    }}class ThreadCommunicationDemo2{                //优化版本    public static void main(String[] args){        Resource r = new Resource();        new Thread(new Input(r)).start();        new Thread(new Output(r)).start();    }}

二 多线程通讯——生产者消费者

  1. 在多个线程运行时,有多个生产者,多个消费者;
  2. 在屏幕上打印输出时,有时候会出现生产两次却只消费了一次的情况,这与预期的生产一次消费一次不符合,这种安全隐患来自于flag的if判断,因为只判定一次,当生产线程1通过if判断时,执行完wait语句就失去了执行权,然后接下来消费线程1执行,然后生产线程2执行(因为此时flag是false),当生产线程2执行完时,flag变成了true,但是由于生产线程1已经在之前中通过了if判断,当生产线程1获得执行权时,就会再生产一次;
    打印结果:
    生产商品1
    消费商品1
    生产商品2
    生产商品3
    消费商品3
    改进方法就是将if换成while判断

  3. 还有一种安全隐患就是notify叫醒的线程池里本类线程,就会出现叫醒生产线程1叫醒生产线程2,然后所有线程都挂了(都进入wait状态);处理方式就是改notify为notifyAll,干脆全叫醒,就不会出现所有线程一起挂在那儿的情况了;

class ProducerConsumerDemo{    public static void main(String[] args){        ProducerConsumer pc = new ProducerConsumer();        Producer pro = new Producer(pc);        Consumer con = new Consumer(pc);        Thread t1 = new Thread(pro);        Thread t2 = new Thread(pro);        Thread t3 = new Thread(con);        Thread t4 = new Thread(con);        t1.start();        t2.start();        t3.start();        t4.start();    }}class ProducerConsumer{    private String name;    private int count = 1;    private boolean flag = false;    public synchronized void setName(String name){        while(true){            while(flag){                try{                    wait();                }catch(InterruptedException e){                }            }            this.name = name+"..."+count++;            System.out.println(Thread.currentThread().getName()+"---生产---"+this.name);            flag = true;            this.notifyAll();        }       }    public synchronized void getName(){        while(true){                while(!flag){                try{                    wait();                }catch(InterruptedException e){                }            }            System.out.println(Thread.currentThread().getName()+"---消费-----"+this.name);            flag = false;            this.notifyAll();        }       }}class Producer implements Runnable{    ProducerConsumer pc;    Producer(ProducerConsumer pc){        this.pc = pc;    }    public void run(){        pc.setName("商品");    }}class Consumer implements Runnable{    ProducerConsumer pc;    Consumer(ProducerConsumer pc){        this.pc = pc;    }    public void run(){        pc.getName();    }}

下面是java5.0改进版本
1. 引入了lock代替synchronized,condition对象的await代替wait,signal和signalAll代替notify和notifyAll;

import java.util.concurrent.locks.*;class ProducerConsumerDemo2{    public static void main(String[] args){        ProducerConsumer pc = new ProducerConsumer();        Producer pro = new Producer(pc);        Consumer con = new Consumer(pc);        Thread t1 = new Thread(pro);        Thread t2 = new Thread(pro);        Thread t3 = new Thread(con);        Thread t4 = new Thread(con);        t1.start();        t2.start();        t3.start();        t4.start();    }}class ProducerConsumer{    private String name;    private int count = 1;    private boolean flag = false;    private Lock lock = new ReentrantLock();    private Condition condition_pro = lock.newCondition();    private Condition condition_con = lock.newCondition();    public void setName(String name) throws InterruptedException{        lock.lock();        try{            while(flag){                condition_pro.await();            }            this.name = name+"..."+count++;            System.out.println(Thread.currentThread().getName()+"---生产---"+this.name);            flag = true;            condition_con.signalAll();        }finally{                   lock.unlock();        }       }    public void getName() throws InterruptedException{          lock.lock();        try{            while(!flag){                condition_con.await();            }            System.out.println(Thread.currentThread().getName()+"---消费-----"+this.name);            flag = false;            condition_pro.signalAll();        }finally{            lock.unlock();        }       }}class Producer implements Runnable{    ProducerConsumer pc;    Producer(ProducerConsumer pc){        this.pc = pc;    }    public void run(){        while(true){            try{                pc.setName("商品");            }catch(InterruptedException e){            }        }       }}class Consumer implements Runnable{    ProducerConsumer pc;    Consumer(ProducerConsumer pc){        this.pc = pc;    }    public void run(){        while(true){            try{                pc.getName();            }catch(InterruptedException e){            }           }       }}

三 中断线程

  1. 有时候线程处于冻结状态后无法正常读取唤醒标记,这时候就需要外力强制中断线程的冻结状态;
  2. Thread类就提供了interrupt方法,强制唤醒线程(好比把被催眠的人一砖头砸醒),但是这样会导致wait或者sleep抛出异常(因为被砖头砸醒不比正常唤醒,会受伤的,,);
class InterruptThreadDemo{    public static void main(String[] args){        int num = 0;        TestThread tt = new TestThread();        Thread t1 = new Thread(tt);        Thread t2 = new Thread(tt);        t1.start();        t2.start();        while(true){            if(num++ == 60){                //tt.changeFlag();                //强制唤醒了t1和t2线程                t1.interrupt();                   t2.interrupt();                break;            }            System.out.println(Thread.currentThread().getName()+"..."+num);        }    }}class TestThread implements Runnable{    private boolean flag = true;    public synchronized void run(){        while(flag){            try{                wait();            }catch(InterruptedException e){                System.out.println(Thread.currentThread().getName()+"InterruptedException");                //结束循环实则为结束进程                flag = false;            }            System.out.println(Thread.currentThread().getName()+"run.....");        }    }    public void changeFlag(){        flag = false;    }}

四 守护线程

  1. 可以通过Thread 的setDaemon方法将一些线程设置为守护线程;
  2. 守护线程的设置应当在线程执行前;
  3. 守护线程可视为后台线程,当所有前台线程都结束时,守护线程自动结束;
  4. 这意味着守护线程即使被wait了,也没必要唤醒后再结束,只要前台线程跑完即可;
class InterruptThreadDemo{    public static void main(String[] args){        int num = 0;        TestThread tt = new TestThread();        Thread t1 = new Thread(tt);        Thread t2 = new Thread(tt);        t1.setDaemon(true);        t2.setDaemon(true);        t1.start();        t2.start();        while(true){            if(num++ == 60){                //tt.changeFlag();                //强制唤醒了t1和t2线程                //t1.interrupt();                   //t2.interrupt();                break;            }            System.out.println(Thread.currentThread().getName()+"..."+num);        }    }}class TestThread implements Runnable{    private boolean flag = true;    public void run(){        while(flag){            System.out.println(Thread.currentThread().getName()+"run.....");        }    }    public void changeFlag(){        flag = false;    }}

五 join、yield方法和优先级

  1. join方法实则为暂停当前线程,优先执行调用join方法的线程,直到调用join方法的线程结束后再执行当前线程;
  2. toString方法可以查看线程名称,优先级,以及所在线程组;
  3. 优先级为1——10,代表被cpu执行的概率;
  4. 通过setPriority(Thread.MAX/MIN/NORM_PRIORITY)设置三个不同的优先级,对应10/1/5;
  5. yield方法是暂停当前线程,执行其他线程;
class JoinDemo{    public static void main(String[] args) throws InterruptedException{        Join jo = new Join();        Thread t1 = new Thread(jo);        Thread t2 = new Thread(jo);        t1.start();        t1.setPriority(Thread.MAX_PRIORITY);        t2.start();        //t1.join();        for(int x = 0;x<80;x++){            System.out.println(Thread.currentThread().toString()+"....."+x);        }    }}class Join implements Runnable{    public void run(){        for (int x = 0;x<70;x++){            System.out.println(Thread.currentThread().toString()+"..."+x);            //Thread.yield();        }    }}
原创粉丝点击