黑马程序员Java学习笔记之多线程(并发)

来源:互联网 发布:vs2010 编译php扩展 编辑:程序博客网 时间:2024/05/17 01:09
-------android培训、java培训、期待与您交流! ----------   
现在的操作系统普遍是多任务操作系统,即同时可以执行多个任务(一般的表现是开启运行多个软件,或者在一个软件中同时同时使用多个CPU核心进行并行运算),多核CPU普及之后,实现了真正意义上的并行运算。多任务的优势自然是不言而喻的,而在程序设计时,通过多线程的方式实现了多任务。
   
     一、多线程的理解 
     在单核CPU上,多线程其实是伪多线程。CPU会分配给每个任务一些时间片,某个任务执行几步,切换到另外的任务再执行几步,这样不停切换。由于CPU运算是以毫秒甚至纳秒为单位的,所以切换的频率非常快,以至于看上去多个任务在并行运行。你可以边听歌边浏览网页,同时后台也会运行一些软件。而在多核CPU上,则是物理上就有多个运算核心同时工作,系统会按照优化的方式分配不同的任务给某一个或几个核心。但是光靠系统的分配是不足够的,如果程序开发人员想要一些大型程序高效运行,就需要善用多线程编程。
     
     二、Java中多线程的基本实现方法
     在Java中,我们类里的main()方法,又称为主线程,如果我们不去创建其他的线程,就会一直执行主线程,但其实比如GC(垃圾回收装置),也是一个线程,所以也就是说,Java程序本身就是多线程的。而我们常说的多线程编程,主要是指的自定线程。
     在Java中,实现自定多线程由两种方法,一是继承Thread类,二是实现Runnable接口。我们通常都会采用第二种方式即实现Runnable接口的方式来实现多线程。因为Java采用单继承,所以如果我们的类直接继承Thread类,就无法再继承其他的类了,而接口实现则可以同时实现多个,所以更加灵活
     1、继承Thread类的多线程实现
           Thread类(java.lang.Thread)为一个基本的多线程类,通过继承Thread类实现多线程,主要是重写其中的run()方法。在run()方法中写线程主体,而在主线程main()合适的位置上,调用类的实例.start()方法,即可开启该线程。
     案例1:使用Thread类创建线程
package JavaMultithreading;/** * Created by za on 2015/7/11. */public class RabAndTor {    public static void main(String[] args) {        Rabbit rab = new Rabbit();        Tortoise tor = new Tortoise();        rab.start();        tor.start();    }}class Rabbit extends Thread {    @Override    public void run() {        for (int i = 0; i < 100; i++) {            System.out.println("兔子在跑第 " + i + " 步!");        }    }}class Tortoise extends Thread {    @Override    public void run() {        for (int i = 0; i < 100; i++) {            System.out.println("乌龟在跑第 " + i + " 步!");        }    }}
运行结果:

...

     
     2、实现Runnable接口的多线程实现
     实现Runnable接口的好处之前已经说了,而方法也是重写接口中的抽象方法run(),创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。
     开启线程的方法和Thread类对象创建法类似,也是在main()中合适的位置使用实例.start()。
     案例2:使用实现Runnable接口创建线程
package JavaMultithreading;/** * Created by za on 2015/7/11. */public class Rabbit2 implements Runnable {    @Override    public void run() {        for (int i = 0; i < 1000; i++) {            System.err.println("兔子在跑第 " + i + " 步...");        }    }}


     三、Java中线程的状态
     一个线程有自己的生命周期,有五种状态:新生(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和终止(Dead)。

          如图所示,一个线程从最初创建后,执行start(),就进入就绪状态,这时相当于告诉CPU,该线程已经可以开始运行了,由CPU分配时间片给该线程。当该线程真正开始执行的时候,就进入了运行状态,run()方法的方法体开始工作。在这个过程中,线程有可能被暂停,比如执行sleep()方法,这就进入了阻塞状态,进入阻塞状态的线程一直处于停滞状态,直到阻塞解除,线程重新回到就绪状态(注意,这里是回到就绪状态重新排队等待运行而不是回到运行状态)。当线程执行完毕或者被强制终止,就进入终止状态,也就是死掉了,和该线程有关的资源将会被释放。

     四、线程的终止
     线程终止有两种情况,自然终止和外部干预。
          1、自然终止,线程运行完毕后终止。
          2、外部干预,有三种方法
               1)使用终止标识,使线程正常退出
                          a、线程类中定义线程体使用的标识     
                          b、线程体中使用该标识
                          c、对外提供方法改变该标识
               案例3:使用终止标识终止线程
