多线程Thread类和Runnable接口资源共享问题分析

来源:互联网 发布:php零基础到项目实战 编辑:程序博客网 时间:2024/06/10 21:09

最近在看多线程时,一直迷茫为什么继承Thread类的多线程不能实现资源共享,但是实现Runnable接口的多线程却能实现资源共享,先看一段经典的卖票多线程,将程序修改一下,使运行结果直观。
首先是实现Runnable接口来实现多线程:

public class TicketSaleMain implements Runnable{    private int num = 5;            //票数为5    public void run() {        System.out.println(Thread.currentThread().getName() );//线程运行时先打印线程名称        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) {        TicketSaleMain ticketThread = new TicketSaleMain();       //声明了一个实现Runnable接口的对象A        Thread th1 = new Thread(ticketThread);    //线程一使用A        th1.setName("窗口一");        Thread th2 = new Thread(ticketThread);    //线程二使用A        th2.setName("窗口二");        Thread th3 = new Thread(ticketThread);    //线程三使用A        th3.setName("窗口三");        th1.start();        th2.start();        th3.start();    }}

运行结果:

窗口二窗口一窗口二卖票: 5窗口一卖票: 4窗口二卖票: 3窗口一卖票: 2窗口二卖票: 1窗口三

因为3个线程共享了TicketSaleMain声明的放在堆里的对象ticketThread,都指向了这个对象,自然共享了成员属性num=5,相当于把一个任务塞到3个线程中,达到了一个卖票任务可以被3个线程共同执行,所以能达到资源共享,但是上述结果并不是一直一样顺序递减,还会有这样的结果1,
运行结果1:

窗口一窗口二窗口一卖票: 5窗口一卖票: 3窗口一卖票: 2窗口一卖票: 1窗口二卖票: 4窗口三

这是因为并发就是这样,如果你没有做并发控制,就会有各种灵异现象,因为线程执行是交错执行的。
也即,窗口一和窗口二分别先后执行一次this.num–后,窗口一执行了println,但是窗口二过了一阵子才执行了println。
还会有这样的结果2:
运行结果2:

窗口一窗口二窗口二卖票: 5窗口一卖票: 5窗口三窗口二卖票: 4窗口三卖票: 2窗口一卖票: 3窗口二卖票: 1

窗口一和窗口二分别同时执行一次this.num–后,一起执行println造成,这种情况在执行了很多次才会出现一次,如果想要更明显,可以修改run方法如下:

    public void run() {        System.out.println(Thread.currentThread().getName() );//线程运行时先打印线程名称        for(int i=0; i<10; i++){             try {                                 Thread.sleep(1000);    //让线程执行的时候休眠1000ms                             }             catch (InterruptedException e) {                                e.printStackTrace();                            }                 if(this.num>0){            //打印卖票信息                 System.out.println(Thread.currentThread().getName() + "卖票: " + this.num--);            }        }    }

运行结果:

窗口二窗口一窗口三窗口二卖票: 5窗口三卖票: 4窗口一卖票: 3窗口二卖票: 2窗口三卖票: 1窗口一卖票: 1

上述这些情况都是因为没有对多线程进行并发控制,需要对线程进行同步操作,避免线程对资源竞争,防止出现线程安全问题,同步操作使得在同一个时间段内只有一个线程能进行System.out.println(Thread.currentThread().getName() + "卖票: " + this.num--);其余线程必须等待此线程完成后才能继续执行。

在此我们用synchronized关键字来同步,synchronized关键字可以同步方法,也可以同步代码块。

同步方法,就是可以把for循环内的代码封装成一个方法booking(),并用public synchronized void声明,然后再run()中调用booking方法

同步代码块,可以用synchronized (this) { }将for循环后的代码块包起来,以此来进行同步控制。

然后是继承Thread类来实现多线程,在此就不多说了,直接上代码:

public class TicketSaleMain extends Thread    private int num = 5;        //总共票数设定为5张        @Override        public void run() {            System.out.println(Thread.currentThread().getName() ); //线程运行时先打印线程名称        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) {    TicketSaleMain th1 = new TicketSaleMain();        //声明的对象线程一    th1.setName("窗口一");    TicketSaleMain th2 = new TicketSaleMain();        //声明的对象线程二    th2.setName("窗口二");    TicketSaleMain th3 = new TicketSaleMain();        //声明的对象线程三    th3.setName("窗口三");                //分别启动三个线程    th1.start();    th2.start();    th3.start();    }}

运行结果:

窗口二窗口一窗口二卖票: 5窗口二卖票: 4窗口二卖票: 3窗口二卖票: 2窗口二卖票: 1窗口一卖票: 5窗口三窗口一卖票: 4窗口三卖票: 5窗口一卖票: 3窗口三卖票: 4窗口一卖票: 2窗口三卖票: 3窗口一卖票: 1窗口三卖票: 2窗口三卖票: 1

从运行结果看,继承Thread类实现多线程,几个线程就要声明几个对象,每个对象都绑定了num=5,相当于每个卖票任务都绑定了一个线程,每个线程执行的卖票任务不是同一个,因此没有实现资源共享。

1 0