线程的创建和启用:
创建新线程的方法:
1、继承Thread类
Thread类的特性:
1、每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。
2、通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
步骤:
1、定义子线程类继承Thread类。
2、子线程类中重写Thread类中的run方法。
3、创建Thread子线程类对象。
4、调用子线程对象start方法:①启动新线程;②调用当前线程run方法。
▲如果想重新新建线程,一个对象不能调用两次start方法,如果新线程run的方法不变需重新new对象来调用start();如果新线程run的方法要改变则需要重新继承Thread类,重新写run方法。
Thread类的方法:
1、start(): 启动线程,并执行对象的run()方法
2、run(): 线程在被调度时执行的操作,如果不适用线程的start方法而直接使用线程的run方法,那么相当于在main线程中执行相关操作。
3、String getName(): 返回线程的名称
4、void setName(String name):设置该线程名称
5、static Thread currentThread(): 返回当前线程类的对象。在Thread子类中就是this,通常用于主线程和Runnable实现类
6、static void yield():线程让步:让当前线程从Runnable的Running进入Runnable的Ready就绪状态,把CPU时间片调度分给优先级相同或更高的线程的概率增加,但是自己也有可能获得CPU时间片调度。若队列中没有其他线程,则CPU时间片调度还是会分给当前线程。
7、static void sleep(long millis):令当前活动线程进入BLOCKED阻塞状态,不占用CPU时间片,阻塞指定时间段。
sleep和yield:
sleep会让线程进入TIMED WAIT状态,不会获得时间片调度,yield进入Runnable的Ready状态,有可能获得时间片调度。
sleep有参数,yield没有参数
8、void join():当线程A中调用其他正在执行线程的join()方法时,线程A将被阻塞,直到其他线程执行完为止(低优先级的线程也可以获得执行)。如果让执行完的线程join等于没有join。这时调用interrupt方法会让当前睡眠方法唤醒并中断并抛出InterruptedException异常。
8、void join(long):当线程A中调用其他正在执行线程的join(long)方法时,线程A将被阻塞,直到其他线程执行完或者阻塞时间到为止。这时调用interrupt方法会让当前睡眠方法唤醒并中断并抛出InterruptedException异常。
9、void interrupt():可以打断阻塞状态和运行状态的线程标记位,线程有一个打断标记位,如果调用interrupt方法则标记位变为true,如果没有调用interrupt方法则标记位为false。如果打断的是阻塞状态的线程,那么标记位会被从true自动重置为false;如果打断的是运行状态的线程,那么标记位不会自动重置,为true。注意:对运行状态Runnable的线程调用interrupt方法不会真正让线程停止运行,线程还会继续执行,只是改变标志位的状态,可以让程序自己通过下面两种方法来控制到底是否停止运行这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。对sleep/wait/join阻塞状态的线程调用interrupt方法会抛出异常,可以在异常中选择是否真正打断。而对park阻塞状态的线程调用interrupt会唤醒线程,改变标记位,不会清除标识位。
10、static boolean interrupted():判断当前线程是否被打断,返回后会清空标记位状态
11、boolean isInterrupted():判断当前线程是否被打断,返回后不会清空标记位状态
12、boolean isAlive():返回boolean,判断线程是否还活着
13、getPriority() :返回线程优先值
14、setPriority(int newPriority) :改变线程的优先级,高优先级的被时间片调度的几率更大。
15、setDaemon(false):将守护线程转变为用户线程
16、setDaemon(true):将用户线程转变为守护线程
Thread类构造器:
Thread(): 创建新的Thread对象
Thread(String threadname): 创建线程并指定线程实例名
Thread(Runnable target): 指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name): 创建新的Thread对象
2、实现Runnable接口
步骤:
1、定义运行的逻辑类,实现Runnable接口。
2、运行逻辑类中重写Runnable接口中的run方法。
3、创建运行逻辑类对象。
4、创建Thread类对象,将Runnable接口的运行逻辑类对象作为实际参数传递给Thread类的构造器中。
5、调用Thread类的start方法:①开启线程;②调用运行逻辑接口的run方法。
▲如果想重新新建线程,一个对象不能调用两次start方法,如果新线程run的方法不变需重新new Thread对象来调用start();如果新线程run的方法要改变则需要新建一个运行逻辑类或直接在Thread构造器中传入新建匿名子类的匿名对象。
▲注意创建线程和调用start方法都是main线程去做,run方法里的才是另一个线程。
▲如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
▲开发中优先使用实现接口的方式:1、打破单继承局限性,脱离了Thread体系,让逻辑实现类还可以实现别的接口。2、天然适合多个线程处理同样的数据(因为继承的方式需要将共享数据声明为static,不然不同子线程不能处理同样数据)。3、增强程序健壮性,代码和数据是独立的。4、更容易与高级API配合,如线程池
▲注意当主线程开启分线程后,main方法中的主线程和其他线程是并发工作的。
3、实现Callable接口
步骤:
1、定义一个运行逻辑类,实现Callable接口。
2、运行逻辑类中重写call()方法,注意此call()方法可以有返回值并能抛出异常。
3、创建运行逻辑类对象,将运行逻辑类对象传入FutureTask构造器,创建FutureTask类的对象。
4、将FutureTask类的对象传入Thread构造器中,创建Thread类的对象。
5、调用Thread类的start方法。
6、若有需要,可获取运行逻辑类中重写call()方法的返回值:futureTask.get(),由于call方法在其他线程中调用,因此会阻塞等待原线程call方法执行完后才返回,体现了同步的概念,该方法也会抛出异常。
为什么Callable接口比Runnable接口更强大?
1、call()方法有返回值,可以使用get方法同步等待获取结果;
2、call()方法能抛出异常;
3、Callable可以支持泛型。
4、使用线程池