Runnable可以实现资源共享但Thread不能实现资源共享的原因

转自:http://blog.csdn.net/javaniceyou/article/details/6859305

线程的两种实现方式,通过实现Runnable接口的线程方式可以实现资源的共享,而继承Thread则不可以,原因何在?先看下面两段代码:

通过Thread实现线程:

Java代码  复制代码
  1. //使用Thread实现线程不能实现资源共享 
[java]  view plain copy
  1. //使用Thread实现线程不能实现资源共享  
  2. class MyThread extends Thread  
  3. {     
  4.     private int ticket=5;  
  5.     private String name;  
  6.     public MyThread(String name ){  
  7.         this.name=name;  
  8.     }  
  9.     public void run(){  
  10.         for(int i=0;i<10;i++){  
  11.             if(ticket>5){  
  12.                 System.out.println("线程"+name+"卖票"+i);  
  13.             }  
  14.         }  
  15.       
  16.     }  
  17. }  
  18.   
  19. public class ThreadDemo02  
  20. {  
  21.     public static void main(String args[]){  
  22.         MyThread A = new MyThread("A");    
  23.         MyThread B = new MyThread("B");  
  24.         A.start();  
  25.         B.start();  
  26.     }  
  27. }  

通过Runnable实现:

Java代码  复制代码
  1.  
    [java]  view plain copy
    1. //使用Runnable实现线程可以实现资源共享  
    2. class MyThread implements Runnable  
    3. {  
    4.     private int ticket=5;  
    5.     private String name;  
    6.     public MyThread(String name){  
    7.         this.name=name;  
    8.     }  
    9.     public void run(){  
    10.         for(int i=1;i<=10;i++){  
    11.             if(ticket>0){  
    12.                 System.out.println("线程"+name+"卖票"+(ticket--));  
    13.                 }  
    14.         }  
    15.     }  
    16. }  
    17. public class RunnableDemo02  
    18. {  
    19.     public static void main(String args[]){  
    20.         MyThread A = new MyThread("A");  //实例化线程要执行的任务  
    21.         Thread Ta = new Thread(A);    //实例两个线程对象,实际传递的是一个任务  
    22.         Thread Tb = new Thread(A);    //因为两个线程执行的是一个任务,所以资源是共享的  
    23.         Ta.start();  
    24.         Tb.start();  
    25.     }  
    26. }  

解释:

因为一个线程只能启动一次,通过Thread实现线程时,线程和线程所要执行的任务是捆绑在一起的。也就使得一个任务只能启动一个线程,不同的线程执行的任务是不相同的,所以没有必要,也不能让两个线程共享彼此任务中的资源。

一个任务可以启动多个线程,通过Runnable方式实现的线程,实际是开辟一个线程,将任务传递进去,由此线程执行。可以实例化多个 Thread对象,将同一任务传递进去,也就是一个任务可以启动多个线程来执行它。这些线程执行的是同一个任务,所以他们的资源是共享。

两种不同的线程实现方式本身就决定了其是否能进行资源共享。


个人理解:网上很多文章讲述可以使用Runnable实现资源共享,当然有的使用继承Thread也可以,但我还是表示怀疑这种做法是否真的可以实现资源共享,在共享资源的过程中并没有使用同步字段,这样是否会引起资源冲突,还需进一步探讨,如有牛人解释,不胜感激!

找到以下解释让我感到我的想法是正确的。



多线程实现资源共享的问题学习与总结

我么知道Java传统多线程的实现有两种方法,继承Thread类或者实现Runnable即可.线程启动时调用start()方法.

实现Runnable接口相比继承Thread类有如下好处:

1.避免单继承的局限,一个类可以同时实现多个接口

2.适合资源的共享.

 

实现多线程模拟售票点卖票来说明实现Runnable即可可以达到资源共享的目的.

使用继承Thread类的多线程售票实现

