线程生命周期及常用方法的使用

一、守护线程

  • 守护线程是什么?

守护线程是一类比较特殊的线程,一般用于处理后台的工作,比如JDK的垃圾回收线程。

  • 守护线程的作用?

JVM(Java Virtual Machine)java虚拟机在正常情况下退出如下:

那么JVM何时会退出呢?官方的说明:The java virtual machine exits when the only thread running are  all daemon thread.

当运行的线程都是守护进程线程时,java虚拟机退出。如果还有其他的任意一个用户线程还在,JVM就不会退出。当主线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要继续关闭子线程的麻烦。

由此知守护线程具备自动结束生命周期的特点,非守护线程不具备这样的特点。

例如:

创建一个匿名线程作为子线程,让它睡眠1ms,在主线程中睡眠1000ms,

public class test3 {
    public static void main(String[] args) {
        //匿名子线程创建,睡眠1ms
        Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        //主线程睡眠1000ms
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread finished");
    }
}

 运行后发现打印了Main thread finished信息,但是JVM没有结束。因为thread是一个非守护线程,main线程正常运行结束之后,JVM进程并没有退出。

之后将子线程设置为守护线程,ps:守护线程 thread.setDaemon(true)必须在thread.start()之前设置,否则会报错IllegalThreadStateException异常

thread.setDaemon(true);

将thread设置为守护线程,main线程结束生命周期,JVM进程退出。

JVM正常结束。

二、并发与并行

  • 并发:并发指的是多个线程操作同一个资源,不是同时执行,而是交替执行,单核cpu,只不过  cpu时间片很短,执行速度很快,看起来同时执行。
  • 并行:并行才是真正的同时执行,多核cpu。每一个线程使用一个单独的cpu运行。

几个常用的概念:

  • QPS: 每秒能够响应的请求数
  • 吞吐量: 单位时间内能够处理的请求数
  • 平均响应时间: 系统对某一个请求的平均响应时间 QPS = 并发数/平均响应时间
  • 并发用户数: 系统可以承载的最大用户数

三、线程生命周期

1.线程状态

  • new(新建状态):
    
     new关键字创建一个线程对象,它并不是处于一个执行状态,因为还没有start启动线程
  • Runnable(就绪状态) :
    
    线程对象调用start方法,才是在JVM中真正创建了一个线程,线程一经启动并不会立即执行,该状态的所有线程位于就绪线程池中,等待操作系统的资源,如:处理器,获得cpu的使用权
  • Blocked(阻塞状态) :
    
    等待一个监视器锁进入同步代码块或者同步方法,代码块/方法某一时刻只能够有一个线程执行,其他线程只能等待
  • Waiting(等待状态):
     
    Object.wait()/Thread.join()/LockSupport.park()都会使得线程阻塞,从Runnable转换到Waiting状态
  • Timed_Waiting(睡眠状态) :
    
    调用加超时参数的Object.wait(long mills)/Thread.slepp(long mills)/LockSupport.parkNano()/LockSupport.parkUntil()
  • Terminated(终止状态) :
    
    是一个线程的最终状态,线程进入到Terminated状态,意味着该线程的生命周期结束了,下面这些情况都会使得线程进入Terminated状态:
     1)线程执行正常结束
     2)线程运行出错意外结束
     3)JVM crash

2.线程状态之间的转换

四、线程常用方法

1.start

启动一个线程,将线程添加一个线程组中,线程状态会从New状态转换到Runnable状态,然后获取Cpu之后进入Running状态执行run

2.sleep

sleep是一个静态方法,其中存在两个重载函数:

public static native void sleep(long millis) 

public static void sleep(long millis, int nanos)  

