多线程

来源:互联网 发布:linux 重定向 2 1 编辑:程序博客网 时间:2024/06/06 20:58

线程基础

  1. 线程的五种状态

    1. 创建(New):使用new创建一个线程时的状态,尚未启动。
    2. 就绪(Runnable):调用start()启动线程,加入就绪队列,等待被调度进CPU运行
    3. 运行(Run):处于就绪状态的线程获取了cpu的执行权
    4. 阻塞(Block):由于某些原因该线程放弃了cpu的使用权。停止执行。除非线程进入可运行的状态,才会有机会获取cpu的使用权。
      • 等待阻塞:运行中的线程执行wait方法,这时候该线程会被放入等待队列。
      • 同步阻塞:运行中的线程获取同步锁,如果该同步锁被别的线程占用,这个线程会成被放入锁池,等待其他线程释放同步锁。
      • 其他阻塞:运行的线程执行sleep或者join方法这个线程会成为阻塞状态。当sleep超时,join等待线程终止,该线程会进入可运行状态。
    5. 死亡(Dead):线程run mian 执行完毕后,或者因为某些异常产生退出了 run 方法,该线程的生命周期结束。
  2. 线程状态转换图
    状态转换

  3. 线程有关方法

    1. sleep(time):使线程进入阻塞状态,但不释放锁,睡眠时间发到后自动进入就绪状态。
      作用:给其他线程执行机会。
    2. wait():使线程进入阻塞状态,释放锁,等到被其他线程notify()时进入就绪状态。
    3. interrupt():停止线程,调用时抛出中断异常。
    4. setDaemon(true):后台线程,当所有的前台线程都结束了,后台线程就结束。
      注意:在启动前调用。作用:为前台线程提供服务。
    5. setPriority(Thead.MAX_PRIORITY):设置线程的优先级(1-10),默认优先级为5。
    6. join():当A线程执行到B线程的join()时,A就会等待,直到B结束A才会执行。
      作用:临时加入线程,使该线程运行完,其他线程才继续。
    7. yield():使当前线程放弃分得的CPU时间片,进入就绪状态。
      作用:使同优先级或更高优先级的线程有执行机会。

