一、概述
1、进程:对应的是一个应用程序在内存中的所属空间。进程是不直接执行的,它只是在分配该应用程序的内存空间
注:
(1)如果一个程序在内存中开辟了空间,就代表它在运行。不运行要释放空间
(2)关于进程
a). 进程是程序(任务)的执行过程 -- 动态性
b). 进程持有资源(共享内存、共享线程)和线程 -- 载体
c). 线程是系统中最小的执行单元,线程共享进程的资源
2、线程:进程中的一个负责程序执行的控制单元,也叫执行路径。一个进程中可以有多个执行路径,称之为多线程。一个进程中至少要有一个线程(线程是负责执行的)
3、开启多个线程是为了同时运行多部分代码。每一个线程都有自己运行的内容,这个内容称为线程要执行的任务
4、多线程的好处与弊端
(1)好处:解决了多部分同时运行的问题
(2)弊端:线程太多会导致效率的降低
注:某一时刻,只有一个程序的一个执行路径在执行
5、应用程序的执行,都是CUP在做着快速的切换完成的。这个切换是随机的,依赖于时间片(CPU负责内存中程序的运行,做快速随机的切换动作)
二、java.lang.Thread
1、public class Thread extends Object implements Runnable:线程是程序中的执行线程。Java虚拟机允许应用程序并发地运行多个执行线程
2、每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称
3、字段
优先级:指能获取到CPU执行权的几率。优先级越大,获取的几率越高。范围:1--10
(1)static int MAX_PRIORITY:线程可以具有的最高优先级(10)
(2)static int MIN_PRIORITY:线程可以具有的最低优先级(1)
(3)static int NORM_PRIORITY:分配给线程的默认优先级(5)
4、构造函数
(1)Thread():分配新的Thread对象。这种构造方法与Thread(null, null, gname)具有相同的作用,其中gname是一个新生成的名称。自动生成的名称的形式为“Thread-”+n,其中的n为整数
(2)Thread(Runnable target)
(3)Thread(Runnable target, String name)
(4)Thread(String name)
(5)Thread(ThreadGroup group, Runnable target)
(6)Thread(ThreadGroup group, Runnable target, String name):分配新的Thread对象,以便将target作为其运行对象,将指定的name作为其名称,并作为group所引用的线程组的一员
(7)Thread(ThreadGroup group, Runnable target, String name, long stackSize):分配新的Thread对象,以便将target作为其运行对象,将指定的name作为其名称,作为group所引用的线程组的一员,并具有指定的堆栈大小
(8)Thread(ThreadGroup group, String name)
5、方法
(1)static Thread currentThread():返回对当前正在执行的线程对象的引用
(2)void start():使该线程开始执行。Java虚拟机调用该线程的run()方法。结果是两个线程并发地运行:当前线程(从调用返回给start()方法)和另一个线程(执行其run()方法)
注:多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动
(3)void run():如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run()方法。否则,该方法不执行任何操作并返回。Thread的子类应该重写该方法
(4)void interrupt():中断线程。可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格
(5)static boolean interrupted():测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false。线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回false的方法反映出来
(6)boolean isInterrupted():测试线程是否已经中断。线程的中断状态不受该方法的影响。线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回false的方法反映出来
(7)static void sleep(long millis) throws InterruptedException:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权
(8)static void sleep(long millis, int nanos) throws InterruptedException:在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权
(9)static void yield():暂停当前正在执行的线程对象,并执行其他线程(当前运行线程释放处理器资源)
注:执行到Thread.yield(),释放执行权,让其他线程和该线程一样有机会再次获取执行权
(10)final void join() throws InterruptedException:等待该线程终止(使其他线程等待当前线程终止)
(11)final void join(long millis) throws InterruptedException:等待该线程终止的时间最长为millis毫秒。超时为0意味着要一直等下去
(12)final void join(long millis, int nanos) throws InterruptedException:等待该线程终止的时间最长为millis毫秒+nanos纳秒
(13)final void checkAccess():判定当前运行的线程是否有权修改该线程。如果有安全管理器,则调用其checkAccess()方法,并将该线程作为其参数。这可能导致抛出SecurityException
(14)long getId():返回该线程的标识符。线程ID是一个正的long数,在创建该线程时生成。线程ID是唯一的,并终生不变。线程终止时,该线程ID可以被重新使用
(15)final String getName():返回该线程的名称
(16)final void setName(String name):改变线程名称,使之与参数name相同。首先调用线程的checkAccess()方法,且不带任何参数。这可能抛出SecurityException
(17)final int getPriority():返回线程的优先级
(18)final void setPriority(int newPriority):更改线程的优先级。首先调用线程的checkAccess()方法,且不带任何参数。这可能抛出SecurityException。在其他情况下,线程优先级被设定为指定的newPriority和该线程的线程组的最大允许优先级相比较小的一个
//将线程t的优先级设置为最大优先级
t.setPriority(Thread.MAX_PRIORITY);
(19)final boolean isDaemon():测试该线程是否为守护线程
(20)final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。该方法首先调用该线程的checkAccess()方法,且不带任何参数。这可能抛出SecurityException(在当前线程中)
(21)final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态
(22)static int activeCount():返回当前线程的线程组中活动线程的数目
(23)Thread.State getState():返回该线程的状态。该方法用于监视系统状态,不用于同步控制
(24)final ThreadGroup getThreadGroup():返回该线程所属的线程组。如果该线程已经终止(停止运行),该方法则返回null
(25)StackTraceElement[] getStackTrace():返回一个表示该线程堆栈转储的堆栈跟踪元素数组。如果该线程尚未启动或已经终止,则该方法将返回一个零长度数组。如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该序列中最新的方法调用。最后一个元素代表堆栈底,是该序列中最旧的方法调用
(26)static Map<Thread, StackTraceElement[]> getAllStackTraces():返回所有活动线程的堆栈跟踪的一个映射。映射键是线程,而每个映射值都是一个StackTraceElement数组,该数组表示相应Thread的堆栈转储。返回的堆栈跟踪的格式都是针对getStackTrace()方法指定的
注:在调用该方法的同时,线程可能也在执行。每个线程的堆栈跟踪仅代表一个快照,并且每个堆栈跟踪都可以在不同时间获得。如果虚拟机没有线程的堆栈跟踪信息,则映射值中将返回一个零长度数组。如果有安全管理器,则通过RuntimePermission("getStackTrace")权限和RuntimePermission("modifyThreadGroup")权限调用其checkPermission()方法,查看是否可以获取所有线程的堆栈跟踪
(27)static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler():返回线程由于未捕获到异常而突然终止时调用的默认处理程序。如果返回值为null,则没有默认处理程序
(28)static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。未捕获到的异常处理首先由线程控制,然后由线程的ThreadGroup对象控制,最后由未捕获到的默认异常处理程序控制。如果线程不设置明确的未捕获到的异常处理程序,并且该线程的线程组(包括父线程组)未特别指定其uncaughtException方法,则将调用默认处理程序的uncaughtException方法。通过设置未捕获到的默认异常处理程序,应用程序可以为那些已经接受系统提供的任何“默认”行为的线程改变未捕获到的异常处理方式(如记录到某一特定设备或文件)
注:未捕获到的默认异常处理程序通常不应顺从该线程的ThreadGroup对象,因为这可能导致无限递归
(29)Thread.UncaughtExceptionHandler getUncaughtExceptionHandler():返回该线程由于未捕获到异常而突然终止时调用的处理程序。如果该线程尚未明确设置未捕获到的异常处理程序,则返回该线程的ThreadGroup对象,除非该线程已经终止,在这种情况下,将返回null
(30)void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):设置该线程由于未捕获到异常而突然终止时调用的处理程序。通过明确设置未捕获到的异常处理程序,线程可以完全控制它对未捕获到的异常作出响应的方式。如果没有设置这样的处理程序,则该线程的ThreadGroup对象将充当其处理程序
(31)ClassLoader getContextClassLoader():返回该线程的上下文ClassLoader。上下文ClassLoader由线程创建者提供,供运行于该线程中的代码在加载类和资源时使用。如果未设定,则默认为父线程的ClassLoader上下文。原始线程的上下文ClassLoader通常设定为用于加载应用程序的类加载器
(32)void setContextClassLoader(ClassLoader cl):设置该线程的上下文ClassLoader。上下文ClassLoader可以在创建线程设置,并允许创建者在加载类和资源时向该线程中运行的代码提供适当的类加载器。如果有安全管理器,则通过RuntimePermission("setContextClassLoader")权限调用其checkPermission()方法,查看是否可以设置上下文ClassLoader
(33)static void dumpStack():将当前线程的堆栈跟踪打印至标准错误流。该方法仅用于调试
(34)static boolean holdsLock(Object obj):当且仅当当前线程在指定的对象上保持监视器锁时,才返回true。该方法旨在使程序能够断言当前线程已经保持一个指定的锁:assert Thread.holdsLock(obj);
(35)static int enumerate(Thread[] tarray):将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。该方法只调用当前线程的线程组的enumerate方法,且带有数组参数
(36)String toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组(意味着线程对象本身就具备着对应的字符串表现形式,而该表现形式来自于Object的覆盖)
三、多线程的创建方式之一--继承Thread类
1、通过继承Thread类创建多线程的步骤:
(1)定义一个类继承Thread类
(2)覆盖Thread类中的run()方法,将线程要运行的自定义的任务代码封装到run()方法中
(3)创建Thread类的子类对象,创建线程
(4)调用start()方法开启线程,并调用线程的任务run()方法执行
注:如果没有调用start()方法开启线程,而是直接调用run()方法,就只有一个线程(主线程)。此时会按顺序执行代码
class ZiThread extends Thread {
/**
* 继承Thread类,覆盖其run()方法,自定义新线程需要执行的任务代码
*/
@Override
public void run() {
//自定义任务代码
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "......" + i);
}
}
}
public class Test {
//主线程
public static void main(String[] args) {
//创建Thread的子类对象
ZiThread zt1 = new ZiThread();
ZiThread zt2 = new ZiThread();
//开启线程,JVM自动调用run()方法执行
zt1.start();
zt2.start();
}
}
2、start()方法被调用后,会去做两件事情:
(1)开启线程
(2)调用run()方法
即 使该线程开始执行;Java虚拟机调用该线程的run()方法
3、开启多线程后,控制台输出的顺序未改变,可调大范围再次尝试。因为CPU的执行速度很快,可能还没来得及切换线程就已经运行完了。如果调大范围后,控制台输出的顺序依旧未改变,此时需要考虑程序是否有问题,有几个线程在运行,线程是否开启等
四、java.lang.Runnable
1、interface Runnable:Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法
2、Runnable为非Thread子类的类提供了一种激活方式。通过实例化某个Thread实例并将自身作为运行目标,就可以运行实现Runnable的类而无需创建Thread的子类。大多数情况下,如果只想重写run()方法,而不重写其他Thread方法,那么应使用Runnable接口。除非打算修改或增强类的基本行为,否则不应为该类创建子类
注:
(1)Thread类也实现了Runnable接口,并对其run()方法进行了默认实现
(2)如果不是线程体系,就不要继承Thread类。需要扩展的功能,首选使用接口
(3)Runnable的出现,本身就是将线程任务进行对象的封装(将任务封装成对象,这是一个思想的变化)
3、方法
(1)void run():使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法。方法run()的常规协定是,它可能执行任何所需的动作
注:Runnable接口只有一个方法run(),用来封装线程任务
五、多线程的创建方式之二--实现Runnable接口
1、通过实现Runnable接口创建多线程的步骤:
(1)定义一个类实现Runnable接口
(2)覆盖接口中的run()方法,将线程要运行的自定义的任务代码封装到run()方法中
(3)通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
(4)调用线程对象的start()方法开启线程
注:一个对象如果没有继承Thread,它就不是线程对象,就不能调用start()开启线程
class ZiRunnable implements Runnable {
/**
* 实现Runnable接口,覆盖其run()方法,自定义新线程需要执行的任务代码
*/
@Override
public void run() {
//自定义任务代码
show();
}
/**
* ZiRunnable中原有的方法
* 因为要使用多线程,所以在run()中调用show()方法,而不需要将show()中的代码复制到run()中
*/
public void show() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "......" + i);
}
}
}
public class Test {
//主线程
public static void main(String[] args) {
ZiRunnable zr = new ZiRunnable();
//创建Thread对象,并将Runnable接口的子类对象作为参数传递
Thread t1 = new Thread(zr);
Thread t2 = new Thread(zr);
//开启线程,JVM自动调用run()方法执行
t1.start();
t2.start();
}
}
2、为什么把Runnable接口的子类对象传给Thread类的构造函数?
因为线程的任务封装在Runnable接口的子类对象的run()方法中,而线程对象在创建时就必须明确要运行的任务(如果不给Thread的构造函数传参,线程要运行的任务是Thread类的run()方法中定义的内容)
3、Thread类的简化源码及各情况分析
class Thread {
private Runnable r;
Thread() {
}
Thread(Runnable r) {
this.r = r;
}
public void run() {
if (r != null) {
r.run();
}
}
public void start() {
run();
}
}
/**
* 情况一:通过继承Thread类创建多线程
*/
class ZiThread extends Thread {
@Override
public void run() {
//......
}
}
/**
分析:
public static void main(String[] args) {
ZiThread zt = new ZiThread();
zt.start();
}
zt.start():子类对象调用start(),但子类本身没有start(),所以执行父类Thread中的start()方法
接着在start()方法体中调用run()方法,根据动态绑定,调用子类中的run()方法,与父类Thread中的run()无关
实际执行的是:子类中覆盖父类Thread的run()方法
*/
/**
* 情况二:通过实现Runnable接口创建多线程
*/
class ZiRunnable implements Runnable {
@Override
public void run() {
//......
}
}
/**
分析:
public static void main(String[] args) {
Thread t = new Thread();
t.start();
}
new Thread():创建Thread对象,调用空参构造函数。Runnable r未被赋值,为null
t.start():调用Thread的start()方法,接着在start()方法体中调用run()方法
执行Thread的run()方法,r为null,执行run()的方法体什么都没有做
public static void main(String[] args) {
ZiRunnable zr = new ZiRunnable();
Thread t = new Thread(zr);
t.start();
}
new Thread(zr):创建Thread对象,调用带Runnable参数的构造函数,传入zr。Runnable r被赋值为zr
t.start():调用Thread的start()方法,接着在start()方法体中调用run()方法
执行Thread的run()方法,r为zr,在run()方法体中调用zr的run()
实际执行的是:Runnable子类对象中的run()方法
*/
4、通过实现Runnable接口创建多线程的好处
(1)将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想,将任务封装成对象
(2)避免了java单继承的局限性
实际开发中,通过实现Runnable接口创建多线程较为常用
六、线程的名称
1、在创建线程子类对象时,该线程子类对象就完成了名称的定义。格式为:Thread-编号,编号从0开始(线程一创建就带着编号)
2、final String getName():获取线程的名称
3、static Thread currentThread():返回对当前正在执行的线程对象的引用
//获取当前运行线程对象
Thread t = Thread.currentThread();
//获取当前运行线程的名称
Thread.currentThread().getName();
4、Thread(String name):Thread类的构造函数之一,为新创建的线程指定名称name
class ZiThread extends Thread {
private String name;
ZiThread() {
}
ZiThread(String name) {
//显示调用父类Thread的构造函数Thread(String name),为新创建的线程指定名称name
//如果此处不显示调用super(name),默认为super(),则新创建的线程名称为默认的 Thread+编号
// super(name);
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i + "......" + Thread.currentThread().getName());
}
}
}
public class Test {
//主线程的名字就是main
public static void main(String[] args) {
//传入为新创建线程指定的名称
// 一定要执行父类Thread的Thread(String name)构造函数才有效,否则还是默认名称
ZiThread zt1 = new ZiThread("zhangsan");
ZiThread zt2 = new ZiThread("xiaoqiang");
zt1.start();
zt2.start();
System.out.println("over......" + Thread.currentThread().getName());
}
}
七、线程的状态
1、主函数结束了,JVM不一定结束。只要还有正在运行的线程,JVM就会存在
2、每个线程在栈内存中都有自己独立的空间,每个run()方法都有自己所属的线程的栈区。一个线程出现异常,该线程挂掉,但不影响其他线程的执行
3、线程的常见状态:
(1)被创建:创建不代表运行,必须有start()开启才有资格运行
(2)运行:既具备着CPU的执行资格,又具备着CPU的执行权
(3)临时阻塞(等待):具备着执行资格,但不具备执行权,正在等待执行权
(4)冻结:在释放执行权的同时释放执行资格。特点:线程不再运行,但是还存活
(5)消亡
注:
(1)执行资格:可以被CPU处理,在处理队列中排队(会因CPU切换到而执行)
(2)执行权:正在被CPU处理
4、线程一开启,不管开启多少个,其中只有一个是具备执行权的。执行权在不断做切换,切到谁,谁就具有执行权,没被切到的都是临时阻塞状态
5、线程的状态转换图
说明:
(1)sleep(time):线程的睡眠方法,必须指定时间。时间一到,自动醒来
(2)wait():没有参数的情况下,线程醒不过来
(3)notify():唤醒。如果线程被wait(),可用此方式将线程唤醒
6、状态转换说明:
(1)运行和阻塞两种状态不断切换
(2)冻结状态时间到,醒来不一定立即运行
(3)只有获取CPU执行权运行,才有资格调用sleep(time),才会冻结
八、总结
线程开启必须要有任务
(1)要么继承Thread,覆盖Thread的run()方法
(2)要么实现Runnable接口,把任务封装成对象传入