第二单元小结

 

 

这个单元的作业完成的并不好,总结一下,大概有以下的问题:

1. 第一次面对多线程,从概念上来说,和传统的单线程程序不同。
       2. 这个单元的作业需要将多线程与设计模式进行结合。(鄙人两者都学的稀烂。。。)
       3. 多线程的程序,如若报错,首先应当先考虑是否是线程之间的关系导致的问题。再明确地找到问题的进程进行错误分析。这和传统的单线程程序完全不同。

既然本单元的学习是多线程。这篇随笔的内容就是多线程的学习。

单线程程序

 

 

 

以此单线程程序为例,其中的main也就是主线程。主线程执行命令行中输入的类的main方法。在方法中的处理都执行完之后,主线程终止了。

另外严格地来说,Java处理的后台也有线程在运行,例如垃圾回收线程、GUI相关线程等。

多线程程序

举例来讲,多线程程序的运行轨迹像多条线交织在一起。

比如GUI:可以查找字段,同时显示“停止查找”的按钮来停止查找。这里如果设置两个线程会比较好。

再比如操作系统课上讲的I/O操作处理问题。文件、网络等的I/O操作一般都非常耗时间。如果是单线程程序,此时就需要等待,毫无疑问,这是十分浪费性能的。此时将I/O操作单独作为一个线程与其他操作分离,从而提高性能。

另外,我们还可以看一下网络服务器同时处理多个客户端的案例。如果设计一个程序来同时针对多个客户端进行处理,那么这样的程序会很复杂,而且会面临服务器反馈客户端请求不及时的问题。但是如果我们给每一个客户端都设计上一个线程,如此一来,每一个客户端都会感觉自己被单独处理了。

总结一下,多线程程序的应用,主要是为了满足功能需求、性能需求这两个方面。

Java对于多线程程序的实现

一般有两个方法:1. 继承Java中自带的Thread类。2. 实现Java中自带的Runnable接口。需要注意的是,Thread类本身上就是实现了Runnable接口的类。

通过重写Thread类里的空方法run(),再调用这个Thread类的start()方法来启动新的进程。以下面的代码为例:

 

 

 

调用start方法后,程序会在后台启动新的线程,然后该线程调用run方法。逻辑结构如下题所示:

 

 

 

这里就涉及到了并发(concurrent)的概念,可以这样理解:“将一个操作分割成多个部分并允许进行无需处理”。需要区别于并行,并行是多个操作同时处理。上图的程序是并发处理的,两个进程进行不断切换,顺序执行,并发处理。为了确保程序能够完全正确地运行,就必须考虑线程的互斥处理和同步处理。

实现Runnable接口也是类似的。如下代码所示:

 

 

 

不管是利用Thread类子类的方法,还是利用Runnable接口实现类的方法,启动新线程的方法最终都是Thread类的start方法。

线程的暂停:

sleep方法可以暂停线程。如:

Thread.sleep(1000);

就是将线程暂停约1000ms。因为该方法可能会抛出InterruptedException异常该方法调用一般放在try…catch块中,如下代码所示:

 

 

 

一般sleep方法可能会用到的场景如下:模拟非常耗时间的线程、自动时间后关闭对话框、显示瞬间的状态可能会用到。

可通过Thread类中的interrupt方法来唤醒。

线程的互斥处理:

为何要进行线程的互斥处理呢?这涉及到了线程之间互相竞争导致的数据竞争的问题。以a、b两线程为例,操作都是在返回当前数字+1。A还没有返回1001的时候,b就已经读取了1000,导致返回的数据不是1002而是1001。

为了解决这个问题,需要用synchronized关键字来给者类方法“上锁”进行互斥处理。当a进程在处理对象时,其他线程就不能对对象进行操作,需要排队,同一时刻只能有一个线程运行。

线程的互斥机制称为监视(monitor)。是否获取锁可通过Thread.holdsLock方法来确认,如:

assert Thread.holdsLock(obj);

来判断是否获取obj对象的锁。注意,synchronized关键字是给对象上锁,表现为多个线程必须排队调用对象的上锁方法,而未上锁的方法不受影响。

而如果只是想让方法中的某一部分由一个线程来运行,而非整个方法,则可使用synchronized方法块,课用于精确控制互斥处理的执行范围。

以下两段代码是等效的:

 

 

 

 

 

 

换句话说,用synchronized关键字修饰的方法是使用该对象(this)的锁来执行线程的互斥处理。

同理若是synchronized关键字修饰的静态方法是利用类对象的锁来进行互斥处理。

线程协作:

目的就是有序控制线程。Java提供wait方法来让线程等待,notify和notifyAll方法来唤醒等待的线程。

等待队列就是实例对象执行了wait方法后,暂停操作的现成的队列。在以下情况发生时,线程会退出等待队列:

1. 其他线程的notify的方法唤醒。
2. 其他线程的notifyAll方法。。。。。
3. 其他线程的interrupt方法。。。。。
4. wait方法超时。

若实例对象执行wait方法,则线程必须持有锁。进入等待队列后便会释放锁。如下图所示:

       1. 获取锁的线程a执行wait方法后释放锁:

      

 

 

       2. 线程B能够获取锁:

      

 

 

notify方法就是随机将等待队列中的一个线程取出来并唤醒。如下图所示:

       1. 获取锁的b线程执行notify方法后,让a线程退出等待队列,想要继续执行wait方法后的操作,但b线程依然持有锁:

      

 

 

       2. a线程获取锁并继续执行。

而notifyAll方法则是唤醒所有等待队列中的线程,其他同notify方法。即所有的线程都退出等待队列,但是只有一个线程获得锁,其余的线程处于阻塞状态。

一般说来,notify方法因为唤醒的线程少,所以处理速度要比notifyAll方法快,但是健壮性却不如notifyAll方法。处理不好,程序可能会停止。除非完全理解代码的含义和范围,否则建议notifyAll方法。

总结一下,以上的方法都是Object类的方法(而非Thread类的固有方法),换句话说,这些方法不能狭义地理解为针对线程的操作,而是针对实例对象的等待队列的操作。

posted on 2020-04-18 19:59  BuniQ  阅读(139)  评论(0编辑  收藏  举报