zoukankan      html  css  js  c++  java
  • 多线程开发

    参考资料:

    参考1:java开发实战经典

    1.多线程概述

    要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。

    范例1:继承Thread类不能资源共享

    View Code
    package test;
    
    public class MyThreadDemo1 {
        public static void main(String args[]) {
            MyThread1 mt1 = new MyThread1();
            MyThread1 mt2 = new MyThread1();
            MyThread1 mt3 = new MyThread1();
            mt1.start();
            mt2.start();
            mt3.start();
        }
    
        static class MyThread1 extends Thread {
            private int ticket = 5;
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 100; i++)
                    if (ticket > 0)// 当余票大于0则买票
                    {
                        System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票
                    }
            }
        }
    }

    程序运行结果:

    View Code
    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=2
    卖票:剩余ticket=1

    以上程序通过继承Thread类实现多线程,程序中启动了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源(Ticket)共享的目的。

    范例2:实现Runable接口可以资源共享

    View Code
    package test;
    
    public class MyRunableThreadDemo1 {
        public static void main(String args[]) {
            MyRunableThread1 mrt = new MyRunableThread1();
            Thread t1 = new Thread(mrt);
            Thread t2 = new Thread(mrt);
            Thread t3 = new Thread(mrt);
            t1.start();
            t2.start();
            t3.start();
        }
    
        static class MyRunableThread1 implements Runnable {
            private int ticket = 5;
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 100; i++)
                    if (ticket > 0)// 当余票大于0则买票
                    {
                        System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票
                    }
            }
        }
    }

    程序运行结果:

    View Code
    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1

    从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。

    可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:

    1. 适合多个相同程序代码的线程去处理同一资源的情况。
    2. 可以避免由于java单继承特性带来的局限
    3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。

    ——————————————————————————————————————

    范例2-2:增加输出线程名称的功能(ps:2012-6-9)

    假如我们需要知道到底是哪一个线程在卖票,那么我们就必须输出线程的名称,对上述实例进行稍作修改该即可完成

    首先是在实例化线程的时候可以传入线程名称,代码如下:

    View Code
    package test;
    
    public class MyRunableThreadDemo2 {
        public static void main(String args[]) {
            MyRunableThread1 mrt = new MyRunableThread1();
            Thread t1 = new Thread(mrt, "t1");
            Thread t2 = new Thread(mrt, "t2");
            Thread t3 = new Thread(mrt, "t3");
            t1.start();
            t2.start();
            t3.start();
        }
    
        static class MyRunableThread1 implements Runnable {
            private int ticket = 5;
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 100; i++)
                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName()
                                + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
                    }
            }
        }
    }

    上述程序运行结果如下:

    t1在卖票:剩余ticket=4
    t3在卖票:剩余ticket=2
    t2在卖票:剩余ticket=3
    t2在卖票:剩余ticket=1
    t3在卖票:剩余ticket=0

    但是多次你运行发现结果各不相同。

    ————————————————————————————————————————————

    2.多线程的同步

    多次运行范例2我们发现得到的结果可能都不相同。下面列举两个可能的输出结果

    范例2可能的输出结果1

    View Code
    卖票:剩余ticket=4
    卖票:剩余ticket=5
    卖票:剩余ticket=2
    卖票:剩余ticket=3
    卖票:剩余ticket=1

    范例2可能的输出结果2

    View Code
    卖票:剩余ticket=4
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=5
    卖票:剩余ticket=1

    为了更形象地说明线程同步,我们在范例2中加入进程延时机制Thread.sleep(300);。代码如下所示:

    View Code
    package test;
    
    public class MyRunableThreadDemo2 {
        public static void main(String args[]) {
            MyRunableThread1 mrt = new MyRunableThread1();
            Thread t1 = new Thread(mrt, "t1");
            Thread t2 = new Thread(mrt, "t2");
            Thread t3 = new Thread(mrt, "t3");
            t1.start();
            t2.start();
            t3.start();
        }
    
        static class MyRunableThread1 implements Runnable {
            private int ticket = 5;
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 100; i++)
                    if (ticket > 0) {
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
                    }
            }
        }
    }

    再次运行多线程主程序,得到的结果如下:

    View Code
    卖票:剩余ticket=4
    卖票:剩余ticket=5
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=0
    卖票:剩余ticket=-1

    出现票数为负的情况是因为:

    线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,这样当线程1 ticket--之后ticket==0了,线程2再次执行ticket--那么ticket==-1。相当于多执行了一次 ticket--

    3.两种线程同步方法

    为了解决范例2中出现的问题,我们通过引入同步机制来解决问题。同步又分为同步代码块同步方法两种类型。

    范例3:同步代码块

    View Code
    package test;
    
    public class MyRunableThreadDemo3 {
        public static void main(String args[]) {
            MyRunableThread1 mrt = new MyRunableThread1();
            Thread t1 = new Thread(mrt, "t1");
            Thread t2 = new Thread(mrt, "t2");
            Thread t3 = new Thread(mrt, "t3");
            t1.start();
            t2.start();
            t3.start();
        }
    
        static class MyRunableThread1 implements Runnable {
            private int ticket = 200;
    
            @Override
            public void run() {
    
                //错误
    //            synchronized (this) {
    //                while (ticket > 0) {
    //                    
    ////                    try {
    ////                        Thread.sleep(3);
    ////                    } catch (InterruptedException e) {
    ////                        e.printStackTrace();
    ////                    }
    //                    System.out.println(Thread.currentThread().getName()
    //                            + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
    //                }
    //            }
                /**
                 * 同步代码块 之前说到ticket出现负数的原因是线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断, 
                 * 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1。相当于多执行了一次ticket--,
                 * 因此我们将synchronized(this)放在了if(ticket>0)之前。for(int i=0;i<100;i++)用来表示连续执行100次。这里synchronized在
                 * for循环后面,因此每个线程都执行100次,彼此都有可能锁冲突。
                 * */
                 for(int i=0;i<100;i++)
                        synchronized(this)
                        {
                            if(ticket>0)
                            {
                                try{
                                    Thread.sleep(30);
                                }catch(InterruptedException e)
                                {
                                    e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName()
                                        + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
                            }
                        }    
            }
        }
    }

    之前说到ticket出现负数的原因是“线程1在执行 --ticket之前,线程2 进入了 if (ticket > 0) 这个判断, 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1,相当于多执行了一次ticket--”,

    因此我们将synchronized(this)放在了if(ticket>0)之前。我们还可以发现外部有一个执行一般次的for循环for(int i=0;i<100;i++),这用来表示run方法中的这synchronized代码块会被执行100次。需要注意的是只有执行完synchronized代码块才会释放锁。因此每一个线程都有100次可能出现锁冲突,一个线程需要等待另外一个线程执行完synchronized代码块中的内容以后才可以访问这个代码块。

    范例4:同步方法

    View Code
    package test;
    
    public class MyRunableThreadDemo4 {
        public static void main(String args[]) {
            MyRunableThread1 mrt = new MyRunableThread1();
            Thread t1 = new Thread(mrt, "t1");
            Thread t2 = new Thread(mrt, "t2");
            Thread t3 = new Thread(mrt, "t3");
            t1.start();
            t2.start();
            t3.start();
        }
    
        static class MyRunableThread1 implements Runnable {
            private int ticket = 200;
    
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    sale();
                }
            }
            
            public synchronized void sale() {
                if (ticket > 0) {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
                }
            }
        }
    }

    这里将操作ticket资源的内容单独抽取出来作为一个方法来调用,然后同步该方法,保证同一时间只有一个进程调用该方法。

    4生产者消费者案例

    范例5:

    View Code
    package test;
    
    
    
    public class ThreadDeadLock {
        public static void main(String args[]) {
            Info info = new Info();
            // info作为参数传入两个线程当中
            ProducerThread pt = new ProducerThread(info);
            ConsumerThread ct = new ConsumerThread(info);
    
            Thread producer = new Thread(pt, "producer");
            Thread consumer = new Thread(ct, "consumer");
            producer.start();
            consumer.start();
        }
        
        //资源类
        static class Info {
            private String name = "name";
            private String content = "content";
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public String getContent() {
                return content;
            }
    
            public void setContent(String content) {
                this.content = content;
            }
        }
    
        // 生产者线程
        static class ProducerThread implements Runnable {
            private Info info = null;
    
            // 构造函数,其参数是资源
            public ProducerThread(Info info) {
                this.info = info;
            }
    
            @Override
            public void run() {
                // boolean flag=false;
                for (int i = 0; i < 10; i++) {
                    this.info.setName("name" + i);
                    try {
                        Thread.sleep(90);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    this.info.setContent("content" + i);
                }
            }
        }
    
        static class ConsumerThread implements Runnable {
            private Info info = null;
    
            // 构造函数,其参数是资源
            public ConsumerThread(Info info) {
                this.info = info;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(this.info.getName() + ":-->"
                            + this.info.getContent());
                }
            }
        }
    
    }

    程序输出:

    View Code
    name1:-->content0
    name2:-->content1
    name3:-->content2
    name4:-->content3
    name5:-->content4
    name6:-->content5
    name7:-->content6
    name8:-->content7
    name9:-->content8
    name9:-->content9

    范例5存在两个问题:

    1. 问题1:假设ProducerThread线程刚设置了name信息,还没有设置content信息,此时程序就切换到了ConsumerThread线程,那么ConsumerThread线程获取的是当前name与之前的content,所以输出结果会出现(比如:name1:-->content0)。这是因为name与content没有一起设置的原因,或者是说name与content信息不同步。
    2. 问题2:生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。(比如出现name1:-->content0 name1:-->content0,这个可以通过调节sleep来控制)

    问题1 解决:加入同步

    如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。

    范例6

    View Code
    package test;
    
    public class ThreadDeadLock2 {
        public static void main(String args[]) {
            Info info = new Info();
            // info作为参数传入两个线程当中
            ProducerThread pt = new ProducerThread(info);
            ConsumerThread ct = new ConsumerThread(info);
    
            Thread producer = new Thread(pt, "producer");
            Thread consumer = new Thread(ct, "consumer");
            producer.start();
            consumer.start();
        }
    
        // 资源类
        static class Info {
            private String name;
            private String content;
    
            //getter and setter
            public String getName() {
                return name;
            }
            public void setName(String name) {
                this.name = name;
            }
            public String getContent() {
                return content;
            }
            public void setContent(String content) {
                this.content = content;
            }
    
            // 获取name与content信息
            public synchronized void get() {
    
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(this.getName() + ":-->" + this.getContent());
            }
    
            // 设置name与content信息
            public synchronized void set(String name, String content) {
    
                this.setName(name);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                this.setContent(content);
            }
        }
    
        // 生产者线程
        static class ProducerThread implements Runnable {
            private Info info = null;
    
            // 构造函数,其参数是资源
            public ProducerThread(Info info) {
                this.info = info;
            }
    
            @Override
            public void run() {
    
                for (int i = 0; i < 10; i++) {
                    this.info.set("name" + i, "content" + i);
                }
            }
        }
    
        static class ConsumerThread implements Runnable {
            private Info info = null;
    
            // 构造函数,其参数是资源
            public ConsumerThread(Info info) {
                this.info = info;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    this.info.get();
                }
            }
        }
    }

    程序运行结果

    View Code
    name-0:-->content-0
    name+1:-->content+1
    name-2:-->content-2
    name+3:-->content+3
    name-4:-->content-4
    name-6:-->content-6
    name+7:-->content+7
    name-8:-->content-8
    name+9:-->content+9
    name+9:-->content+9

    从程序的运行结果中可以发现,问题1:信息错乱的问题已经解决,但是依然存在问题2:重复读取的问题,以及漏读信息的问题(比如上述输出中name9:-->content9重复读取,而name5:-->content5被漏读了)。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。

    Object类是所有类的父类,在此类中有以下几种方法是对线程操作有所支持的,如下表所示:

    如果想让生产者不重复生产,消费者不重复消费,可以设置有一个标志位,假设标志位为boolean型变量,如果标志位内容为true,则表示可以生产,但是不能取走,如果标志位内容为false,则表示可以取走,不能生产。操作流程如下:

    问题解决2——加入等待与唤醒

    View Code
    package edu.sjtu.erplab.thread;
    
    class Info{
        private String name="name";
        private String content="content";
        private boolean flag=true;
        public  synchronized void set(String name,String content)
        {
            if(!flag)//标志位为false,不可以生产
            {
                try {
                    super.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            this.setName(name);
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            this.setContent(content);
            flag=false;//修改标志位为false,表示生产者已经完成资源,消费者可以消费。
            super.notify();//唤醒消费者进程
        }
        
        public synchronized void get()
        {
            if(flag)
            {
                try {
                    super.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(this.getName()+":-->"+this.getContent());
            flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。
            super.notify();//唤醒生产者进程。
        }
        
        
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getContent() {
            return content;
        }
        public void setContent(String content) {
            this.content = content;
        }
        
    }
    
    class Producer implements Runnable{
        private Info info=null;
        public Producer(Info info)
        {
            this.info=info;
        }
        
    
        @Override
        public void run() {
            boolean flag=false;
            for(int i=0;i<10;i++)
                if(flag)
                {
                    this.info.set("name+"+i, "content+"+i);
                    flag=false;
                }
                else
                {
                    this.info.set("name-"+i, "content-"+i);
                    flag=true;
                }
        }
    }
    
    class Consumer implements Runnable{
        private Info info=null;
        public Consumer(Info info)
        {
            this.info=info;
        }
        @Override
        public void run() {
            for(int i=0;i<10;i++)
            {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                this.info.get();
            }
            
        }
    }
    
    public class ThreadDeadLock {
        public static void main(String args[])
        {
            Info info=new Info();
            Producer pro=new Producer(info);
            Consumer con=new Consumer(info);
            new Thread(pro).start();
            new Thread(con).start();
        }
        
    }

    程序运行结果:

    View Code
    name-0:-->content-0
    name+1:-->content+1
    name-2:-->content-2
    name+3:-->content+3
    name-4:-->content-4
    name+5:-->content+5
    name-6:-->content-6
    name+7:-->content+7
    name-8:-->content-8
    name+9:-->content+9

    ps:添加注释,解释Object.wait()与this.wait()(2012-5-12)

    View Code
    package edu.sjtu.erplab.thread;
    
    class Info{
        private String name="name";
        private String content="content";
        private boolean flag=true;//标识位,表示生产者是否可以进行生产,true表示可以,false表示不可以。
        
        //生产者线程调用的方法
        public  synchronized void set(String name,String content)
        {
            if(!flag)//如果不可以进行生产,则对Info对象加锁
            {
                try {
                    super.wait();//让拥有Info对象的生产者线程进入等待状态
                    //this.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            //如果可以进行生产,则设置name与content属性
            this.setName(name);
    //        try {
    //            Thread.sleep(30);
    //        } catch (InterruptedException e) {
    //            // TODO Auto-generated catch block
    //            e.printStackTrace();
    //        }
            this.setContent(content);
            System.out.println("生产:"+this.getName()+":-->"+this.getContent());
            flag=false;//修改标志位为false,表示可以消费,不能生产。
            super.notify();//唤醒拥有Info对象的线程
        }
        
        //消费者线程调用的方法
        public synchronized void get()
        {
            if(flag)//如果flag==true表示可以生产不可以消费,
            {
                try {
                    super.wait();//让拥有Info资源的消费者线程进入等待
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
    //        try {
    //            Thread.sleep(30);
    //        } catch (InterruptedException e) {
    //            // TODO Auto-generated catch block
    //            e.printStackTrace();
    //        }
            System.out.println("消费:"+this.getName()+":-->"+this.getContent());
            flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。
            super.notify();//唤醒生产者进程。
        }
        
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getContent() {
            return content;
        }
        public void setContent(String content) {
            this.content = content;
        }
        
    }
    
    //生产者线程
    class Producer implements Runnable{
        //生产者线程拥有的资源
        private Info info=null;
        //构造函数
        public Producer(Info info)
        {
            this.info=info;
        }
       
        @Override
        public void run() {
            boolean flag=false;
            for(int i=0;i<10;i++)
                if(flag)
                {
                    this.info.set("name+"+i, "content+"+i);
                    flag=false;
                }
                else
                {
                    this.info.set("name-"+i, "content-"+i);
                    flag=true;
                }
        }
    }
    
    //消费者线程
    class Consumer implements Runnable{
        //消费者线程拥有的资源
        private Info info=null;
        //构造函数
        public Consumer(Info info)
        {
            this.info=info;
        }
        @Override
        public void run() {
            for(int i=0;i<10;i++)
            {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                this.info.get();
            }
        }
    }
    
    public class ThreadDeadLock {
        public static void main(String args[])
        {
            Info info=new Info();
            Producer pro=new Producer(info);
            Consumer con=new Consumer(info);
            new Thread(pro).start();
            new Thread(con).start();
        }
        
    }

    运行结果

    View Code
    生产:name-0:-->content-0
    消费:name-0:-->content-0
    生产:name+1:-->content+1
    消费:name+1:-->content+1
    生产:name-2:-->content-2
    消费:name-2:-->content-2
    生产:name+3:-->content+3
    消费:name+3:-->content+3
    生产:name-4:-->content-4
    消费:name-4:-->content-4
    生产:name+5:-->content+5
    消费:name+5:-->content+5
    生产:name-6:-->content-6
    消费:name-6:-->content-6
    生产:name+7:-->content+7
    消费:name+7:-->content+7
    生产:name-8:-->content-8
    消费:name-8:-->content-8
    生产:name+9:-->content+9
    消费:name+9:-->content+9
    作者:xwdreamer
    欢迎任何形式的转载,但请务必注明出处。
    分享到:
  • 相关阅读:
    JS日期显示格式 yyyy-MM-dd hh:mm:ss
    Html.Partial和Html. RenderPartial
    $.ajax()详解
    B/S 类项目改善
    yaml
    跟我学SpringMVC目录汇总贴、PDF下载、源码下载
    全网最全ASP.NET MVC 教程汇总
    持续集成实践
    http://blog.jobbole.com/50603/#comment-153933
    Java代码优化(转)
  • 原文地址:https://www.cnblogs.com/xwdreamer/p/2296931.html
Copyright © 2011-2022 走看看