多线程编程需要考虑最多的问题就是线程安全问题,线程安全指的就是共享资源的同步问题。所谓同步就是指多个线程在同一个时间段内只能由一个线程进行操作,其他线程需要等待正在操作的线程完成操作之后才可以继续执行。
1深入了解Thread类和Runnable接口
通过继承Thread类和实现Runnable接口都可以实现多线程的创建,那么两者有哪些联系和区别呢?
01、Thread类的定义
以下代码片段,摘自JDK源码里Thread类的部分定义:
public class Thread implements Runnable {
通过以上Thread类的定义,可以看出,Thread类是Runnable接口的子类,但在Thread类中并没有完全地实现Runnable接口中的run方法。Thread类中的run方法调用的是target的run方法,而target则是Runnable接口的子类,所以通过继承Thread类实现多线程,则该线程子类必须覆写run方法。
02、通过实现Runnable接口创建多线程的UML图
通过以上的UML类图,我们可以发现,Thread类和MyThread类都实现了Runnable接口,通过实现Runnable接口来创建线程类MyThread,其实就是把MyThread类放到了Thread类的target变量中,线程启动后,实际上是调用的target的run方法,也就是MyThread类中的run方法,所以说MyThread类必须覆写run方法。这种操作模式是典型的代理设计模式。
通过以上2个部分的说明,我们可以很明显地看出Thread类和Runnable接口的联系,那就是Thread类实现了Runnable接口。
03、通过继承Thread类实现的多线程能实现资源共享吗
答案是不能,如果一个类继承Thread类的话,它就不适合多个线程之间共享资源。
示例:通过继承Thread类的方式实现模拟售票场景,假设有5张电影票,两个窗口同时卖这5张电影票。
public class SaleTicketTask extends Thread {
从以上的程序运行结果来看,通过继承Thread类实现的多线程,每一个窗口都卖了5张电影票,没有达到5张电影票资源共享的目的。
04、通过实现Runnable接口创建的多线程可以实现资源共享
如果一个类继承Thread类的话,它就不适合多个线程之间共享资源,但是一个类实现了Runnable接口的话,就可以方便的实现资源共享。
示例:通过实现Runnable接口的方式实现模拟售票场景,假设有5张电影票,两个窗口同时卖这5张电影票。
public class SaleTicketTask1 implements Runnable {
从以上的运行结果来看,通过实现Runnable接口实现的多线程,两个窗口共卖了5张电影票,达到了5张电影票资源共享的目的。
实现Runnable接口相对于继承Thread类来说,有以下3个优势:
- 适合多个相同程序代码的线程去处理同一资源的情况;
- 可以避免由于Java的单继承特性带来的局限;
- 代码能够被多个线程共享,代码与数据可以独立;
因此,在开发中建议通过实现Runnable接口来创建多线程。
2演示线程安全问题
上面通过实现Runnable接口实现的线程,达到了两个窗口卖出5张电影票的目的,那么请大家思考,以上的程序是线程安全的吗?
答案是以上的程序不是线程安全的,可能出现重复售票的情况。
01、演示共享资源的线程不安全问题
我们可以在上面的SaleTicketTask1类中的run方法中加入一段线程休眠的代码,来放大这种线程不安全性,再次观察程序的运行情况。
public class SaleTicketTask1 implements Runnable {
通过上面两次程序的运行结果很明显的可以看出,两个线程窗口售票出现了共享资源(5张电影票)不安全的情况,第一次的程序卖出了3张重复票,第二次的程序票已近卖完了,依然又卖出了一张票号为0的票。
3解决线程安全问题
在多线程编程中,遇到多个线程要操作同一个资源的时候,就有可能出现共享资源的同步问题。要想解决这样的问题,就必须使用同步。
所谓同步就是指多个线程在同一个时间段内只能由一个线程进行操作,其他线程需要等待正在操作的线程完成操作之后才可以继续执行。
在Java编程中,实现同步操作的关键字是synchronized,解决资源共享的同步操有两种方法:一是使用同步代码块,二是使用同步方法。
01、使用同步代码块解决共享资源的线程不安全问题
代码块就是指用“{}”括起来的一段代码,如果再代码块上加上synchronized关键字,则此代码块就是同步代码块。
同步代码块的格式如下:
synchronized ( 同步对象 ){
在使用同步代码块时必须指定一个同步对象,一般将当前对象(this)设置成同步对象。
示例1:使用同步代码块解决以上售票的同步问题
public class SaleTicketTask1 implements Runnable {
通过上面的结果可以看出,加入同步代码块确实解决了共享资源电影票安全性的问题,但是上面的示例代码,在run方法开始的时候就加入了synchronized同步,导致只有一个窗口在卖票,因此以上的代码是不完美的。
示例2:使用同步代码块解决以上售票的同步问题
public class SaleTicketTask1 implements Runnable {
说明,上面的程序通过同步代码块,完成了共享资源的同步安全性问题。
02、使用****同步方法****解决共享资源的线程不安全问题
同步方法顾名思义就是在方法的声明的时候加上synchronized关键字。
Java中方法定义的完整格式如下:
访问权限{public|default|protected|private} [final] [static]
示例:使用同步方法解决以上售票的同步问题
public class SaleTicketTask2 implements Runnable {
说明,上面的程序通过同步方法,也能完成共享资源的同步安全性问题。
以上内容对多线程的两种实现方式的联系和区别、对多线程编程中共享资源同步问题的解决进行了说明,希望大家可以熟练掌握。
网友评论