我这些学习笔记,记录的都是我自己认为的知识点,可能以后再看的时候还要翻书,但是可以用来定位到准确的书中示例的位置,减少翻书重找的时间,利于自身知识体系的搭建。 self-transcendence
18. 多线程
-------------------------------------------------------------------------------
18.1 线程简介
程序有时候要同时处理多个事情,在java中这就是并发,而将并发完成的每一件事情被称为线程。
Java提供的并发机制,使程序员在程序中执行多个线程,每一个线程完成一个功能,并与其它线程同时执行,这种机制被称为多线程。
Java中的多线程在每个操作系统中的运行方式也存在差异,例如windows操作系统中的运行模式:windows操作系统是多任务操作系统,以进程为单位。一个进程是一个包含自身地址的程序,每个单独执行的程序都成为进程,也就是正在执行的程序。系统给每个进程一段有限的使用cpu的时间(cpu时间片),cpu在这个时间片中执行某个程序,下个时间片跳至另一个程序中执行。由于cpu转换较快,所以使得每个进程好像同时执行一样。一个线程则是进程中的执行流程,一个进程中同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。单线程中,程序代码按照调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要多线程。
------------------------------------------------------------------------------
18.2 实现线程的两种方式
分别为继承:
Java.lang.Thread
Java.lang.Runnable
18.2.1 继承 Thread类
Thread类常用的两个构造方法如下:
1. public Thread()
2. public Thread(String threadName)
完成线程的功能代码放在继承Thread类的子类的重写的run()方法中,然后调用start()方法执行线程。(start方法其实就是调用子类重写的run方法)
如果start()方法调用一个已经启动的线程,系统抛出illegalThreadStateException异常
当执行一个程序的时候,就自动产生一个线程,这个线程执行主方法。不启动其他线程这个程序就是单线程程序。主方法线程由java虚拟机负责,程序员启动自己的线程。
通常在run方法中使用无限循环的形式使线程一直运行下去,当达到某一个条件跳出。
18.2.2 实现Runnable接口
实现Runnable接口的程序会创建一个Thread对象,并将Runnable对象与Thread对象相关联。Thread类中有一下两个构造方法:
1.public Thread(Runnable target)
2.public Thread(Runnable target,String threadName)
使用以上构造方法就可以将Runnable实例与Thread实例相关联。
使用Runnable接口启动新线程的步骤如下:
1. 建立Runnable对象
2. 使用参数为Runnable对象的构造方法创建Thread实例
3. 调用start()方法启动线程。
----------------------------------------------------------------------------
18.3 线程的生命周期
用户使用该线程实例调用start()方法之前线程都处于出生状态;当用于调用start()方法后,线程处于就绪状态(又被称为可执行状态);当线程得到系统资源后就进入运行状态。
一旦线程进入可执行状态,他就会在就绪与运行状态下转换。当处于运行状态下的状态线程调用Thread类中的wait()方法,线程进入等待状态,进入等待状态要调用notify()方法被唤醒,notifyAll()方法将所有处于等待状态的线程唤醒;调用sleep()方法线程进入休眠状态。如果一个线程在运行状态下发出输入/输出请求,将进入阻塞状态,在等待输入输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。当线程的run()方法执行完毕,线程进入死亡状态。
虽然多线程看起来像同时执行,但事实上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生线程是同时进行的假象。
使线程处于就绪状态有以下几种方法:
1. 调用sleep方法
2. 调用wait方法
3. 等待输出/输入结束
线程处于就绪状态后,可以使用一下几种方法再次进入运行状态:
1. 调用notify方法
2. 调用notifyAll方法
3. 调用interrupt方法
4. 线程的休眠时间结束
5. 输入/输出结束
----------------------------------------------------------------------------------
18.4 操作线程的方法
18.4.1 线程的休眠
sleep方法通常是在run方法内的循环中被使用,参数是毫秒
try{
Thread.sleep(2000);
}catch(Excsption e){
e.printStackTrace();
}
使用sleep方法的线程在一段时间内会醒来,但是不保证它进入运行状态,只能保证进入就绪状态。
18.4.2 线程的加入
当前正在执行线程A,但是需要插入线程B,要求线程B先执行完,然后继续执行线程A,此时可以使用Thread类中的join()方法来完成,使用join方法,A会等待B执行完之后再继续执行。
18.4.3 线程的中断
以往可以使用stop()方法停止线程,现在的jdk废除了stop方法。现在提倡在run方法中使用无限循环的形式,然后使用一个布尔类型标记控制循环的停止。如果线程使用了sleep或者wait方法进入了就绪状态,可以使用Thread类中的inerrupt方法使线程离开run方法,同时结束线程。但是程序会抛出interruptedException异常,用户可以在处理该异常时完成线程的中断业务处理。
18.4.4 线程的礼让
Thread类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态的线程一个提醒,告知他可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
yield方法使具有同样优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yield方法,因为操作系统会为线程自动分配cpu时间片了来执行。
----------------------------------------------------------------------------------------------
18.5 线程的优先级
每个线程都具有各自的优先级,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使那个线程进入运行状态。但这并不意味着低优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收线程的优先级就较低。
Thread类中包含的成员变量代表了线程的某些优先级。其中每个线程优先级都在Thread.Min_Priority(常数1)~Thread.max_Priority(常数10)之间,默认优先级都是Thread.Norm_Priority。每个新产生的线程都继承了父线程的优先级。
多线程中每个线程都有一个时间片,当时间结束时,会轮换另一个线程进入运行状态,这个线程的优先级与结束的同级,没有就选择优先级较高的线程进入运行状态。
线程的优先级使用setPriority()方法调整,如果该方法设置的优先级不在1~10,会产生illegalArgumentException异常。
-----------------------------------------------------------------------------------------------
18.6 线程同步
多线程会发生两个线程抢占资源的问题,如两个人过一个独木桥。所以在多线程编程中需要防止这些资源访问的冲突。java提供了线程同步的机制来防止资源访问的冲突。
18.6.1 线程安全
功能场景:当两个线程同时访问卖票操作,但此时票只剩最后一张,线程1将票售出,同时线程2完成了判断得出票余下数量大于1,于是线程2也把票出售了,这样就产生了负数。这就是线程安全问题。
18.6.2 线程同步机制
给共享资源上一道锁,同一时间只允许一个线程访问共享资源。
1. 同步块
java中提供了同步机制,可以有效的防止资源冲突。同步机制使用synchronize关键字。
synchronize(Object){
}
公共资源放在同步块中,这个同步块也被称为临界区。当有两个线程访问时,必须等这个锁释放之后才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有0和1两个值。一个线程运行到同步块是首先检查该对象的标志位,如果为0状态,表明此同步块中存在其他线程在运行。这时该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这时该对象的标志位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行。
2. 同步方法
同步方法就是在方法前面修饰synchronize关键字的方法,语法如下:
synchronize void f(){
}
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronize,否则就会出错。