什么是进程?
执行中的程序叫做进程(Process)
,是一个动态的概念。其实进程就是一个在内存中独立运行的程序空间 。
现代操作系统比如Mac OS X,
Linux
,
Windows
等,都是支持 “多任务
”
的操作系统,叫
“
多任务
”
呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用逛淘宝,一边在听音乐,一边在用微信聊天,这就是多任务,至少同时有3
个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
什么是线程?
线程(Thread
)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
有些进程还不止同时干一件事,比如微信,它可以同时进行打字聊天,视频聊天,朋友圈等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务
”
,我们把进程内的这些
“
子任务
”
称为线程(
Thread
)。
进程、线程的区别
乔布斯想开工厂生产手机,费劲力气,制作一条生产线,这个生产线上有很多的器件以及材料。一条生产线就是一个进程。 只有生产线是不够的,所以找五个工人来进行生产,这个工人
能够利用这些材料最终一步步的将手机做出来,
这五个工人就
是五个线程。为了提高生产率,有两种办法:
- 一条生产线上多招些工人,一起来做手机,这样效率是成倍増长,即单进程多线程方式
- 多条生产线,每个生产线上多个工人,即多进程多线程
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 3进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
- 4 调度和切换:线程上下文切换比进程上下文切换要快得多。
什么是并发
并发是指在一段时间内同时做多个事情。当有多个线程在运行时,如果只有一个CPU,这种情况下计算机操作系统会采用并发技术实现并发运行,具体做法是采用“ 时间片轮询算法”,在一个时间段的线程代码运行时,其它线程处于就绪状。这种方式我们称之为并发。(Concurrent)。
|
线程的执行特点
什么是主线程以及子线程
主线程
当Java
程序启动时,一个线程会立刻运行,该线程通常叫做程序的主线程(main thread
),即
main
方法对应的线程,它是程序开始时就执行的。 Java应用程序会有一个
main
方法,是作为某个类的方法出现的。当程序启动时,该方法就会第一个自动的得到执行,并成为程序的主线程。也就是说,main
方法是一个应用的入口,也代表了这个应用的主线程。JVM
在执行
main
方法时
,main
方法会进入到栈内存
,JVM会通过操作系统开辟一条main
方法通向
cpu
的执行路径
,cpu
就可以通过这个路径来执行main
方法
,
而这个路径有一个名字
,
叫
main(
主)线程。
主线程的特点
它是产生其他子线程的线程。
它不一定是最后完成执行的线程,子线程可能在它结束之后还在运行。
子线程
在主线程中创建并启动的线程,一般称之为子线程。
线程的创建
通过继承
Thread
类实现多线程
继承
Thread
类实现多线程的步骤:
1
- 在Java中负责实现线程功能的类是java.lang.Thread 类。 此种方式的缺点:如果我们的类已经继承了一个类(如小程序必须继承自 Applet 类),则无法再继承 Thread 类。
- 可以通过创建 Thread的实例来创建新的线程。
- 每个线程都是通过某个特定的Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。
- 通过调用Thread类的start()方法来启动一个线程。
通过继承Thread类实现多线程
public class TestThread extends Thread {//自
定义类继承Thread类
//run()方法里是线程体
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + ":" +i);//getName()方法是返回线程名称
}
}
public static void main(String[] args) {
TestThread thread1 = new TestThread(); //创建线程对象
thread1.start();//启动线程
TestThread thread2 = new TestThread();
thread2.start();
}
}
通过Runnable接口实现多线程
在开发中,我们应用更多的是通过Runnable
接口实现多线程。这种方式克服了继承Thread
类的缺点,即在实现
Runnable
接口的同时还可以继承某个类。
从源码角度看,Thread
类也是实现了
Runnable
接口。
Runnable
接口的源码如下:
public class TestThread2 implements Runnable{
//自定义类实现Runnable接口;
//run()方法里是线程体;
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
//创建线程对象,把实现了Runnable接口的对象
作为参数传入;
Thread thread1 = new Thread(new TestThread2());
thread1.start();//启动线程;
Thread thread2 = new Thread(new TestThread2());
thread2.start();
}
}
线程状态和生命周期
一个线程对象在它的生命周期内,需要经历5
个状态。
新生状态(New)
用new
关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start
方法
进入就绪状态。
2
就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“
线程就绪队列
”
,等待系统为其分配
CPU
。就绪状态并不是执行状态,当系统选定一个等待执行的Thread
对象后,它会进入执行状态。一旦获得CPU
,线程就进入运行状态并自
动调用自己的
run
方法。有
4
种原因会导致线程进入就绪状态:
1
新建线程:调用
start()
方法,进入就绪状态;
2
阻塞线程:阻塞解除,进入就绪状态;
3
运行线程:调用
yield()
方法,直接进入就绪状态;
4
运行线程:
JVM
将
CPU
资源从本线程切换到其他线程。
3
运行状态(Running)
在运行状态的线程执行自己run
方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“
导致阻塞的事件
”
而进入阻塞状态。
4 阻塞状态(Blocked)
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。
有
4
种原因会导致阻塞:
- 执行sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
- 执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,它进入就绪状态。
- 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
- join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法。
死亡状态(Terminated)
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()
或
destroy()
方法来终止一个线程(注:stop()/destroy()
方法已经被
JDK
废弃, 不推荐使用)。
当一个线程进入死亡状态以后,就不能再回到其它状态了。
线程的使用
终止线程的典型方法
public class StopThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程开始");
int i= 0;
while(flag){
System.out.println(Thread.currentThread().getName()+" "+i++);
try {
Thread.sleep(1000);
} catch
(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 线程结束");
}
public void stop(){
this.flag = false;
}
public static void main(String[]args)throws Exception {
System.out.println("主线程开始");
StopThread st = new StopThread();
Thread t1 = new Thread(st);
t1.start();
System.in.read();
st.stop();
System.out.println("主线程结束");
}
}
线程休眠
sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。sleep方法的参数为休眠的毫秒数。
public class SleepThread implements Runnable
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程开始");
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
//线程休眠1秒
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 线程结束");
}
public static void main(String[] args) {
System.out.println("主线程开始");
Thread t = new Thread(new SleepThread());
t.start();
System.out.println("主线程结束");
}
}
线程让步
使用yield
方法时要注意的几点:
- yield是一个静态的方法。
- 调用yield后,yield告诉当前线程把运行机会交给具有相同优先级的线程。
- yield不能保证,当前线程迅速从运行状态切换到就绪状态。
- yield只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。
public class TestyieldThread implements Runnable {
@Override
public void run() {
for(int i=0;i<30;i++){
if("Thread-0".equals(Thread.currentThread().getName()))
{
if(i == 0){
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new TestyieldThread());
Thread t2 = new Thread(new TestyieldThread());
t1.start();
t2.start();
}
}
线程联合
当前线程邀请调用方法的线程优先执行,在调用方法的线程执行结束之前,当前线程不能再次执行。线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线
程B
执行完毕后,才能继续执行。
join方法的使用
join()方法就是指调用该方法的线程在执行完run()方法后,再执行
join方法后面的代码,即将两个线程合并,用于实现同步控制。
class A implements Runnable{
private Thread b;
public A(Thread b){
this.b = b;
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" A "+i);
if(i == 5){
try {
this.b.join();
} catch
(InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class B implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" B "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public class TestJoinThread {
public static void main(String[] args) {
Thread t1 = new Thread(new B());
Thread t = new Thread(new A(t1));
t.start();
t1.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i ==2){
try {
t.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
线程联合案例
需求:
实现爸爸让儿子买烟。
/**
* 儿子买烟线程
*/
class SonThread implements Runnable{
@Override
public void run() { System.out.println("儿子出门买烟");
System.out.println("儿子买烟需要10分钟");
for(int i=0;i<10;i++){
System.out.println("第"+i+"分钟");
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("儿子买烟回来了");
}
}
/**
* 爸爸抽烟线程
*/
class FatherThread implements Runnable{
@Override
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完了");
System.out.println("爸爸让儿子去买一包红塔山");
Thread t = new Thread(new SonThread());
t.start();
System.out.println("等待儿子买烟回来");
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("爸爸出门找儿子");
System.exit(1);
}
System.out.println("爸爸高兴的接过烟,并把零钱给了儿子");
}
}
public class TestJoinDemo {
public static void main(String[] args) {
System.out.println("爸爸和儿子买烟的故事");
Thread t = new Thread(new
FatherThread());
t.start();
}
}
Thread类中的其他常用方法
获取当前线程名称
方式一
this.getName()获取线程名称,该方法适用于继承
Thread
实现多线程方式。
class GetName1 extends Thread{
@Override
public void run() {
System.out.println(this.getName());
}
}
方式二
Thread.currentThread().getName()获取线程名称,该方法适用于 实现Runnable
接口实现多线程方式。
class GetName2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
设置线程的名称
方式一
通过构造方法设置线程名称。
方式二
通过setName()
方法设置线程名称。
thread
.
setName
(
"SetName2"
);
|
判断线程是否存活
isAlive()
方法: 判断当前的线程是否处于活动状态。
活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。
class Alive implements Runnable{
@Override
public void run() {
for(int i=0;i<4;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public class TestAliveThread {
public static void main(String[] args) {
Thread thread = new Thread(new Alive());
thread.setName("Alive");
thread.start();
System.out.println(thread.getName()+""+thread.isAlive());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName()+""+thread.isAlive());
}
}
线程的优先级
什么是线程的优先级
每一个线程都是有优先级的,我们可以为每个线程定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程的优先级用数字表示,范围从1到
10
,一个线程的缺省优
先级是
5
。
Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。
注意
线程的优先级,不是说哪个线程优先执行,如果设置某个线程的优先级高。那就是有可能被执行的概率高。并不是优先执行。
线程优先级的使用
使用下列方法获得或设置线程对象的优先级。
- int getPriority();
- void setPriority(int newPriority);
注意:
优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。