Java中线程的生命周期

线程的生命周期的定义

线程的生命周期是线程从生到死的过程中,经历的各种状态及状态转换。

Java线程的状态

Java总共定义了6种状态,全部定义在Thread类的内部枚举类种。分别为NEW; RUNNABLE; BLOCKED; WAITING; TIMED_WAITING; TERMINATED
在这里插入图片描述

线程的6种状态互相转换

在这里插入图片描述
说明过程:

  • NEW新建状态,代码中New一个Thread类对象出来,此时这个线程对象就是一个新建状态。如果用这个线程对象调用了start方法,它就进入了Runnable可运行状态。
  • 调用start方法进入可运行状态是因为它要等CPU调度之后才能跑,调了之后其实是一个所谓的可运行状态。一旦CPU把它调度了,然后假如它执行完毕了,或者在中间执行的时候出现了异常,都会进入终止状态。
  • 可运行状态如果没有得到锁对象,就会进入到锁阻塞。(小明和小红两条线程都在运行,同时抢锁,假如小明没有抢到锁他就会进入锁阻塞,他要等到小红执行完了才能进入)。如果获得锁对象了它会再次进入可运行状态。
  • 如果可运行的线程获得了锁对象之后调用了wait方法把自己等待了,就会进入Waiting无限等待状态。如果别人把这个线程给唤醒了,唤醒了之后它又要去抢锁,如果它还是没有抢到锁又会进入锁阻塞状态。假如抢到了锁就进入可运行状态。
  • 如果线程处于可运行状态,调用sleep就进入了休眠状态,它就进入了Timed Waiting计时等待状态,但注意此时它的锁是不释放的。或者可运行的线程调用wait方法,它也会进入TimedWaiting计时等待状态,这个wait方法可以传以毫秒为单位的参数,假如传了3000,那么就代表它会让自己等待3秒钟,如果3秒中没有人唤醒它,它会自动醒的。但是wait跟sleep的区别在于wait是会释放锁的,所以到时间需要重新抢锁。在wait等待期间如果被唤醒也需要自己重新抢锁,抢到进入可执行,抢不到进入锁阻塞。

悲观锁和乐观锁

提问:假如我既要线程安全又要同时执行,请问阁下该如何应对?
答:使用乐观锁解决

悲观锁

首先回顾悲观锁示例如下:
CountRunnable.java

public class CountRunnable implements Runnable{
    // 没必要声明成静态变量,到时候任务对象就只New一个就好了
    // 记录浏览人数
    private int count;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("count====>" + (++count));
        }
    }
}

OptimisticLockTest.java

public class OptimisticLockTest {
    public static void main(String[] args) {
        // 目标:悲观锁及乐观锁原理
        // 悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入,访问完毕后再解锁。               线程安全,性能较差。
        // 乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候再开始控制。  线程安全,性能较好。

        // 需求:1个变量,100个线程,每个线程对其加100次。(相当于模拟个阅读人数统计)
        Runnable target = new CountRunnable();
        // 任务对象只有1个但是线程对象有100个,每个线程对象执行各自的run方法
        for (int i = 1; i <= 100 ; i++) {
            new Thread(target).start();
        }
    }
}

上述程序是线程不安全的,当运行主程序后会发现出现的结果可能是9997,9998,9999,10000这样子,比较随机。但总体冲突的几率是比较小的
原因说明:假设中途有一次变量变为10了,此时线程1将10拽出来,加个1变成11,与此同时线程2也将10拽出来,加个1也变成11,它俩都放回去,结果还是11,但实际上加了2次,缺失1次。这是导致结果不对的原因,多个线程访问同一个资源出现的线程安全问题。

IDEA中快速包裹结构快捷键:Ctrl + Alt + T
给涉及共享资源的地方加锁:

@Override
public void run() {
    for (int i = 0; i < 100; i++) {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + this + "count====>" + (++count));
        }
    }
}

这里this代表的是target对象,只有一份。最终结果如下:

Thread-65com.thd.optimistic_lock.CountRunnable@34a1e733count====>10000

线程安全问题解决,上述使用的是悲观锁,1001个线程排10000次队。也可以看到线程的抢夺是随机的,上述结果说明最后一次是由65号线程作运算的。

乐观锁

比较和交换算法CAS(Compare and Set)
假设中途有一次变量变为10了,此时线程1将10拽出来,先记录原有的状态10,加个1变成11,这时候再跟刚才记录的状态比较,如果还是10,说明没有人修改,那就将11替换上去。假设在比较的时候已经被改成11了,比较发现不同,就将这次修改作废,继续拿11继续作计算。上述就是乐观锁的机制,因为它并不加锁,所以一般比加锁的性能好很多。

由于乐观锁在Java开发中比较重要,实际上它早已内置了很多乐观锁的技术。典型的乐观锁比如整数修改乐观锁,是用原子类进行实现的。任务类可修改如下:

public class CountRunnable2 implements Runnable{
    // 整数修改的乐观锁:原子类实现的
    private AtomicInteger count = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + this + "count====>" + (count.incrementAndGet()));
        }
    }
}

incrementAndGet()方法翻译过来就是先加个1再拿出来展示,方法内部并没有加锁。看看源码:
在这里插入图片描述
●this代表当前AtomicInteger原子对象,因为任务对象只有一个,所以原子对象也只有一个。
●valueOffset往上看就会发现是原子对象里面的value的地址,根据地址取value的值。
在这里插入图片描述
方法具体实现-使用乐观锁思想
在这里插入图片描述
在这里插入图片描述

练习题

有100份礼品,小红和小明两人同时发送,当剩下的礼品小于10份的时候不再送出,利用多线程模拟该过程并将该线程的名称打印出来,并最后在控制台分别打印小红,小明各自送出多少份礼物。

示例代码如下:
SendThread.java

public class SendThread extends Thread{
    private final List<String> gift;

    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public SendThread(List<String> gift, String name){
        super(name);
        this.gift = gift;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        // 小明 小红发礼物出去,实现线程安全问题
        // 注意:锁必须唯一
        Random r = new Random();
        while (true) {
            // 这里并不能用this作为锁对象因为线程对象不唯一了
            synchronized (gift) {
                if(gift.size()<10){
                    break;
                }
                String rs = gift.remove(r.nextInt(gift.size()));
                System.out.println(name + "send the "+ rs);
                // 这里统计个数对于小红和小明两个线程对象互不影响
                count++;
            }
        }
    }
}

SendGift.java

public class SendGift {
    public static void main(String[] args) throws Exception {
        ArrayList<String> gift = new ArrayList<>();
        String[] names = {"aaaaa","bbbbb","ccccc","ddddd","eeeee"};
        Random r = new Random();
        for (int i = 0; i < 100; i++) {
            gift.add(names[r.nextInt(names.length)] + (i+1));
        }
        System.out.println(gift);

        SendThread xm = new SendThread(gift, "小明");
        xm.start();
        SendThread xh = new SendThread(gift, "小红");
        xh.start();
        
        //需要等上述的线程都执行完毕才能拿最终的统计结果
        xm.join();
        xh.join();

        System.out.println(xm.getCount());
        System.out.println(xh.getCount());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值