并发编程 - Thread

ReentrantLock 在阻塞线程,用的是LockSupport.park()
与这对应,唤醒线程,调用LockSupport.unpark()。
可看源码时,会发现,调用LockSupport.park(),紧接着会调用Thread.interrupted(),Why?

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 阻塞线程
    return Thread.interrupted(); // 清除线程中断标记
}
  1. LockSupport.park(),可通过两种方式被唤醒,LockSupport.unpark() 或者 interrupt()
  2. public void interrupt(){} 给线程打一个中断标志
  3. public boolean isInterrupted(){} 检测下线程是否被中断,不清除中断标志
  4. public static boolean interrupted() {} 也是检测下线程是否被中断,但清除中断标志

t0 线程运行时,第一次循环,调用park()后线程就阻断了,LockSupport.unpark(t0)执行后,阻断解除,会进行第二次循环,再次被阻断,就停着不动了。

public static void main(String[] args) {
    Thread t0 = new Thread(new Runnable() {
        @Override
        public void run() {
            Thread current = Thread.currentThread();
//                LockSupport.unpark(current);
            log.info("current Thread name:{}",current.getName());
            for(int i = 0; i < 3; i++){
                log.info("for循环第{}次,begin",i+1);
                log.info("准备park当前线程:{}",current.getName());
                LockSupport.park();
                log.info("线程{}阻断后又运行了",current.getName());
                log.info("for循环第{}次,end",i+1);
            }
        }
    },"t0");
    t0.start();
    try {
        log.info("休眠…………");
        Thread.sleep(2000);
        log.info("调用LockSupport.unpark方法,唤醒线程{}", t0.getName());
        LockSupport.unpark(t0);
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

一、线程状态转换图

在这里插入图片描述
注意:
调用obj.wait()的线程需要先获取obj的monitor,wait()会释放obj的monitor并进入等待态。所以wait()/notify()都要与synchronized联用。

1.1 阻塞与等待的区别

  1. 阻塞:当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。
  2. 等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,可响应中断。 例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。

需要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。 但话又说回来,虽然等锁时进入的状态不一样,但被唤醒后又都进入runnable态,从行为效果来看又是一样的。

二. 主要操作

2.1 start()

新启一个线程执行其run()方法,一个线程只能start一次。主要是通过调用native start0()来实现。

public synchronized void start() {
 	//判断是否首次启动
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);

    boolean started = false;
    try {
       //启动线程
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

2.2 run()

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

2.3 sleep()

sleep方法有两个重载版本:

sleep(long millis)     //参数为毫秒

sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。

但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

2.4 yield()

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。 但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

2.5 join()

join方法有三个重载版本:

join()
join(long millis)     //参数为毫秒
join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    //0则需要一直等到目标线程run完
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        //如果目标线程未run完且阻塞时间未到,那么调用线程会一直等待。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

2.6 interrupt()

此操作会将线程的中断标志位置位,至于线程作何动作那要看线程了。

  1. 如果线程sleep()、wait()、join()等处于阻塞状态,那么线程会定时检查中断状态位如果发现中断状态位为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断状态位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
  2. 如果线程正在运行、争用synchronized、lock()等,那么是不可中断的,他们会忽略。

可以通过以下三种方式来判断中断:

1)isInterrupted()
此方法只会读取线程的中断标志位,并不会重置。
2)interrupted()
此方法读取线程的中断标志位,并会重置。
3)throw InterruptException
抛出该异常的同时,会重置中断标志位。

原文:https://blog.csdn.net/every__day/article/details/108956415
原文:https://www.cnblogs.com/waterystone/p/4920007.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值