最近在看多线程时,一直迷茫为什么继承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,相当于每个卖票任务都绑定了一个线程,每个线程执行的卖票任务不是同一个,因此没有实现资源共享。