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

最近在看多线程时,一直迷茫为什么继承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关键字来同步(如下两种方式任选其一):

①可以同步方法

同步方法:就是可以把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,相当于每个卖票任务都绑定了一个线程,每个线程执行的卖票任务不是同一个,因此没有实现资源共享。

转载自: https://blog.csdn.net/lyt_7cs1dn9/article/details/52486317
阅读更多

没有更多推荐了,返回首页