Thread类
1.1 步骤:
- 定义一个Thread类的子类,重写run()方法,将相关逻辑实现,run()方法就是线程要执行的业务逻辑
- 创建自定义的线程类子对象
- 调用子类实例的start()方法来启动线程
1.2 Thread类详解
Thread类也是实现了Runnable接口:
public class Thread implements Runnable{}
Thread类重载了很多构造器:
(1)无参构造函数:如果没有显示的指定线程的名称,那么线程会以“Thread-”作为前缀与一个自增数字进行组合,自增数字在整个JVM进程中将不断自增
(2)将Runnable实现类作为参数传入
Thread中常用的方法:
(1)start()方法:start()用来启动一个线程,当调用start方法后线程并没有立即执行而是处于就绪状态,这个就绪状态是指该线程已经获取了除CPU资源外的其他资源,当获得CPU资源后线程才会执行
(2)run()方法:Thread类中的run()方法是实现了Runnable接口中的run()方法,run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得CPU执行时间,便进入run方法体去执行具体的任务
(3)sleep()方法:sleep相当于让线程睡眠,让出CPU,让CPU去执行其他任务,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的,指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptException异常而返回
sleep有两个版本:
(4)yield()方法:Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际上就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示。
补充:sleep()方法和yield()方法的区别在于,当线程调用sleep()方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程,而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪态,线程调度器下一次调度时就有可能调度到当前线程执行
(5)join()方法:
我们先看一下join方法的源码:
在join方法中,我们发现,它调用了wait()方法
public class joinTest {
public static void main(String[] args) throws InterruptedException{
//创建线程一
Thread threadOne=new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("child threadOne over!");
}
});
Thread threadTwo=new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("child threadTwo over!");
}
});
//启动线程
threadOne.start();
threadTwo.start();
System.out.println("wait all child thread over!");
//等待子线程执行完毕,返回
threadOne.join();
threadTwo.join();
System.out.println("all child thread over!");
}
}
执行结果:
当主线程调用ThreadOne的join()方法时,主线程被阻塞,当执行完线程一时,主线程又调用ThreadTwo的join()方法,主线程再次被阻塞,当等待完线程ThreadTwo执行完毕时,主线程才会继续开始执行
(6)wait()方法:当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起
wait()方法具有三个方式的重载:
1)无参wait()方法:
2)wait(long timeout)函数
该方法比无参wait()方法多了一个超时参数,它的不同之处在于:如果一个线程调用共享变量的wait()方法挂起后,没有在指定的timeout ms时间内被其他线程调用该共享变量的notify()或者notifyAll()唤醒后,那么该函数还是会因为超时而返回
3)wait(long timeout,int nanos)函数
当一个线程调用了一个共享对象的wait()方法时,该调用线程会被阻塞挂起,直到发生 下面几件事之一才会返回:
- 其他线程调用了该共享对象的notify()或者notifyAll()方法
- 其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回
那么一个线程如何才能获得一个共享变量的监视器锁呢
执行synchronied同步代码块时,使用该共享变量作为参数:
synchronized(共享变量){
//dosomething
}
调用该共享变量的方法,并且该方法使用了synchronized修饰
synchronized void add(int a,int b){
//dosomething
}
(7)notify()函数:一个线程在获取到了某共享变量的监视器锁后,可以调用共享变量的notify()方法,接着会唤醒一个在该共享变量上调用wait系列方法后被挂起的函数,一个共享变量可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的
被唤醒的线程的线程不能马上从wait()方法返回并继续执行,它必须在获取了共享对象的监视器锁后才能返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行
(8)notifyAll()函数:不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用wait()方法而被阻塞挂起的线程
我们来看一个例子,看notify()和notifyAll()的区别
public class notifyAllTest {
//创建资源
private static volatile Object resourceA=new Object();
public static void main(String[] args) throws InterruptedException {
//创建线程
Thread threadA=new Thread(new Runnable(){
public void run(){
//获取resourceA共享资源的监视器锁
synchronized (resourceA){
System.out.println("threadA get resourceA lock");
try{
System.out.println("threadA begin wait");
//线程A调用了共享对象resourceA的wait()方法,被阻塞
resourceA.wait();
System.out.println("threadA end wait");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
});
//创建线程
Thread threadB=new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA){
System.out.println("threadB get resourceA lock");
try{
System.out.println("threadB begin wait");
resourceA.wait();
System.out.println("threadB end wait");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
});
//创建线程
Thread threadC=new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("threadC begin notify");
//线程C调用共享资源的notifyAll()方法
resourceA.notify();
}
}
});
//启动线程
threadA.start();
threadB.start();
Thread.sleep(1000);
threadC.start();
//等待线程结束
threadA.join();
threadB.join();
threadC.join();
System.out.println("main over");
}
}
当调用notify()函数时:
当调用notifyAll()时:
(9)interrupt()方法:
interrupt()方法仅是将线程的中断标志设置为true,并不是真正意义上的中断该线程的运行,所以如果没有相应的处理,该线程还是会继续执行下去,比如在while循环中使用**interrupted()或者isInterrupted()**来检查线程的中断标志位,从而做出相应的中断处理
当线程被阻塞的时候,比如线程被Object.wait,Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,==因为没有占用CPU运行的线程是不可能给自己的中断状态置位的,这样就会产生一个InterruptedException异常(该线程必须事先预备好处理此异常,可以使用catch捕获这个异常从而进行相应的处理),从而提早地终结被阻塞状态,==如果线程没有被阻塞,这是调用interrupt()方法将不起作用