java并发-线程相关方法

线程通知与等待

wait()/wait(long timeout)

当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:
(1)其他线程调用了该共享对象的notify()或者notifyAll()方法;
(2)其他线程调用了该线程的interrupt()方法,该线程抛出 InterruptedException异常返回。

// 生产者线程
synchronized(queue) {
	// 消费队列满,则等待队列空闲
	while(queue.size() == MAX_SIZE) {
		try{
			// 挂起当前线程,并释放同步块获取的queue上的锁,让消费者线程可以获取该锁
			// 然后获取队列里面的元素
			queue.wait();
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
	// 空闲则生成元素,并通知消费者线程
	queue.add(ele);
	queue.notifyAll();
}
// 消费者线程
synchronized(queue) {
	// 消费队列为空
	while(queue.size() == 0) {
    	try{
			// 挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取锁
			// 将生产元素放入队列
			queue.wait();
		}catch(Exception ex) {
			ex.printStackTrace();
		}
		// 消费元素,并通知唤醒生产者线程
		queue.take();
		queue.notifyAll();
    }
}

notify()/notifyAll()

一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的,被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行

notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程

private static volatile Object resouceA = new Object();
   public static void main(String[] args) throws InterruptedException {
       Thread threadA = new Thread(()->{
           synchronized (resouceA){
               System.out.println("threadA get resourceA lock");
               try {
                   System.out.println("threadA begin wait");
                   resouceA.wait();
                   System.out.println("threadA end wait");
               }catch (InterruptedException e){
                   e.printStackTrace();
               }
           }
       });
       Thread threadB = new Thread(()->{
           synchronized (resouceA){
               System.out.println("threadB get resourceA lock");
               try {
                   System.out.println("threadB begin wait");
                   resouceA.wait();
                   System.out.println("threadA end wait");
               }catch (InterruptedException e){
                   e.printStackTrace();
               }
           }
       });
       Thread threadC = new Thread(()->{
           synchronized (resouceA){
               System.out.println("threadC begin notify");
               // 调用notify 线程A,线程B,只有一个线程唤醒
               resouceA.notify();
               // 调用notifyAll 线程A和线程B 都唤醒
               // resouceA.notifyAll()
           }
       });
       threadA.start();
       threadB.start();
       Thread.sleep(1000);
       threadC.start();
       threadA.join();
       threadB.join();
       threadC.join();
   }

只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的notify()方法,否则会抛出IllegalMonitorStateException异常

join()

java.lang.Thread#join()throws InterruptedException 等待线程执行终止join,可以响应中断

  public static void main(String[] args) {
        Thread threadA = new Thread(()->{
            System.out.println("threadA  begin run ");
            for(;;){}
        });
        final Thread mainThread = Thread.currentThread();
        Thread threadB = new Thread(()->{
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            // 中断主线程
            mainThread.interrupt();
        });
        threadA.start();
        threadB.start();
        try {
            // 等待threadA执行结束
            threadA.join();
        }catch (InterruptedException e) {
            System.out.println("Main thread"+e);
        }
        System.out.println("main end");
    }
threadA  begin run 
Main threadjava.lang.InterruptedException
main end

sleep()

Thread.sleep(1000) 让线程睡眠,可以响应中断
当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回

yelid

Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示。

当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权

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

线程上下文切换

在多线程编程中,线程个数一般都大于CPU个数,而每个CPU同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的,CPU资源的分配采用了时间片轮转的策略,也就是给每个线程分配一个时间片,线程在时间片内占用CPU执行任务。当前线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程占用,这就是上下文切换,从当前线程的上下文切换到了其他线程。
那么就有一个问题,让出CPU的线程等下次轮到自己占有CPU时如何知道自己之前运行到哪里了?
所以在切换线程上下文时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。
线程上下文切换时机有:当前线程的CPU时间片使用完处于就绪状态时,当前线程被其他线程中断时

线程中断

  • void interrupt()
    中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException异常而返回
    线程通过检查此时是否被中断来进行相应,可以通过isInterrupted()来判断是否被中断。

  • boolean isInterrupted()
    检测当前线程是否被中断,如果是返回true,否则返回false

  • Thread.interrupted()
    检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志.

使用Interrupted优雅退

public void run {
	try{
	  // 线程推出条件
	  while(!Thread.currentThread().isInterrupted()&& other){
	  	// do more work
	  }
	}catch(InterruptedException e) {

	}
}

守护线程和用户线程

Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程

t.setDaemon(true); //设置守护线程

区别: 主线线程结束后,用户子线程继续运行,守护线程立即结束。

线程死锁

死锁的产生必须具备以下四个条件
● 互斥条件:指线程对已经获取到的资源进行排它性使用,即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
● 请求并持有条件:指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。
● 不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源。
● 环路等待条件:指在发生死锁时,必然存在一个线程—资源的环形链,即线程集合{T0,T1, T2, …, Tn}中的T0正在等待一个T1占用的资源,T1正在等待T2占用的资源,……Tn正在等待已被T0占用的资源。

可以通过线程堆栈查看死锁情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值