学习目标
知识点 要求 |
|
多线程介绍 |
了解 |
线程的创建 |
掌握 |
线程的使用 |
掌握 |
线程的优先级 |
掌握 |
守护线程 |
掌握 |
线程同步 |
掌握 |
线程并发协作 |
掌握 |
“程序(Program)”是一个静态的概念,一般对应于操作系统中的一个可执行文件,比如:我们要启动酷狗听音乐,则需要执行酷狗对应的的可执行程序。当我们双击酷狗的可执 行程序后操作系统会将该程序加载到内存中,开始执行该程序,于是产生了“进程”。
执行中的程序叫做进程(Process),是一个动态的概念。其实进程就是一个在内存中独 立运行的程序空间 。如正在运行的写字板程序就是一个进程。
进程是程序的一次动态执行过程, 占用特定的地址空间。
每个进程由 3 部分组成:cpu、data、code。每个进程都是独立的,保有自己的 cpu 时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这 3 样东西,这样的缺点是:浪费内存,cpu 的负担较重。
多任务(Multitasking)操作系统将 CPU 时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己 独占 CPU 的使用权。
进程的查看
Windows 系统: Ctrl+Alt+Del,启动任务管理器即可查看所有进程。
一个进程可以产生多个线程。同多个进程可以共享操作系统的某些资源一样,同一进程 的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process)。
一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。
一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。
一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的 变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的 变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
线程的启动、中断、消亡,消耗的资源非常少。
一个进程可以包含多个线程。
进程要比线程消耗更多的计算机资源。
进程间不会相互影响,因为它们的空间是完全隔离的。而进程中的一个线程挂掉将 导致整个进程挂掉。
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等 它结束,才能使用这一块内存。
一个进程如果只有一个线程则可以被看作单线程的,如果一个进程内拥有多个线 程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
并发是指在一段时间内同时做多个事情。当有多个线程在运行时,如果只有一个 CPU,这种情况下计算机操作系统会采用并发技术实现并发运行,具体做法是采用“ 时间片轮询算法”,在一个时间段的线程代码运行时,其它线程处于就绪状。这种方式我们称之为并发(Concurrent)。
当Java 程序启动时,一个线程会立刻运行,该线程通常叫做程序的主线程(main thread),即 main 方法对应的线程,它是程序开始时就执行的。
Java 应用程序会有一个 main 方法,是作为某个类的方法出现的。当程序启动时,该方法就会第一个自动的得到执行,并成为程序的主线程。也就是说,main 方法是一个应用的入口,也代表了这个应用的主线程。JVM 在执行 main 方法时,main 方法会进入到栈内存,JVM 会通过操作系统开辟一条 main 方法通向 cpu 的执行路径,cpu 就可以通过这个路径来执行main 方法,而这个路径有一个名字,叫 main(主)线程
主线程的特点:
它是产生其他子线程的线程。
它不一定是最后完成执行的线程,子线程可能在它结束之后还在运行。
在主线程中创建并启动的线程,一般称之为子线程。
在 Java 中使用多线程非常简单,我们先学习如何创建线程,然后再结合案例深入剖析线程的特性。
在 Java 中负责实现线程功能的类是 java.lang.Thread 类。继承 Thread 类实现多线程的步骤:
继承 Thread 类定义线程类。
重写 Thread 类中的 run( )方法。run( )方法也称为线程体。
实例化线程类并通过 start()方法启动线程。
package 包1;
public class TestThread01 extends Thread{
// 构造无参构造方法
/*
* 因为这里继承了线程类,作为线程的子类,所以它在调用构造方法的时候,会返回当前对象(线程)的getName
* */
public TestThread01() {
System.out.println(this.getName());
}
// 当前线程的线程体
@Override
public void run() {
System.out.println(this.getName()+"线程开始! ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i =0; i<10 ;i++){
System.out.println(this.getName()+"----"+i+"执行!");
}
System.out.println(this.getName()+"线程结束!!");
}
// 主方法 (方法中一共有三个线程,thread.main、thread、thread1)
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"线程开始!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里其实就是普通的实例化过程,在这里就是创建线程
TestThread01 test01 = new TestThread01();
// 启动线程
test01.start();
TestThread01 test02 = new TestThread01();
test02.start();
System.out.println("主线程结束!");
}
}
运行结果:
我们可以看到,我们实例化了 两个 线程类 的对象,她们在运行过程中就好比争风吃醋的女生一样不断的抢占着进程资源。
public class TestThread02 implements Runnable{
public TestThread02() {
}
/*
* 构建当前线程的线程体
* */
@Override
public void run() {
for(int i = 0; i<10 ; i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
public static void main(String[] args) {
//因为是实现接口的形式,所以这几=实例化对象的时候并没有同时创建线程
TestThread02 testThread02 = new TestThread02();
// 我们定义一个新的线程,把对象以参数的方式传递进去
Thread thread = new Thread(testThread02);
thread.start();
TestThread02 testThread02_1 = new TestThread02();
Thread thread1 = new Thread(testThread02_1);
thread1.start();
}
}
运行结果:
两个线程其实也是在抢占进程中的资源在运行!
一个线程对象在它的生命周期内,需要经历 5 个状态。
新生状态(New)
用 new 关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用 start 方法进入就绪状态。
就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到 CPU,处于“线程就绪队列”,等待系统为其分配 CPU。就绪状态并不是执行状态,当系统选定一个等待执行的 Thread 对象后,它就会进入执行状态。一旦获得 CPU,线程就进入运行状态并自动调用自己的 run 方法。有 4 中原因会导致线程进入就绪状态:
运行状态(Running)
在运行状态的线程执行自己 run 方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回 到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。
阻塞状态(Blocked)
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。有 4 种原因会导致阻塞:
执行 sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
执行 wait()方法,使当前线程进入阻塞状态。当使用 nofity()方法唤醒这个线程后, 它进入就绪状态。
线程运行时,某个操作进入阻塞状态,比如执行 IO 流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法。
死亡状态(Termiated)
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它 run()方法内的全部工作; 另一个是线程被强制终止,如通过执行 stop() 或 destroy()方法来终止一个线程(注:stop()/destroy()方法已经被 JDK 废弃,不推荐使用)。当一个线程进入死亡状态以后,就不能再回到其它状态了。
下一章是线程的使用