1/线程与进程
(1)线程的概念:
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
(2)进程的概念:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。一个进程至少包含一个线程。
(3)给大家推荐一个讲述进程和线程的图画形式的网址:
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html。
2/线程的状态
(1)创建(new)状态:准备好了一个多线程的对象
(2)就绪(runnable)状态:调用了start()方法,等待CPU进行调度
(3)运行(running)状态:执行run()方法
(4)阻塞(blocked)状态:暂时停止执行,可能将资源交给其它线程使用
(5)终止(dead)状态:线程销毁
如图:
3/什么是线程安全
(1)当多个线程访问run的方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后的顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码体的内容;拿不到锁,这个线程就会不断的尝试得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁(也就是会有所的竞争问题)
4/上下文切换的概念
(1)对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。
(2)由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。
(3)因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。
(4)说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
(5)虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。
5/java实现多线程的方式
(1)继承Thread类
public class MyThread {
public static void main(String[]args) {
Thread1 myThread =new Thread1();
myThread.start();
}
}
class Thread1extends Thread {
public void run() {
try {
Thread.sleep(100L);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(2) 实现Runnable
public class MyThread1 {
public static void main(String[]args) {
Thread thread2 =new Thread(new Thread2());
thread2.start();
}
}
class Thread2implements Runnable {
@Override
public void run() {
try {
Thread.sleep(100L);
} catch (InterruptedExceptione) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
如图:
这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
还有一点最重要的就是使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.(例子如:卖火车票)
6/实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,很好的体现了面向对象的设计思想。
(2)可以避免单继承带来的局限性。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
7/线程的常用方法
(1)start()方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
(2)run()方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
(3)join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。这里有一个很值得注意的问题,join的底层调用的是wait方法,而且是循环调用,源码如图所示:
我们可以看到源码中join方法会在while循环中一直调用wait方法,假如wait的时间是1000ms,在500ms的时候另外一个线程调用了notifyAll方法时,线程就会苏醒。
(4)setDaemon()方法
用来设置线程是否成为守护线程和判断线程是否是守护线程。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
(5)sleep()方法
此方法调用的是native的方法,sleep方法是使当前线程休眠,讲cpu占用权交给其他任意优先级的线程。但是我们应该注意:sleep方法并不会释放对象锁。
(6)yield()方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
(7)isAlive()方法
方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。需要注意的地方是,在不调用sleep()方法时,isAlive()方法为true,在执行sleep(1000)的方法后,IsAlive()方法结果为false。是因为线程已经在1秒之内执行完毕。
(8)currentThread()方法
currentThread()方法可以返回代码段正在被哪个线程调用的信息。
(9)setPriority()和getPriority()方法
用来获取和设置线程优先级,尽管jdk有10哥优先级,但它与多数操作系统都不能映射的很好。比如,Winfdows有7哥优先级且都不是固定的。所以这种映射关系也是不确定的。唯一可移植的方法是当调整优先级的时候,使用MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY三种级别。