package JavaMultithreading;/** * Created by za on 2015/7/12. */public class TestThreadStop {    public static void main(String[] args) {        Study std = new Study();        new Thread(std).start();        for (int i = 0; i < 100; i++) {            if(50 == i) {                std.stop();            }            System.out.println("Online Chatting..." + i);        }    }}class Study implements Runnable {    public boolean flag = true;    @Override    public void run() {        while (flag) {            System.out.println("Studying Thread...");        }    }    public void stop() {        this.flag = false;    }}
运行结果:


               2)使用stop()方法强行终止线程
                    一般不使用,因为stop和suspend、resume一样,也可能发生不可预料的结果。
               3)使用interrupt()方法中断线程

     五、线程的阻塞
          1、join()方法:合并线程
          案例5:join()方法合并线程
package JavaMultithreading;/** * 合并线程 * Created by za on 2015/7/12. */public class TestJoin extends Thread {    public static void main(String[] args) throws InterruptedException {        TestJoin t1 = new TestJoin();        //进程新生        Thread thread = new Thread(t1);        //进程就绪        thread.start();        for (int i = 0; i < 200; i++) {            if(i == 50) {                //使用join方法,将main线程阻塞,要等待join执行完,再执行main                thread.join();            }            System.out.println("main..." + i);        }    }    public void run() {        for (int i = 0; i < 100; i++) {            System.out.println("join..." + i);        }    }}
运行结果:


          2、yield()方法:暂停当前执行的线程,让出CPU调度,先执行其他线程
          yield()方法是一个static静态方法,暂停当前线程并不意味这当前线程一定不会被执行,只是现在暂停了,回到了就绪模式,还是有可能在未来的某个时间被CPU调到
          案例6:yield()方法暂停线程
package JavaMultithreading;/** * Created by za on 2015/7/12. */public class TestYield implements Runnable {    public static void main(String[] args) {        TestYield t2 = new TestYield();        new Thread(t2).start();        for (int i = 0; i < 100; i++) {            if (i%2 == 0) {                Thread.yield();//暂停所在线程main            }            System.out.println("main..." + i);        }    }    @Override    public void run() {        for (int i = 0; i < 100; i++) {            System.out.println("Yield..." + i);        }    }}
运行结果:


          3、sleep()方法:当前线程在指定的毫秒数内休眠,不释放锁
               应用:1)与时间相关的,倒计时,计时记数
                          2)模拟网络延时
          案例7:倒计时
package JavaMultithreading;/** * 使用进程sleep模拟倒计时 * Created by za on 2015/7/12. */public class TestSleep01 implements Runnable {    public static void main(String[] args) {        TestSleep01 ts1 = new TestSleep01();        new Thread(ts1).start();    }    @Override    public void run() {        int counter = 10;        while(true) {            System.out.println(counter--);            if(counter < 0) break;            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
运行结果:


          案例8:模拟网络延时售票
package JavaMultithreading;/** * Created by za on 2015/7/11. */public class TestWebTicketSellingSystem implements Runnable {    public static int numLeft = 50;    public static int numCounter = 1;    public static void main(String[] args) {        TestWebTicketSellingSystem sell = new TestWebTicketSellingSystem();        Thread t1 = new Thread(sell, "张三");        Thread t2 = new Thread(sell, "李四");        Thread t3 = new Thread(sell, "王五");        Thread t4 = new Thread(sell, "赵六");        Thread t5 = new Thread(sell, "刘七");        t1.start();        t2.start();        t3.start();        t4.start();        t5.start();    }    @Override    public void run() {        while(true) {            if (numLeft <= 0) {                System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" );                break;            }   else {                    System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 "                            + numLeft-- + " 张。" );                try {                    //模拟网络延时                    Thread.sleep(500);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}
运行结果:

     六、线程的优先级
          1、线程状态信息的获取方法
               1)Thread.currentThread()方法,是一个静态static方法,返回当前线程的对象的引用
               2)setName()    设置名称
               3)getName()    获取名称
               4)isAlive()    判断状态
          案例9:获取线程状态信息
package JavaMultithreading;/** * Created by za on 2015/7/13. */public class ThreadTest1 implements Runnable {    private boolean flag = true;    private int num = 0;    @Override    public void run() {        while(flag) {            //获取线程状态信息            System.out.println(Thread.currentThread().getName() + "---->" + num++);        }    }    public void setFlag(boolean flag) {        this.flag = flag;    }    public void stop() {        setFlag(false);    }}

          
          2、线程的优先级
               线程优先级有三种
               1)最大优先MAX_PRIORITY = 10
               2)一般优先NORM_PRIORITY = 5(默认优先级)
               3)最小优先MIN_PRIORITY = 1
          案例10:线程优先级
package JavaMultithreading;/** * Thread.currentThread()    当前线程 * setName()    设置名称 * getName()    获取名称 * isAlive()    判断状态 * * Created by za on 2015/7/13. */public class TestPriority {    public static void main(String[] args) throws InterruptedException {        ThreadTest1 it1 = new ThreadTest1();        Thread p1 = new Thread(it1, "进程1");        ThreadTest1 it2 = new ThreadTest1();        Thread p2 = new Thread(it2, "进程2");        //p1.setPriority(Thread.MAX_PRIORITY);        //p2.setPriority(Thread.MIN_PRIORITY);        p1.start();        p2.start();        Thread.sleep(2);        it1.stop();        it2.stop();    }}
运行结果:


     七、线程的同步(并发)
       线程的同步也成为并发,因为有多线程访问同一个资源,我们就需要确保这个资源安全。因此我们就要对资源加入同步,使资源变为线程安全的。
       使用同步关键字synchronized,修饰代码块或者方法。也叫做锁,要实现线程同步,首先要获得该线程的锁。
       只要是线程安全的,效率就没有线程不安全的高。这个要按需求进行处理。
          1、同步块
               synchronized(引用类型|this|类.class) {
               
               }
          2、同步方法
               使用synchronized修饰的方法。
          案例11:线程锁,同步块和同步方法的应用
package JavaMultithreading;/** * 线程锁|线程安全 * Created by za on 2015/7/13. */public class TestSyn {    public static void main(String[] args) {        Test12306 sell = new Test12306();        Thread t1 = new Thread(sell, "张三");        Thread t2 = new Thread(sell, "李四");        Thread t3 = new Thread(sell, "王五");        Thread t4 = new Thread(sell, "赵六");        Thread t5 = new Thread(sell, "刘七");        t1.start();        t2.start();        t3.start();        t4.start();        t5.start();    }}class Test12306 implements Runnable {    public static int numLeft = 49;    public static int numCounter = 1;    public boolean flag = true;    @Override    public void run() {        while(flag) {            TicketSystem3();        }    }    //线程安全的 局部synchronized    public void TicketSystem3() {        synchronized(this) {            if (numLeft == 0) {                System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" );                flag = false;                return;            }            System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 "                    + numLeft-- + " 张。" );            /*try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }*/        }    }    //线程安全的    public synchronized void TicketSystem2() {        if (numLeft <= 0) {            System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" );            flag = false;            return;        }   else {            System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 "                    + numLeft-- + " 张。" );            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    //线程不安全    public void TicketSystem1() {        if (numLeft <= 1) {            System.err.println(Thread.currentThread().getName() + " 抢到了最后一张票,票已售罄。" );            flag = false;            return;        }   else {            System.out.println(Thread.currentThread().getName() + " 抢到了第 " + numCounter++ + " 张票,还剩 "                    + numLeft-- + " 张。" );            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
运行结果:
图略

     八、单例模式的线程锁
          单例模式是指一个类只有一个实例的设计模式,一般采用私有构造器的方式去避免创建实例,同时对是否已经存在实例进行判断,如果不存在才创建实例。但是如果涉及到多线程,就有可能存在并发的问题,即如果几个线程同时请求创建实例,如果线程不安全,就有可能创建出大于一个的实例。这时要采用线程安全的synchronized方法检测。同时应用双层检测机制double checking。
          案例12:单例模式的线程锁
package JavaMultithreading;/** * 懒汉式 * Created by za on 2015/7/13. */public class MyJvm {    private static MyJvm instance;    private  MyJvm() {    }    public static void main(String[] args) {    }    public static MyJvm getInstance() {        if (null == instance) { //提供效率            synchronized(MyJvm.class) {                if (null == instance) { //安全                    instance = new MyJvm();                }            }        }        return instance;    }}/* * 饿汉式 * 1、构造器私有化 * 2、声明私有的静态属性,并创建该对象 * 3、对外提供访问属性的静态方法 */class MyJvm2 {    private static MyJvm2 instance = new MyJvm2();    private  MyJvm2() {    }    public static void main(String[] args) {    }    public static MyJvm2 getInstance() {        if (null == instance) { //提供效率            synchronized(MyJvm2.class) {                if (null == instance) { //安全                    instance = new MyJvm2();                }            }        }        return instance;    }}


     九、死锁
          1、Java死锁
               Java线程死锁是指,多个资源都在等待无法被释放的锁,使得无法继续进行。过多的同步容易造成死锁,死锁一定建立在同步之上,没有同步也就不存在死锁了。     
          案例13:一手交钱一手交货的死锁
package JavaMultithreading;/** * 一手交钱一手交货的死锁示例 * Created by za on 2015/8/12. */public class TestSyn02 {    public static void main(String[] args) {        Object goods = new Object();        Object money = new Object();        //Test1和Test2都要访问goods和money这一份资源,这样就容易产生死锁        Client t1 = new Client(goods, money);        Sellsman t2 = new Sellsman(goods, money);        Thread proxy1 = new Thread(t1);        Thread proxy2 = new Thread(t2);        //执行结果可能是死锁,谁都没有办法执行了        proxy1.start();        proxy2.start();    }}class Client implements Runnable {    //建立一个客户类,要求拿到货才给钱    //定义钱和货物两个Field    Object goods;    Object money;    //创建构造器,由外部传入钱和货物两个对象    public Client(Object goods, Object money) {        this.goods = goods;        this.money = money;    }    @Override    public void run() {        while(true) {            test();        }    }    //写一个test方法    public void test() {        //第一道锁,对货物的,要先拿到货物的锁        //过多的线程同步会造成死锁        synchronized (goods) {            try {                //添加延时为了增大死锁的概率                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }            //第二道锁,对钱的            synchronized (money) {            }        }        System.out.println("一手交钱");    }}class Sellsman implements Runnable {    //建立一个商人类,要求拿到钱才给货    //定义钱和货物两个Field    Object goods;    Object money;    //创建构造器,由外部传入钱和货物两个对象    public Sellsman(Object goods, Object money) {        this.goods = goods;        this.money = money;    }    @Override    public void run() {        while(true) {            test();        }    }    //写一个test方法    public void test() {        //第一道锁,对钱的,要先拿到钱的锁        //过多的线程同步会造成死锁        synchronized (money) {            try {                //添加延时为了增大死锁的概率                Thread.sleep(200);            } catch (InterruptedException e) {                e.printStackTrace();            }            //第二道锁,对货物的的            synchronized (goods) {            }        }        System.out.println("一手交货");    }}


          2、解决方案     
               解决死锁问题的一个办法,采用生产者消费者模式。
               生产者消费者问题(Producer-consumer problem)也成为有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程----即所谓的“生产者”和“消费者”----在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复这一过程。与此同时,消费者也在缓冲区中消耗这些数据。该问题的关键就是要保证生产者不会再缓冲区蛮时加入数据,消费者也不会在缓冲区空时消耗数据。
               要解决该问题,就必须让生产者在缓冲区满师休眠(要么干脆放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区中添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,在唤醒消费者。
               常用办法:信号灯法,管程等。
               存在问题:如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。
               关键方法:设置boolean类型信号灯flag
                                   java.lang.Object类方法notify()唤醒,notifyAll()唤醒所有,wait()等待
                                   wait()等待,释放锁|sleep()不释放锁
          案例14:生产者消费者模式(信号灯法)
package JavaMultithreading;/** * * Created by za on 2015/8/12. */public class TestPcp {    public static void main(String[] args) {        //共同的资源是        Movie m = new Movie();        //多线程        Player p = new Player(m);        Watcher w = new Watcher(m);        new Thread(p).start();        new Thread(w).start();    }}class Movie {    //共同的movie资源    //使用生产者消费者模式(信号灯法)    //与synchronized一起使用    private String pic;    /**     * 创建信号灯flag     * flag == true,生产者生产,消费者等待,生产完成后通知消费者     * flag == false,消费者消费,生产者等待,消费完成后通知生产     */    private boolean flag = true;    /**     * 播放     */    public synchronized void play(String pic) {        if (!flag) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        //开始生产,用sleep方法模拟生产耗费时间        try {            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("生产了:" + pic);        //生产完毕        this.pic = pic;        //通知消费者        this.notify();        //生产停止        this.flag = false;    }    public synchronized void watch() {        if (flag) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        //开始消费        try {            Thread.sleep(200);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("消费了:" + pic);        //消费完毕        //通知生产者        this.notifyAll();        //消费停止        this.flag = true;    }}class Player implements Runnable {    /**     * 生产者     */    private Movie m;    public Player(Movie m) {        super();        this.m = m;    }    @Override    public void run() {        for (int i = 0; i < 20; i++) {            if (0 == i%2) {                m.play("case1");            } else  {                m.play("case2");            }        }    }}class Watcher implements Runnable {    /**     * 消费者     */    private Movie m;    public Watcher(Movie m) {        super();        this.m = m;    }    @Override    public void run() {        for (int i = 0; i < 20; i++) {            m.watch();        }    }}


     十、任务调度
          1、常用类:Timer定时器类
               java.util.Timer是线程用其安排以后在后台线程中执行的任务。可安排任务执行一次或者定期重复执行。
          2、常用方法:schedule()日程安排
               参数
          public void schedule(TimerTask task, Date time)
          public void schedule(TimerTask task, long delay, long period)
          案例15:任务调度
package JavaMultithreading;import java.util.Date;import java.util.Timer;import java.util.TimerTask;/** * Created by za on 2015/8/12. */public class TestSchedule {}class Timer01 {    public static void main(String[] args) {        Timer timer = new Timer();        timer.schedule(new TimerTask() {            //TimerTask是java.util包下的一个抽象类,实现了Runnable接口,就是一个线程            //我们要重写其中的run()方法,这里使用了匿名            @Override            public void run() {                System.out.println("Test timer");            }        }, /*开始时间*/new Date(System.currentTimeMillis() + 1000), /*间隔时间,没有就是运行一次*/200);    }}

          
          
          
     
               
     
           
0 0
原创粉丝点击