使得线程进入睡眠状态,暂停执行,sleep不会放弃monitor lock的所有权

 jdk1.5之后,jdk引入一个枚举TimeUnit,其对sleep方法做了封装,直接使用从而时间单位换算的步骤,比如线程睡眠3h27min8sec88msec

 如:

 new Thread(()->{
            try {
                //Thread.sleep(5000);
                TimeUnit.HOURS.sleep(3);
                TimeUnit.MINUTES.sleep(27);
                TimeUnit.SECONDS.sleep(8);
                TimeUnit.MILLISECONDS.sleep(88);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

3.yeild

yield表示提醒cpu调度器我愿意放弃当前的cpu资源,(属于启发式方法),如果cpu资源不紧张的情况下,会忽略这种提示

 yield和sleep的区别 :

  • jdk1.5之前,yield调用了sleep 
  • sleep使得当前线程暂停执行,不会占用cpu资源 , yield只是对于cpu调度器的一个提示
  • sleep会导致线程短暂的阻塞,yield会使得线程Running->Runnable
  • sleep会捕获到中断异常,yield不会捕获到中断异常

4.join

在线程B中join某个线程A,会使得B线程进入等待,直到线程A结束生命周期,或者达到给定的时间, 在这期间线程B会处于等待状态

例如有A,B,C三个线程 使得A,B,C三个线程顺序打印0~9:

class threadTest extends Thread{
    private Thread thread;
    private String name;

    public threadTest(String name, Thread thread) {
        this.name = name;
        this.thread = thread;
    }

    @Override
    public void run() {
        if(thread != null){
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(name+":"+i);
        }
    }
}

public class test4 {
    public static void main(String[] args) {
        threadTest threadA = new threadTest("threadA:", null);
        threadTest threadB = new threadTest("threadB:", threadA);
        threadTest threadC = new threadTest("threadC:", threadB);
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

5.wait,notify,notifyAll

wait :调用某个对象的wait()方法可以让当前线程阻塞 

notify :调用当前对象notify才能够唤醒这个对象所在的线程 ,随机唤醒其中一个

notifyAll :唤醒所有线程,然后所有线程开始抢锁

 注意:使用这三个方法需要让当前线程拥有当前对象的monitor lock

例如,在线程A中,先调用wait方法释放当前对象的monitor lock ,进入Waiting状态,即当前线程进入到阻塞状态,然后在线程B中利用notify进行唤醒。

public class test5 {
    public static void main(String[] args) {
        String str = new String("test str!");

        new Thread("thread A"){
            @Override
            public void run() {
                synchronized (str){
                    try {
                        str.wait();
                        System.out.println("the current thread is:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread("thread B"){
            @Override
            public void run() {
                synchronized (str){
                    str.notify();
//                    str.notifyAll();
                }
            }
        }.start();
    }
}

6.线程中断方法

每个Java线程都会有一个中断状态位,程序可以检测这个中断状态位判读线程是否执行结束。

1)interrupt()

由线程对象调用,将中断位置置为true

如下方法能够使得当前线程进入阻塞状态,调用interrupt方法可以打断阻塞,因此这种方法被称之为可中断方法 :

  • Object.wait()/wait(long)
  • Thread.sleep(long)/TimUnit.XXX.sleep(long)
  • Thread.join()/Thread.join(long)

2)isInterrupted()

判断当前线程的中断状态位是否为true

3)interrupted()

调用interrupted会擦除中断状态位的标识

使用方法:例如子线程睡眠10s,当发生异常时打印提示信息,主线程睡眠200ms,然后调用interrupt方法,此时子线程被中断,捕获到异常后打印提示信息,wait方法为可打断方法。

public static void main(String[] args) {

        Thread thread = new Thread(){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    System.out.println("I am interrupted!");
                }
            }
        };
        thread.start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        thread.interrupt();
}

 

如果一个线程被interrupt,设置interrupt flag;如果当前线程正在执行可中断方法,调用 interrupt方法,反而导致interrupt flag被清除

 

      Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("test interrupt");
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        //e.printStackTrace();
                        System.out.println(Thread.currentThread().isInterrupted());
                    }
                }
            }
        };
        thread.start();
        thread.interrupt(); //将thread的中断状态位置为true
        System.out.println("Thread interrupt flag: "+thread.isInterrupted());

 开始线程中断位置为true,在捕获到异常后,标志位被置为false

sleep方法会擦除中断状态位,如下,开始标志位为false,调用interrupt方法后应该为true,但是sleep会擦除状态位,会变成false

      Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    try {
                        TimeUnit.MILLISECONDS.sleep(1); //sleep会擦除中断状态位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread is interrupted: "+thread.isInterrupted());
        thread.interrupt();
        System.out.println("Thread is interrupted: "+thread.isInterrupted());

 

interrupted调用时也会擦除中断标志位,持续监控标志位,将它设为守护线程,开始标志位为false,在调用interrupted后变成true,之后又恢复为false,其中发生了一次中断。

   Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println(Thread.interrupted());
                }
            }
        };
        thread.setDaemon(true);
        thread.start();

        try {
            TimeUnit.MILLISECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();

五、并发编程

1.概念

并发编程是为了提高程序的执行速度,在宏观上使得多个任务同时执行,则需要启动多个线程  ,但事实启动多个线程之后,不管针对单核cpu还是多核cpu线程进行上下文切换(cpu 通过)给每一个线程分配时间片,只有说拿到时间片的线程才可以执行,通常时间片很短,所以才会感觉到多个线程在并行操作,存在线程不安全性。一个正确执行的并发程序,满足并发编程的三大特性,原子性、可见性、有序性。

1)原子性:

所谓原子性是指一次或者多次操作中,要么所有的操作全部执行要么所有的操作都不执行,原子操作是不可分割的操作,一个原子操作中间是不会被其他线程所打断的

例如:

  • int a = 10; //1 10赋值给线程工作内存中的变量a                      原子操作
  • a++; //2 拿a 进行a+1 赋值a                                                     非原子操作
  • int b = a; //3 拿a b=a                                                                非原子操作
  • a = a+1; //4 拿a 进行a+1 赋值a                                               非原子操作

2)可见性:

如果在一个线程对共享变量做了修改,那么另外的线程立即可以看到修改后的最新值。

例如:两个线程A与B,A中持续判定localValue的值是否小于5,比较value与localValue是否相等,不等的话打印输出。线程B中获取到value的值,赋给localValue,若小于5,就打印输出并使localValue++,然后将localValue赋给value。之后睡眠线程切换为A进行打印输出。

 private static int value = 0;
    public static void main(String[] args) {
        new Thread("A"){
            @Override
            public void run() {
                int localValue = value;
                while(localValue < 5){
                    if(localValue != value){
                        System.out.println("the value is updated to "+value);
                        localValue = value;
                    }
                }
            }
        }.start();

        new Thread("B"){
            @Override
            public void run() {
                int localValue = value;

                while(localValue < 5){
                    System.out.println("the value will be changed to "+(++localValue));
                    value = localValue;

                    //短暂睡眠,使得A线程进行输出
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

输出结果如下,A线程仅打印了一次,预想是发生一次更新,打印一次。造成这种结果的原因是线程A有一份地址空间,线程B有一份地址空间,他们的值发生更新后没有同步到主存中,也就是不可见。 

3)有序性

有序性指的程序代码在执行过程中的先后顺序,由于编译器或计算机的优化,导致代码的执行未必是开发者编写代码时的顺序。

如:

int x = 1;

int y = 2;

x++;

计算机或编译器在执行过程中会进行优化,先执行x=1,然后进行x++操作,在不影响最终结果的情况下速度更快。

 x++在int y=0之前得到执行,这种情况叫做指令重排序

在单线程环境中,无论怎么重排序,都不会影响最终的结果

在多线程环境中,如果有序性得不到保证,最终结果也可能与预期不符 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值