多线程-常见方法详解

sleep方法:让线程睡眠

sleep()方法是Thread类的静态方法,调用线程会暂时让出指定时间的CPU执行权,把CPU执行权让给其他线程,等到睡眠时间一到,该函数就会正常返回,此线程会自动苏醒。苏醒后,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。

注意:sleep方法会让出CPU资源,但是不会释放锁.

如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回。

yield方法:让出CPU执行权

yield()方法是Thread的静态方法,它的作用是放弃当前的CPU资源,将它让给其他优先级更高的线程去占用CPU执行时间。但放弃时间不确定,有可能刚刚放弃,马上又获得CPU时间片。这里需要注意的是yield()方法和sleep方法一样,线程并不会让出锁,和wait方法不同

sleep与yield方法的区别在于,当线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程.而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行 。

join方法:等待线程执行终止

在线程操作中,可以使用join()方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。

如果在一个线程A中执行了thread.join()语句,其含义是:当前线程A必须等待thread线程终止之后才从thread.join()返回。

实例代码如下:

public static void main(String[] args){
    //启动子线程
    threadOne.start() ;
    threadTwo.start() ;

    //等待子线程执行完毕,返回
    threadOne.join () ;
    threadTwo.join () ;
    
    System.out.printf("main thread over");
}


如上代码在主线程里面启动了两个子线程,然后分别调用了它们的join()方法,那么主线程首先会在调用threadOne.join()方法后被阻塞,等待threadOne执行完毕后返回.threadOne执行完毕后threadOne.join()就会返回,然后主线程调用threadTwo.join()方法后再次被阻塞,等待threadTwo执行完毕后返回,最后才会执行主线程。

扫描二维码关注公众号,回复: 9259403 查看本文章

join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”做为同步。

join提供了另外两种实现方法:join(long millis)和join(long millis, int nanos),至多等待多长时间而退出等待(释放锁),退出等待之后还可以继续运行。内部是通过wait方法来实现的。

wait, notify, notifyAll:线程的通知与等待

只能在同步方法或者同步块中使用wait()方法。在执行wait()方法后,当前线程释放锁(这点与sleep和yield方法不同)。调用了wait函数的线程会一直等待,直到有其他线程调用了同一个对象的notify或者notifyAll方法才能被唤醒

需要注意的是:被唤醒并不代表立刻获得对象的锁,要等待执行notify()方法的线程执行完,即退出synchronized代码块后,当前线程才会释放锁,而呈wait状态的线程才可以获取该对象锁

而且被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

如果调用wait()或者nofity()方法时没有事先获取该对象的监视器锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕获异常。

notify方法只会(随机)唤醒一个正在等待的线程,而notifyAll方法会唤醒所有正在等待的线程。如果一个对象之前没有调用wait方法,那么调用notify方法是没有任何影响的。

一个需要注意的地方是,在共享变量上调用notifyAll()方法只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。如果调用 notifyAll()方法后一个线程调用了该共享变量的wait()方法而被放入阻塞集合, 则该线程是不会被唤醒的。

带参数的wait(long timeout)或者wait(long timeout, int nanos)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

注意事项

虚假唤醒

另外需要注意的是,一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒。

虽然虚假唤醒在应用实践中很少发生,但要防患于未然,做法就是不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用 wait()方法进行防范。退出循环的条件是满足了唤醒该线程的条件 。

//使用while来避免虚假唤醒
synchronized (obj) {
    while (条件不满足){
        obj.wait();
    }
}

wait只释放当前共享变量上的锁

另外需要注意的是,当前线程调用共享变量的wait()方法后只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的 。

为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,不在同步块中调用会报编译时异常:InterruptedException

当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

为什么这些操作线程的方法要定义在object类中呢?

简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。

专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。

sleep方法和wait方法有什么区别

sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间.

  • 如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wai方法会放弃这个对象的监视器
  • sleep是Thread类的静态方法,谁调用,谁睡觉;wait是Object类中的方法,sleep是Thread类中的方法;
  • 调用wait方法的线程,不会自己唤醒,需要线程调用notify/notifyAll方法唤醒等待池中的所有线程,才会进入就绪队列中等待系统分配资源。sleep方法会自动唤醒,如果时间不到,想要唤醒,可以使用interrupt方法强行打断;
  • sleep可以在任何地方使用,而wait/notify/notifyAll只能在同步控制方法或者同步控制块中使用;
  • sleep和wait必须捕获异常,notify/notifyAll不需要捕获异常;
  • wait通常被用于线程间通信交互,sleep通常被用于暂停执行。

sleep与yield的比较

(1)sleep方法给其它线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield方法只会给相同或更高优先级的线程以运行的机会

(2)sleep方法之后转入阻塞状态,yield方法之后转入就绪状态

(3)sleep方法声明抛出InterruptedException,而yield方法没有声明任何异常;

(4)sleep方法具有更好的可移植性(yield不好控制,只是瞬间放弃CPU的执行权,有可能马上又抢回接着执行,而sleep更容易被控制);

为什么Thread类的sleep()和yield()方法是静态的?

Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

说说 synchronized 关键字和 volatile 关键字的区别

  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证.
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性
  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块.synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞

interrupt方法作用

当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态.

发布了47 篇原创文章 · 获赞 16 · 访问量 3476

猜你喜欢

转载自blog.csdn.net/kaihuishang666/article/details/104190333