继承Thread类

  1. 代码演示

    class Test extends Thread {    Test(String name)    {        super(name);    }    //run()存储线程要运行的代码    public void run()    {        for(int x = 0; x < 60;x++)        {            System.out.println(Thread.currentThread().getName()+"   run....."+x);        }    }}class  ThreadDemo1{    public static void main(String[] args)     {        Test t1 = new Test("one");        Test t2 = new Test("two");        t1.start();        t2.start();    }}
  2. 存在的不足

    1. 子类无法多继承。
    2. 不适合资源共享。

实现Runnable接口

  1. 代码演示

    class Ticket implements Runnable{    private int tick = 100;    public void run()    {        while(true)        {            if(tick>0)            {                System.out.println(Thread.currentThread().getName()+"sale:"+tick--);            }        }    }}class  ThreadDemo3{    public static void main(String[] args)     {        Ticket t  = new Ticket();        //创建线程的同时明确线程要运行的代码        Thread t1 = new Thread(t);        Thread t2 = new Thread(t);        Thread t3 = new Thread(t);        Thread t4 = new Thread(t);        //启动线程,使线程处于就绪状态,等待被调度        t1.start();        t2.start();        t3.start();        t4.start();    }}
  2. 注意:实现Runnable接口的类不是线程类。创建线程对象的是Thread或Thread的子类。

多线程的安全问题

  1. 代码演示安全问题

    class Ticket implements Runnable{    //线程的共享数据    private int tick = 100;    public void run()    {        while(true)        {            if(tick>0)            {                //为了演示可能出现的问题,让当前线程释放执行权                try{Thread.sleep(10);} catch(Exception e){}                System.out.println(Thread.currentThread().getName()+"sale:"+tick--);            }        }    }}

    当多线程用到共享资源时,由于CPU的时间片特性,不管run()有没有执行完毕,CPU时间片一旦执行完一个线程的时间片,会自动跳转到另一个线程的run()中去执行其时间片,这样就可能导致共享数据的不一致,也即出现了多线程安全问题。

  2. 解决方案

    class Ticket implements Runnable{    private int tick = 100;    public void run()    {        while(true)        {            //在涉及到共享数据的代码上加互斥锁            synchronized(this)            {                if(tick>0)                {                    try{Thread.sleep(10);} catch(Exception e){}                    System.out.println(Thread.currentThread().getName()+"sale:"+tick--);                }            }        }    }}

互斥锁

  1. 互斥锁的理解

    互斥锁相当于公共厕所的一把锁,公共厕所是一个共享资源,为了使当前正在使用资源的人不被其他人打扰,在门上加一把锁,里面的人不开门(释放锁),外面的人就进不去。

  2. 如何判断哪些代码需要加锁

    1. 明确哪些代码是多线程运行代码
    2. 明确共享数据
    3. 明确多线程运行代码中哪些语句是操作共享数据的。
  3. 加锁后同步的前提

    1. 有两个或两个以上的线程
    2. 多个线程使用同一个锁
  4. 加锁的弊端

    多个线程需要判断锁,较为消耗资源。

  5. 锁对象

    1. 同步代码块的锁可以是任意对象。
    2. 同步函数的锁是this。
    3. 静态同步函数的锁是Class对象。

多线程间的通信

  1. 问题描述

    当多个线程在操作同一个资源,但是操作的动作不同(run()不同),就涉及到了线程间的通信问题。

  2. 解决方法:用标记+等待唤醒机制wait()和notify()实现同步

    1. wait():使正在执行的线程等待,进入阻塞状态。
    2. notify():唤醒线程池中等待的线程,进入就绪状态。
    3. notifyAll() :唤醒线程池中所有线程。

    注意:这些方法都在互斥锁中使用,因为它们都被互斥锁对象调用。 以下是代码示例。

    class Res{    private String name;    private String sex;    private boolean flag = false;   //是否释放锁的标记    //送入    public synchronized void  set(String name,String sex)   //加锁    {        if(flag)        //有煤了,等,释放锁            try{this.wait();} catch(Exception e){}          this.name = name;        this.sex = sex;        flag = true;        //标记有资源了        this.notify();      //唤醒对方线程,叫对方送走资源    }    //送走    public synchronized void out()    {        if(!flag)       //没有资源,等            try{this.wait();} catch(Exception e){}          System.out.println(name+"........"+sex);        flag = false;   //标记没有资源了        this.notify();  //唤醒对方线程,叫对方送资源    }}class Input implements Runnable{    Res r;    Input(Res r)    {        this.r = r;    }    public void run()    {        int x = 0;        while(true)        {            if(x == 0)                  r.set("mike","male");            else                r.set("lili","女女女女女女");            x = (x+1) % 2;        }    }}class Output implements Runnable{    Res r;    Output(Res r)    {        this.r = r;    }    public void run()    {        while(true)        {            r.out();        }    }}class  ThreadDemo7{    public static void main(String[] args)     {        //要操作的共享资源        Res r = new Res();        //开启两个线程同时运行        new Thread(new Input(r)).start();        new Thread(new Output(r)).start();    }}
  3. 需要同步的几种情况

    1. 两个操作一样的线程:直接加锁(锁对象是本身对象)
    2. 两个操作不一样的线程:加锁+判断标记+等待唤醒机制(notify())
    3. 两个执行A操作的线程、两个执行B操作的线程:加锁+循环判断标记+等待唤醒机制(notifyAll())
  4. 生产者消费者问题代码示例

    //共享资源class Resource{    private String name;            //资源名称    private int count = 1;          //操作资源的次数    private boolean flag = false;       //是否释放锁的标记    public synchronized void set(String name)       //隐式的锁机制    {        while(flag)                 //有资源            try{this.wait();} catch(Exception e){}  //等待的同时释放锁        this.name = name + "--" + count++;        System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name);        flag = true;                    //有资源了        this.notifyAll();               //唤醒所有等待队列中的线程    }    public synchronized void out()    {        while(!flag)                    //没有资源            try{this.wait();} catch(Exception e){}        System.out.println(Thread.currentThread().getName()+"....消费者.........."+this.name);        flag = false;                   //资源被取走了        this.notifyAll();               //唤醒所有等待队列中的线程    }}//生产者class Producer implements Runnable {    private Resource res;    Producer(Resource res)    {        this.res = res;    }    public void run()    {        while(true)        {            res.set("+商品+");        }    }}//消费者class Consumer implements Runnable{    private Resource res;    Consumer(Resource res)    {        this.res = res;    }    public void run()    {        while(true)        {            res.out();        }    }}class ThreadDemo8{    public static void main(String[] args)     {        //共享资源对象        Resource r = new Resource();        //生产者、消费者对象操作同一个资源        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        //两个生产者线程、两个消费者线程        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();    }}

JDK5.0升级后的同步解决方案

  1. 升级内容

    1. 将同步synchronized替换成Lock接口
    2. 由于wait()、notify()都要标识自己所属的锁,因此将Object中的wait()、notify()、 notifyAll()封装成Condition监视器对象,该对象可以通过Lock锁对象获取,且一个锁可以对应多个监视器对象。
  2. 升级后的好处

    1. 显式的锁机制,使加锁和释放锁操作更透明。
    2. 显式的等待唤醒机制,可以有多个Condition监视器对象,使不会唤醒本方。
    3. 由于一个锁可以对应多个Condition监视器对象,因此不容易发生死锁。
  3. 生产者消费者问题代码示例

    import java.util.concurrent.locks.*;//要操作的共享资源class Resource{    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 set(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.signal();     //消费者醒来        }        finally         //在await()之前必须先释放锁        {            lock.unlock();  //释放锁        }    }    //获取资源    public void out() throws InterruptedException    {        lock.lock();        //加锁        try        {            while(!flag)                condition_con.await();  //消费者等待,没有释放锁            System.out.println(Thread.currentThread().getName()+"....消费者.........."+this.name);            flag = false;            condition_pro.signal();     //生产者醒来        }        finally         //在await()之前必须先释放锁        {            lock.unlock();        }    }}//生产者class Producer implements Runnable {    private Resource res;    Producer(Resource res)    {        this.res = res;    }    public void run()    {        while(true)        {            try            {                res.set("+商品+");            }            catch (InterruptedException e)            {            }        }    }}//消费者class Consumer implements Runnable{    private Resource res;    Consumer(Resource res)    {        this.res = res;    }    public void run()    {        while(true)        {            try            {                res.out();            }            catch (InterruptedException e)            {            }        }    }}class ThreadDemo9{    public static void main(String[] args)     {        //创建共享资源对象        Resource r = new Resource();        //创建生产者消费者对象,操作同一个资源对象        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        //两个生产者线程,两个消费者线程        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();    }}
原创粉丝点击