Java每天一个知识点+Demo-多线程相关命令

一 多线程相关命令介绍

1 wait()  sleep()

 (1)这两个方法来自不同的类分别是wait()来自Thread,sleep()来自Object。

 (2) 最主要是sleep方法没有释放锁,sleep使当前线程进入停滞状态(阻塞当前线程),让出cpu的使用、目的是不让当前线程   独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会线程虽然休眠了,但是对象的机锁并木有被释放,其         他线程无法访问这个对象(即使睡着也持有对象锁)。

      而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。当一个线程执行到wait()方法时,它就进入到一个和该对   象相关的等待池中,同时失去(释放)了对象锁(暂时失去对象锁,wait(long timeout)超时时间到后还需要返还对象锁),从而    其他线程可以访问;

 (3) wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

               synchronized(x){

                         x.notify()

                       //或者wait()

               }

    synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁标志,进入等待状态。

   (4) sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

2 notify()  notifyAll()

(1) wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

(2) notify()通知等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有线程。

3 join()

    等待该线程终止。

    等待调用join()方法的线程结束。比如t.join(),用于等待t线程运行结束。

4 yield()

暂停当前正在执行的线程。

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行。

二 Demo

1 wait()  sleep()

public class ThreadDemo implements Runnable{
    
    public void do1() throws Exception{
        synchronized(this){
            
             System.out.println("11111111111111111");
        }
    }
    
    public void do2() throws Exception{
        synchronized(this){
            //Thread.sleep(2000);
            this.wait();
            System.out.println("22222222222222222");
        }
    }
    
