参考资料:

参考1:java开发实战经典

1.多线程概述

要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。

范例1:继承Thread类不能资源共享

多线程开发View Code

程序运行结果:

多线程开发View Code

以上程序通过继承Thread类实现多线程,程序中启动了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源(Ticket)共享的目的。

范例2:实现Runable接口可以资源共享

多线程开发View Code

程序运行结果:

多线程开发View Code

从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。

可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:

  1. 适合多个相同程序代码的线程去处理同一资源的情况。
  2. 可以避免由于java单继承特性带来的局限
  3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。

——————————————————————————————————————

范例2-2:增加输出线程名称的功能(ps:2012-6-9)

假如我们需要知道到底是哪一个线程在卖票,那么我们就必须输出线程的名称,对上述实例进行稍作修改该即可完成

首先是在实例化线程的时候可以传入线程名称,代码如下:

多线程开发View Code

上述程序运行结果如下:

t1在卖票:剩余ticket=4
t3在卖票:剩余ticket=2
t2在卖票:剩余ticket=3
t2在卖票:剩余ticket=1
t3在卖票:剩余ticket=0

但是多次你运行发现结果各不相同。

————————————————————————————————————————————

2.多线程的同步

多次运行范例2我们发现得到的结果可能都不相同。下面列举两个可能的输出结果

范例2可能的输出结果1

多线程开发View Code

范例2可能的输出结果2

多线程开发View Code

为了更形象地说明线程同步,我们在范例2中加入进程延时机制Thread.sleep(300);。代码如下所示:

多线程开发View Code

再次运行多线程主程序,得到的结果如下:

多线程开发View Code

出现票数为负的情况是因为:

线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,这样当线程1 ticket--之后ticket==0了,线程2再次执行ticket--那么ticket==-1。相当于多执行了一次 ticket--

3.两种线程同步方法

为了解决范例2中出现的问题,我们通过引入同步机制来解决问题。同步又分为同步代码块同步方法两种类型。

范例3:同步代码块

多线程开发View Code

之前说到ticket出现负数的原因是“线程1在执行 --ticket之前,线程2 进入了 if (ticket > 0) 这个判断, 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1,相当于多执行了一次ticket--”,

因此我们将synchronized(this)放在了if(ticket>0)之前。我们还可以发现外部有一个执行一般次的for循环for(int i=0;i<100;i++),这用来表示run方法中的这synchronized代码块会被执行100次。需要注意的是只有执行完synchronized代码块才会释放锁。因此每一个线程都有100次可能出现锁冲突,一个线程需要等待另外一个线程执行完synchronized代码块中的内容以后才可以访问这个代码块。

范例4:同步方法

多线程开发View Code

这里将操作ticket资源的内容单独抽取出来作为一个方法来调用,然后同步该方法,保证同一时间只有一个进程调用该方法。

4生产者消费者案例

范例5:

多线程开发View Code

程序输出:

多线程开发View Code

范例5存在两个问题:

  1. 问题1:假设ProducerThread线程刚设置了name信息,还没有设置content信息,此时程序就切换到了ConsumerThread线程,那么ConsumerThread线程获取的是当前name与之前的content,所以输出结果会出现(比如:name1:-->content0)。这是因为name与content没有一起设置的原因,或者是说name与content信息不同步。
  2. 问题2:生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。(比如出现name1:-->content0 name1:-->content0,这个可以通过调节sleep来控制)

问题1 解决:加入同步

如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。

范例6

多线程开发View Code

程序运行结果

多线程开发View Code

从程序的运行结果中可以发现,问题1:信息错乱的问题已经解决,但是依然存在问题2:重复读取的问题,以及漏读信息的问题(比如上述输出中name9:-->content9重复读取,而name5:-->content5被漏读了)。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。

Object类是所有类的父类,在此类中有以下几种方法是对线程操作有所支持的,如下表所示:

多线程开发

如果想让生产者不重复生产,消费者不重复消费,可以设置有一个标志位,假设标志位为boolean型变量,如果标志位内容为true,则表示可以生产,但是不能取走,如果标志位内容为false,则表示可以取走,不能生产。操作流程如下:

多线程开发

问题解决2——加入等待与唤醒

多线程开发View Code

程序运行结果:

多线程开发View Code

ps:添加注释,解释Object.wait()与this.wait()(2012-5-12)

多线程开发View Code

运行结果

多线程开发View Code

 

 

本文转自xwdreamer博客园博客,原文链接:http://www.cnblogs.com/xwdreamer/archive/2011/11/20/2296931.html,如需转载请自行联系原作者

相关文章:

  • 2021-04-12
  • 2021-11-18
  • 2021-11-16
  • 2021-07-25
  • 2021-11-19
  • 2021-10-21
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-01-09
  • 2021-05-08
相关资源
相似解决方案