复制代码
package org.dennist.thread.demo;/** * *  TicketThread.java     * *  @version : 1.1 *   *  @author  : 苏若年    <a href="mailto:DennisIT@163.com">发送邮件</a> *     *  @since     : 1.0        创建时间:    2013-2-24        下午02:22:49 *      *  TODO     :    class TicketThread.java is used for ... * */public class TicketThreadT extends Thread{        private int num = 5;        //总共票数设定为5张        @Override    public void run() {        for(int i=0; i<10; i++){            if(this.num>0){        //打印买票信息                System.out.println(Thread.currentThread().getName() + "买票: " + this.num--);            }        }    }        public static void main(String[] args) {        TicketThreadT th1 = new TicketThreadT();        //线程一        th1.setName("售票口一");            TicketThreadT th2 = new TicketThreadT();        //线程二        th2.setName("售票口二");        TicketThreadT th3 = new TicketThreadT();        //线程三        th3.setName("售票口三");                //分别启动三个线程        th1.start();        th2.start();        th3.start();    }}
复制代码

程序运行结果:

总共5张票,启动了三个线程,从打印结果可以看出,一共卖出去了15张票,线程之间没有进行资源共享

实现Runnable的售票线程

复制代码
package org.dennist.thread.demo;/** * *  TicketThreadR.java     * *  @version : 1.1 *   *  @author  : 苏若年    <a href="mailto:DennisIT@163.com">发送邮件</a> *     *  @since     : 1.0        创建时间:    2013-2-24        下午02:29:23 *      *  TODO     :    class TicketThreadR.java is used for ... * */public class TicketThreadR implements Runnable{        private int num = 5;            //总共票数设定为5张        @Override    public void run() {        for(int i=0; i<10; i++){            if(this.num>0){            //打印买票信息                System.out.println(Thread.currentThread().getName() + "买票: " + this.num--);            }        }    }    public static void main(String[] args) {        TicketThreadR ticketThread = new TicketThreadR();                Thread th1 = new Thread(ticketThread);    //线程一        th1.setName("售票口一");        Thread th2 = new Thread(ticketThread);    //线程二        th2.setName("售票口二");        Thread th3 = new Thread(ticketThread);    //线程三        th3.setName("售票口三");                th1.start();        th2.start();        th3.start();    }}
复制代码

程序运行结果

虽然现在程序中有三个线程,但是三个线程总共卖出了5张票,也就是说使用Runnable实现的多线程可以达到资源共享的目的.

Java多线程访问共享方式

(1)如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。

(2)如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:

  1、将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

  2、将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

  3、上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

  4、总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

(3)极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。

 

 

在Thread类中存在以下的几个方法可以设置和取得名字.

设置名字: public final void setName(String name) 

public Thread(Runnable target, String name)

public Thread(String name)

取得名字: public final String getName()

在线程的操作中因为其操作的不确定性,所以提供了一个方法,可以取得当前的操作线程.

public static Thread currentThread()

说明:

对于线程的名字一般是在启动前进行设置,最好不要设置相同的名字,最好不要为一个线程改名字.

在Java执行中一个Java程序至少启动2个线程:一个主线程和一个垃圾回收线程.

 

多线程的同步问题

上面的实现Runnable程序就真的没问题了吗?我们知道现实生活中买票总会有等待,跟延迟,那么我们模拟现实生活中的买票然后再来看上面的程序输出.

复制代码
package org.dennist.thread.demo;/** * *  TicketThreadR.java     * *  @version : 1.1 *   *  @author  : 苏若年    <a href="mailto:DennisIT@163.com">发送邮件</a> *     *  @since     : 1.0        创建时间:    2013-2-24        下午02:29:23 *      *  TODO     :    class TicketThreadR.java is used for ... * */public class TicketThreadR implements Runnable{        private int num = 5;            //总共票数设定为5张        @Override    public void run() {        for(int i=0; i<10; i++){            try {                Thread.sleep(200);    //休息200毫秒            } catch (InterruptedException e) {                e.printStackTrace();            }                    if(this.num>0){            //打印买票信息                System.out.println(Thread.currentThread().getName() + "买票: " + this.num--);            }        }    }    public static void main(String[] args) {        TicketThreadR ticketThread = new TicketThreadR();                Thread th1 = new Thread(ticketThread);    //线程一        th1.setName("售票口一");        Thread th2 = new Thread(ticketThread);    //线程二        th2.setName("售票口二");        Thread th3 = new Thread(ticketThread);    //线程三        th3.setName("售票口三");                th1.start();        th2.start();        th3.start();    }}
复制代码

如果想解决这样的问题(这个问题我是没有太看明白为何会输出这样的结果,两个4是什么情况下输出的,虽然有问题,但是能确定的是如果要实现资源共享,必须的是有同步操作才可以,不然会出现线程安全问题,同步方法可以避免资源竞争),就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只有一个线程进行,其他线程要等待此线程完成之后才可以继续执行.

可以通过同步代码的方法进行代码的加锁操作,同步的实现有2中方法:

JAVA多线程同步主要依赖于若干方法和关键字
  1  wait方法

  2  notify方法和notifyAll方法

  3  synchronized关键字

  4 atomic action(原子操作)

详细说明见:[http://www.cnblogs.com/dennisit/archive/2013/02/25/2931573.html]

此处针对上面情况使用同步关键字synchronized解决.同步关键字使用有2种方法

  1.同步代码块

  2.同步方法

同步代码块

使用synchronized关键字进行同步代码块的声明,但是在使用此操作时必须明确的指出到底要锁定的是哪个对象,一般是以当前对象为主.

  synchronized(对象){   //一般都是讲this锁定

         //锁定对象

     }

上面的问题使用同步代码块解决

复制代码
package org.dennist.thread.demo;/** * *  TicketThreadR.java     * *  @version : 1.1 *   *  @author  : 苏若年    <a href="mailto:DennisIT@163.com">发送邮件</a> *     *  @since     : 1.0        创建时间:    2013-2-24        下午02:29:23 *      *  TODO     :    class TicketThreadR.java is used for ... * */public class TicketThreadR implements Runnable{        private int num = 5;            //总共票数设定为5张        @Override    public void run() {        for(int i=0; i<10; i++){            //使用同步代码块            synchronized (this) {                try {                    Thread.sleep(300);    //休息300毫秒                } catch (InterruptedException e) {                    e.printStackTrace();                }                        if(this.num>0){                        //打印买票信息                    System.out.println(Thread.currentThread().getName() + "买票: " + this.num--);                }            }                    }    }    public static void main(String[] args) {        TicketThreadR ticketThread = new TicketThreadR();                new Thread(ticketThread,"售票口一").start();    //线程一        new Thread(ticketThread,"售票口二").start();    //线程二        new Thread(ticketThread,"售票口三").start();    //线程三    }}
复制代码

 

同步方法

同步方法是在方法上增加synchronized关键字修饰
上面的问题使用同步代码块解决

复制代码
package org.dennist.thread.demo;/** * *  TicketThreadR.java     * *  @version : 1.1 *   *  @author  : 苏若年    <a href="mailto:DennisIT@163.com">发送邮件</a> *     *  @since     : 1.0        创建时间:    2013-2-24        下午02:29:23 *      *  TODO     :    class TicketThreadR.java is used for ... * */public class TicketThreadR implements Runnable{        private int num = 5;            //总共票数设定为5张        @Override    public void run() {        for(int i=0; i<10; i++){            sale();                    //调用同步方法        }    }        //使用同步方法    public synchronized void sale(){        try {            Thread.sleep(300);    //休息300毫秒        } catch (InterruptedException e) {            e.printStackTrace();        }                if(this.num>0){                //打印买票信息            System.out.println(Thread.currentThread().getName() + "买票: " + this.num--);        }    }        public static void main(String[] args) {        TicketThreadR ticketThread = new TicketThreadR();                new Thread(ticketThread,"售票口一").start();    //线程一        new Thread(ticketThread,"售票口二").start();    //线程一        new Thread(ticketThread,"售票口三").start();    //线程一    }}
复制代码

 

多个线程共享同一资源的时候需要进行同步,但是过多的同步会造成死锁.

 

什么叫死锁?死锁产生的主要原因是什么?死锁产生的必要条件,如何解决死锁?​

死锁指在多道程序系统中,一组进程中的每一个进程均无限期的等待该被改组进程中的另一个进程所以占有且永远不会释放的资源,这种现象称为系统处于死锁状态.​

死锁产生的原因主要有2个:​

  1.竞争资源,系统提供的资源数量有限,不能满足每个进程的要求​

  2.多道程序运行时,.进程推进顺序不合理​

产生死锁的必要条件

  1.互斥使用资源​

  2.占用并等待资源​

  3.不可抢夺资源​

  4.循环等待资源​

解决死锁的方法​

  1.预防死锁:破坏死锁产生的条件(除过互斥条件,因为破坏互斥条件不现实)​

  2.避免死锁​

  3.检测与排除​

  4.置之不理​

 

转载请注明出处:[http://www.cnblogs.com/dennisit/archive/2013/02/24/2925288.html]


已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页