    @Override
    public void run(){
        try{
            do1();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws Exception {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread t = new Thread(threadDemo);
        t.start();
        threadDemo.do2();
    }
    
}

运行结果:

假如运行的是this.wait(),结果是

11111111111111111

假如运行的是Thread.sleep(2000),结果是

22222222222222222
11111111111111111

分析:

在main()方法里(在主线程)开启了一个子线程(t.start()),接着执行了方法do2()。自此,主线程和子线程交替轮流运行,由于子线程刚启动,所以do2()方法会先被执行。必须注意的是,在方法do1()和do2()中都使用了同步锁(synchronized(this)),且都是同一个对象锁,而do1()运行在子线程,do2()运行在主线程,可想而知,主线程和子线程可能会因为同一个对象锁而发生阻塞。

好的,再来看具体的代码。假如运行的是this.wait(),那么主线程中的do2()走到this.wait(),线程方法wait()会使当前线程也就是主线程进入等待池中也就是线程停滞了,那么其它线程有机会得到cpu调度,而且放弃了对象锁,那么使得其它线程能够得到该对象锁从而能够执行加了同步锁的代码块。(特别注意这里,线程进入等待池和放弃同步锁带来不一样的东西)由于主线程停滞,所以子线程得以运行,由于主线程放弃了同步锁,所以子线程运行到do1()方法时能够进入同步代码块,打印出结果。

假如运行的是Thread.sleep(2000),那么同样首先主线程do2()走到Thread.sleep(2000),那么此时主线程就开始睡眠,但是睡眠的同时也并没有放弃锁,导致的结果是虽然主线程睡眠让子线程有被调度的机会但是主线程还保持着同步锁所以让子线程无法执行do1()里的同步代码块,所以结果是要一直等到2s结束,主线程重新得到调度执行完do2()后放弃了锁,子线程再执行do1()。

2 wait()  notify()  notifyAll()

wait()与notify()

public class ThreadDemo {
    
static class ThreadA extends Thread{
   public ThreadA(String name) {
       super(name);
   }
   public void run() {
       synchronized (this) {
           System.out.println(Thread.currentThread().getName()+"  notify()");
           // 唤醒当前的wait线程
           notify();
       }
   }
}

public static void main(String[] args) {
        ThreadA t1 = new  ThreadA("t1");
        synchronized(t1) {
            try {
                // 启动线程t1
                t1.start();


                // 主线程进入等待池
                System.out.println(Thread.currentThread().getName()+" wait()");
                t1.wait();

               //主线程等待t1通过notify()唤醒
                System.out.println(Thread.currentThread().getName()+" continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

main wait()
t1  notify()
main continue

分析:

主线程通过 new ThreadA("t1") 新建线程t1 ,随后通过synchronized(t1)获取t1对象的同步锁,然后调用t1.start()启动线程t1。
主线程执行t1.wait() 释放t1对象的锁并且进入等待(阻塞)状态。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
线程t1运行之后,通过synchronized(this)获取当前对象的锁;接着调用notify()唤醒当前对象上的等待线程,也就是唤醒主线程。
线程t1运行完毕之后,释放当前对象的锁,紧接着,主线程获取t1对象的锁,然后接着运行。


* 注意jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程。
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在主线程main中。而主线程必须是当前线程,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程是主线程main。因此,t1.wait()是让主线程等待,而不是线程t1。

wait()与notifyAll()

public class ThreadDemo {
    private static Object obj = new Object();
    
    public static void main(String[] args) {
        ThreadA t1 = new ThreadA("t1");
        ThreadA t2 = new ThreadA("t2");
        ThreadA t3 = new ThreadA("t3");
        t1.start();
        t2.start();
        t3.start();
        try {
        System.out.println(Thread.currentThread().getName()+ " sleep(3000)");
        Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (obj) {
        // 唤醒线程
        System.out.println(Thread.currentThread().getName() + " notifyAll()");
        obj.notifyAll();
        }
    }
    
    static class ThreadA extends Thread {
        public ThreadA(String name) {
            super(name);
        }
        public void run() {
            synchronized (obj) {
                try {
                    // 线程进入等待池
                    System.out.println(Thread.currentThread().getName() + " wait");
                     obj.wait();


                    // 唤醒当前的wait线程
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

t1 wait
main sleep    (3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue

分析:

主线程中新建并且启动了3个线程t1, t2和t3。
主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设t1, t2和t3这3个线程都运行了。以t1为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,t2和t3也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒t1, t2和t3这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放obj锁。这样,t1, t2和t3就可以获取obj锁而继续运行了。
 

3 join()

   

public class ThreadDemo extends Thread {

    public ThreadDemo(String name){
        super(name);
    }

    @Override
    public void run(){
        for(int i=1;i<5;i++){
            System.out.println(this.getName() + "    do" + i);
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo t=new ThreadDemo("子线程");
        t.start();
        //调用t线程的join方法,等待t线程执行完毕
        t.join();
        System.out.println("主线程");
    }
}

运行结果:

子线程 do1
子线程 do2
子线程 do3
子线程 do4
主线程

分析:

主线程中新建并且启动了线程t,调用t.join(),主线程会等t线程执行完再执行。

4 yield()

public class ThreadDemo {
    
    public static void main(String[] args) {
        YieldThread t1 = new YieldThread("线程A");
        YieldThread t2 = new YieldThread("线程B");
        t1.start();
        t2.start();
    }
    
   static class YieldThread extends Thread {
        private String name;

        public YieldThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            for (int i = 1; i <= 4; i++) {
                System.out.println(name + "   " + i);
                if (i == 2) {
                 Thread.yield();
                }
           }
       }
    }
}

运行结果:

线程A   1
线程A   2
线程B   1
线程B   2
线程B   3
线程B   4
线程A   3
线程A   4

分析:

这只是其中之一的一个运行结果,随机会有好几种运行结果。当i==2时,执行Thread.yield()会暂停当前线程,进入就绪状态,不过也有可能该线程再次进入运行状态,这也是导致上述demo有多种运行结果的原因之一。

三 总结&注意

(1) 在Java.lang.Thread类中,提供了sleep(),而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程

(2) Thread.sleep()方法是一个静态方法,它暂停的是当前执行的线程。

(3) wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果不在同步条件下使用,会抛出IllegalMonitorStateException异常。

(4)注意持有锁和cpu调度的区别,线程持有锁代表拥有该锁对象对应的资源的使用权,其它线程再无法使用该资源;而cpu调度是cpu调度策略来控制的,与是否持锁并无关系。比如执行sleep时持有锁但不会被cpu调度。

很多知识都是通过前辈博客整理而来,前辈比较好的代码demo也会借鉴用来展示说明,比如介绍比较全面的线程相关命令介绍

猜你喜欢

转载自blog.csdn.net/weixin_42093428/article/details/80822629