给我十分钟带你过完java多线程所有基础知识

在这里插入图片描述

目录:
1.并发并行与线程进程
2.认识CPU和主线程
3.多线程的三种创建方式
4.三种创建多线程方式的进一步探究和对比
5.匿名内部类的多线程创建
6.多线程内存的分析
7.深度了解线程run()和start()方法的作用
8.获取和设置线程的名字
9.多线程多个窗口卖票的安全问题及三种解决方法
10.线程的五种状态
11.消费者生产者问题
12.线程常用方法总结

1.并发并行与线程进程

(1) 并发并行

并发:指两个或者多个事件在同一个时间段发生交替执行

并行:指两个或者多个在同一时刻发生同时执行
并发就是你有两袋辣条,你想知道两袋辣条哪一个袋好吃,所以你就先打开第一袋吃一口,然后又打开第二袋吃一口,你发现都好吃,所以你就换这吃,吃一口第一袋,再吃一口第二袋,再吃一口第一袋,再第二袋再…,直到把两袋辣条吃完

并行就是你有两袋辣条你分给你女朋友(醒醒你不可能有女朋友)一袋吃,让后你俩同时吃,直到把辣条吃完

并发就是CPU在执任务的时候同时在多个任务之间切换,并行就是同时拥有多个CPU同时执行任务

(2)线程与进程
进程:指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,进程是程序的一次执行过程。

线程:线程是进程的一个执行单位,一个进程可以有多个线程。一个程序运行后至少有一个进程,一个进程可以有多个线程

2.认识CPU和主线程

(1)CPU:中央处理器,对数据进行计算指挥电脑中软件和硬件干活

CPU分为AMD(单核心单线程CPU)和Inter(多核心多线程CPU)具体下图有

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

在这里插入图片描述
对于上图补充解释:
应用程序保存在硬盘中,打开应用程序,在内存中会打开一块空间用于执行应用程序,进入内存的程序叫进程

点击应用程序其中的功能(功能由代码实现),然后就会开启一条应用程序到CPU的执行路径,CPU可以通过路径执行功能,这个路径有个名字叫做线程

(2)执行Main的方法的进程叫做主线程

jvm在执行main方法时,main方法进入栈内存, jvm会让操作系统开辟一条cpu到main方法的路径叫主线程
在这里插入图片描述

3.创建多线程的三种方式

(1)继承Thread类创建多线程

分为四步:

  • 1.定义继承Thread的子类
  • 2.重写该类的run()方法,将线程执行的操作声明在run()的方法里
  • 3.创建Thread类子类的对象
  • 调用start()方法启动线程
例子:
package untl1;
public class MyThread  extends Thread {
    public void run()
    {
        for (int i = 0; i <100 ; i++) {
            System.out.println("我已经数到"+i+"了");
        }
    }
    public static void main(String[] args) {
        MyThread  p=new MyThread();
        p.start();
        for (int i = 0; i <100 ; i++) {
            System.out.println("hello  world!");
        }
    }
}


(2)实现Runable接口

分为五步:

  • 1.定义一个实现Runable接口的类
  • 2.重写run()方法设置线程任务
  • 3.创建实现类对象
  • 4.将创建好的实现类对象传到Thread类的构造器中创建Thread对象
  • 5.调用start()启动多线程

例子;

package untl1;
public class MyThread  implements Runnable {
    public void run()
    {
        for (int i = 0; i <100 ; i++) {
            System.out.println("我已经数到"+i+"了");
        }
    }
    public static void main(String[] args) {
        MyThread  p1=new MyThread();
        Thread p2=new Thread(p1);
        p2.start();
        for (int i = 0; i <100 ; i++) {
            System.out.println("hello  world!");
        }
    }
}

(3)使用Callable和Future创建线程

分为五步:

  • 1.创建一个Callable的实现类,实现call方法,并将此线程的任务放在call方法里
  • 2.创建Callable的实现类对象
  • 3.将此对象传到FuterTask(是Futer的实现类,下一块会讲)构造器里,创建FuterTask对象
  • 4.将创建的FuterTask对象传入Thread的构造器中,创建Thread对象
  • 5.调用FuterTask对象的get()方法获得结束后的返回值,需要我们手动抛出或者捕获异常
例子:
package untl1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyThread  implements Callable {
    public Object call()
    { int i;
        for (i = 0; i <100 ; i++) {
            System.out.println("我已经数到"+i+"了");
        }
        return i;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        MyThread  p1=new MyThread();
        FutureTask  p2=new FutureTask(p1);
       Thread  p3=new Thread(p2);
       p3.start();
       Object a=p2.get();
        for (int i = 0; i <100 ; i++) {
            System.out.println("hello  world!");
        }
        System.out.println("a的值为"+a);
    }
}

4.三种创建多线程方式的进一步探究和对比

(1)第一种创建多线程的方式,为啥要继承Thread呢?是抽象类?

答案当然是NO,你可以直接创建Thread的对象但是run方法里没有线程任务,所以我们需要继承Thread和重写run方法

(2)关于Future和FutureTask的继承关系
在这里插入图片描述
(3) Callable和Future

Callable 接口类似于Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值

Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果

(4)FutureTask

表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包装,由于FutureTask 也是Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。

(5)关于继承Thread和实现Runable的区别:

  • 1.第一种方法直接继承了Thread,因为java是单继承所以,继承了Thread就不能继承其他的类了,第二种没有类的单继承局限性,只是实现了接口而且java支持实现多个接口
  • 2.Runable可以发生多态,即传入Thread构造器中不同类型的对象就会有不同的线程任务
  • 3.在run()方法内获取当前线程,继承Thread方法下可以使用this不用使用Thread.currentThread(),但是在Runable的实现类不能使用this必须使用Thread.currentThread()

(6)实现Runnable和实现Callable的区别

  • 1.Runnable 接口 run()方法无返回值
  • 2.Callable 接口 call()方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  • 3.Runnable 接口 run()方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call() 方法允许抛出和捕获异常,可以获取异常信息

在这里插入图片描述

5.匿名内部类的多线程创建

第一种直接用Thread:

 package acm;
public class acm {
    public static void main(String[] args) {
       new Thread() {
            public void run()
            {
                for (int i = 0; i <10 ; i++) {
                    System.out.println(i);
                }
              
            }unable
        }.start();
    }

第二种new一个Runable接口放在Thread的构造器里边:

package acm;
public class acm {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        }
        ).start();
    }
}

6.多线程内存的分析

package untl1;
public class MyThread {
    public static void main(String[] args) {
      for(int i=11;i<20;i++)
      {
          System.out.println(i);
      }
      Thread p=new person();
      p.run();
      p.start();
    }
}
class person extends Thread{
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(i);
        }
    }
}   

我们随便拿上图多线程的例子来说:
在这里插入图片描述
JVM在执行main方法时找操作系统开辟一条CPU通往main方法的路径,这条路径叫做main线程,同理还会创建另一条路径通往CPU,这条路径就是我们创建的新的线程,两个线程抢夺CPU的使用权,谁抢到执行谁的代码,多个线程之间互不影响,因为所属栈空间不同

两个线程并发进行,抢夺cpu使用权使用时间,使用时间结束后,又开始新一轮的抢夺),java属于抢占式调度,那个线程的优先级高那个就先执行,如果是同一优先级那么随机选择一个执行。

7.线程run()和start()方法

方法名 方法作用
run() 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。run() 方法用于执行线程的运行时代码。可以重复调用
start() 通过调用Thread类的start()方法来启动一个线程 ,而且 start() 只能调用一次。

那么还有一个问题启动多线程为啥不直接调用run方法而是调用start再间接调用run方法?

首先我们要了解两点:
1.start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。

2.run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法

了解之后就可以总结为:
new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作

8.获取和设置线程的名字

(1)获取线程的名字:
第一种Thread类中有getname()方法获得线程名字

 package untl1;
public class MyThread  extends Thread {
    public void run()
    {
       String name=getName();
        System.out.println(name);
    }
    public static void main(String[] args) {
        MyThread  p=new MyThread();
        p.start();
    }
}
  

第二种可以先获得当前所在线程,再使用getname()方法

package untl1;
public class MyThread  extends Thread {
    public void run()
    {
       String name=getName();
        System.out.println(name);
    }
    public static void main(String[] args) {
        MyThread  p=new MyThread();
        p.start();
        Thread  a=Thread.currentThread();
        System.out.println(a.getName());
    }
}

Thread.currentThread()是一个静态方法可以获得当前线程

(2)设置线程的名字
第一种方式:
用对象名.setname("名字”)直接修改

package untl1;
public class MyThread  extends Thread {
    public void run()
    {
        System.out.println("hello  world!");
    }
    public static void main(String[] args) {
        MyThread  p=new MyThread();
        p.start();
        Thread.currentThread().setName("小哥哥线程");
        System.out.println(  Thread.currentThread().getName());
    }
}

第二种方式:
调用父亲的构造方法传参帮子类线程取名字

package untl1;
public class MyThread  extends Thread {
    MyThread()
    {

    }
    MyThread(String  name)
    {
        super(name);
    }
    public void run()
    {
        System.out.println("hello  world!");
    }
    public static void main(String[] args) {
        MyThread  p=new MyThread("小姐姐线程");
        p.start();
        System.out.println(p.getName()
        );
    }
}

9.多线程多个窗口卖票的安全问题

首先我们有三个窗口卖100张票,分别是窗口1窗口2和窗口3
我们使用多线程的对象来模拟三个窗口来卖票
我们为了使三个窗口的票是共享的所以我们把票声明成static
然后进行卖票

如下:

package untl1;
public class MyThread  extends Thread {
    private static  int ticket=100;
    public void run() {
     while(true)
      {
        if(ticket>0)
          {
             System.out.println("第"+Thread.currentThread().getName()+"在卖第"+ticket+"票");
             ticket--;
          }
         else
          {
             System.out.println("第"+Thread.currentThread().getName()+"已经无票可卖");
              break;
          }
       }
    }
    public static void main(String[] args) {
        MyThread  oneThread=new MyThread();
        MyThread  twoThread=new MyThread();
        MyThread  threeThread=new MyThread();
        oneThread.setName("一号窗口");
        twoThread.setName("二号窗口");
        threeThread.setName("三号窗口");
        oneThread.start();
        twoThread.start();
        threeThread.start();

   }
}
     

sleep静态方法是Thread里边的静态方法,以毫秒为单位使程序进入睡眠状态,在run方法中使用的时候要进行捕获异常的操作

由于运行结果太长就不展示了,我总结一下以上代码会出现的问题

1.会出现卖重复的票,卖不存在的票,以及有的票号没有卖出
卖重复的票是因为当cpu在执行到语句后丧失了cpu的使用权(此时还没执行if里边的内容),被另一个线程抢到了cpu的使用权,同样的情况,在没执行if里边的内容就被别的线程抢走使用权。然后第一次的哪个线程抢到使用权后,还没来得及自减就被别的线程抢走了cpu使用权,然后就出现重复。

不存在的票是当三个都进入了if循环,但是此时票只剩下1张,打印结束后还没来得及自减,就被别的线程抢走了cpu的使用权,别的线程又打印0张票,最后一个打印-1张票。
有的票没卖出,就比如99号票,当重复卖出100张票的时候几个,如果两个线程都执行到ticket–,而且在执行这两个tacket–的时候没有卖出过票,那么就会有票漏卖
2.必须使用static才能实现数据的共享

那么如何解决呢

第一个问题,我们称为线程安全问题,我们可以通过java同步机制解决
第二个问题我们可以使用一个实现了Runable接口的类所实例化的对象传给Thread的不同引用,那么数据一样可以共享

我们先用代码解决第二个问题:

package untl1;
public class MyThread  implements Runnable{
    private   int ticket=100;
    public void run() {
        while(true)
        {
          if(ticket>0)
         {
              try {
                  Thread.sleep(50);
           } catch (InterruptedException e) {
                  e.printStackTrace();
               }
           System.out.println("第"+Thread.currentThread().getName()+"在卖第"+ticket+"张票");
                ticket--;
          }
            else
         {
                System.out.println("第"+Thread.currentThread().getName()+"已经无票可卖");
                break;
          }
        }
    }
    public static void main(String[] args) {
        MyThread  myThread=new MyThread();
        Thread  oneThread=new Thread(myThread);
        Thread  twoThread=new Thread(myThread);
        Thread  threeThread=new Thread(myThread);
        oneThread.setName("一号窗口");
        twoThread.setName("二号窗口");
        threeThread.setName("三号窗口");
        oneThread.start();
        twoThread.start();
        threeThread.start();
    }
}

那么解决第一个问题有三种方式

1.同步代码块
2.同步方法
3.锁机制

其实我们要解决的根本问题是当一个线程在执行共享数据的时候,另外的所有线程要等到它执行完成后再执行,那么就不会发生线程安全问题了,所有解决方法都是以此为原理展开的

1.同步代码块

synchronized(锁对象){
这里边存放可能出现线程安全的代码块(访问了共享数据的代码)
}

代码详解:

package untl1;
public class MyThread  implements Runnable{
    private  int ticket=100;
    Object  o=new Object();
    public void run() 
    {
        while (true)
        {
            synchronized (o)
            {
                if(ticket>0)
                {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第"+Thread.currentThread().getName()+"在卖第"+ticket+"张票");
                    ticket--;
                }
                else
                {
                    System.out.println("第"+Thread.currentThread().getName()+"已经无票可卖");
                    break;
                }
            }

        }

    }
    public static void main(String[] args) {
        MyThread  myThread=new MyThread();
        Thread  oneThread=new Thread(myThread);
        Thread  twoThread=new Thread(myThread);
        Thread  threeThread=new Thread(myThread);
        twoThread.setName("二号窗口");
        oneThread.setName("一号窗口");
        threeThread.setName("三号窗口");
        oneThread.start();
        twoThread.start();
        threeThread.start();
    }
}

首先锁对象可以是任意的对象,但是必须保证多线程使用一个锁对象,这就是我把锁对象定义为类元素成员的原因

锁对象的作用:
把同步代码锁住,只让一个线程在同步代码块中进行。

同步技术原理:
使用了一个锁对象,这个锁叫做同步锁,也叫对象锁,也叫对象监视器,三个线程一起抢夺cpu使用权,谁抢到谁执行run卖票,当第一个线程遇到synchronized代码块会检查是否有锁对象,有的话获取锁对象进入同步中执行,(只有拿到锁对象才能进入同步代码块中执行,否则,不能执行)。当第一个线程的代码块没执行完cpu就被第二个线程抢走,那么检查锁对象,结果发现锁对象被第一个线程拿着,所以第二个线程处于阻塞状态,只有第一个线程执行完代码块中的内容才会归还(没执行完不会释放锁),然后执行第二个线程,这样就会避免多线程的安全问题。

同步代码块的优缺点
优点:保证了一个线程执行共享数据里边的内容,保证了线程安全。
缺点:由于程序频繁的判断锁获取锁,释放锁程序效率比较低。

2.同步方法(分为静态方法和非静态方法)

访问修饰符 (static)  synchronized  返回值类型  方法名(){
这里边存放可能出现线程安全的代码块(访问了共享数据的代码)
}

代码详解:

package untl1;
public class MyThread  implements Runnable{
    private  int ticket=100;
    Object  o=new Object();
    public  synchronized void func()
    {
        if(ticket>0)
        {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第"+Thread.currentThread().getName()+"在卖第"+ticket+"张票");
            ticket--;
        }

    }
    public void run()
    {
        while (true)
        {

            if(ticket>0)
            {
                func();
            }
               else
            {
                System.out.println("第"+Thread.currentThread().getName()+"已经无票可卖");
                break;
            }
        }
    }
    public static void main(String[] args) {
        MyThread  myThread=new MyThread();
        Thread  oneThread=new Thread(myThread);
        Thread  twoThread=new Thread(myThread);
        Thread  threeThread=new Thread(myThread);
        twoThread.setName("二号窗口");
        oneThread.setName("一号窗口");
        threeThread.setName("三号窗口");
        oneThread.start();
        twoThread.start();
        threeThread.start();
    }
}

其实同步方法也是利用的锁对象锁住,锁对象就是this,对于上述例子来说就是myThread这个对象

以上是非静态方法,那么静态方法就是方法带上static,并且ticket也要带上static,我们都知道静态方法内没有this对象,所以静态同步方法的锁对象变成本类生成的class文件对象(在反射里讲在这里不必深究了解即可)

3.锁机制
实际上就是利用java中的Lock接口,里边有两个方法:

void lock();获取锁
void unlock();释放锁

在可能出现线程安全的代码块前获取锁,在可能出现线程安全地代码块后释放锁,但是前提也是同一个Lock接口实现类对象

在java中有一个Lock接口实现类ReentrantLock

代码实现:

package untl1;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread  implements Runnable{
    private  int ticket=100;
     ReentrantLock lock=new ReentrantLock();
    public void run() {
        while (true)
        {
            lock.lock();
                if(ticket>0)
                {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第"+Thread.currentThread().getName()+"在卖第"+ticket+"张票");
                    ticket--;
                }
                else
                {
                    System.out.println("第"+Thread.currentThread().getName()+"已经无票可卖");
                    break;
                }
            lock.unlock();

        }

    }
    public static void main(String[] args) {
        MyThread  myThread=new MyThread();
        Thread  oneThread=new Thread(myThread);
        Thread  twoThread=new Thread(myThread);
        Thread  threeThread=new Thread(myThread);
        twoThread.setName("二号窗口");
        oneThread.setName("一号窗口");
        threeThread.setName("三号窗口");
        oneThread.start();
        twoThread.start();
        threeThread.start();
    }
}

在这里插入图片描述

10.线程的五种状态

在这里插入图片描述

线程状态 状态来源
New(新建) 新创建了一个线程对象
Runable(可运行状态也称就绪状态) 线程对象创建后,当调用线程对象的start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
Runing(运行状态) 可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中
Blocked(阻塞状态) 处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。阻塞的情况分三种:(一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting queue)中,使本线程进入到等待阻塞状态;(二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态(三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
Dead(死亡状态) 线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

11.消费者生产者问题

生产者与消费者问题其实就是等待唤醒机制,等待唤醒机制是一种协作机制,平常线程之间都是竞争关系,比如竞争锁,但是并不是所有线程都是竞争关系,就像我们现在讲的协作关系。等待唤醒的一种简单实现是利用以下两个方法
1.wait():生产者或消费者线程停止自己的执行,释放锁,使自己处于等待状态,让其它线程执行。
2.notify():向其他等待的线程发出通知,同时释放锁,使自己处于等待状态,让其它线程执行。

场景:顾客来买包子,卖包子的人没包子了,又由于顾客一年没吃饭了,所以卖包子的人做好一个提醒顾客吃一个(别问为啥卖包子的一次只能做一个)

整体思路:
1.创建一个顾客线程:告知老板要的包子的数量和种类,调用wait()方法,放弃cpu的执行,进入等待状态
2.创建一个老板线程,包子做好后调用notify()方法,唤醒顾客吃包子
3.顾客和老板必须用同步代码块包裹起来,保证等待和唤醒只有一个在执行

代码实现:

package untl1;
public  class acm{
    public static void main(String[] args) {
        Object p=new Object();
        person p1=new person(p);
        Thread p2=new Thread(p1);
        shopower p3=new shopower(p);
        Thread p4=new Thread(p3);
        p2.start();
        p4.start();
    }
}
class person implements Runnable{
    Object p;
    person(Object p)
    {
        this.p=p;
    }
    public void run()
    {
        while(true)
        {
            synchronized (p)
            {
                System.out.println("我要买包子老板");
                try {
                    p.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("吃光了");
            }
        }
    }
}
class  shopower implements  Runnable{
    Object p;
    shopower(Object p)
    {
        this.p=p;
    }
    public void run()
    {

        while(true)
        {
            try {
                Thread.sleep(500);//在父类的run未抛异常子类也不能抛
            } catch (InterruptedException e) {
                e.printStackTrace();}
            synchronized (p)
            {
                System.out.println("包子花五秒做好了,请吃把");
                p.notify();
            }
        }
    }
}

12.线程常用方法总结

1.如图:

方法名 作用
setName (String) ; 设置线程的名称
String getName () 返回线程的名称
start() 启动线程,并执行对象的 run() 方法
run() 线程在被调度时执行的 操作,子线程要执行的代码放入run()方法
interrupt() 中断线程,由运行状态到死亡状态
join(long millis) 等待该线程终止的时间最长为 millis 毫秒。join()方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行注意该方法也需要捕捉异常
sleep(long millis) 静态方法,睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。
yield() 是一种静态的方法,暂停当前正在执行的线程对象,并执行其他线程。
stop() 强制线程生命周期结束,已经废止
wait() 一旦执行此方法,当前线程就进入阻塞状态,并释放锁。而当前线程 排队 等候其他线程调用notify() 或 notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行.
notify() 一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个,如果优先级相同就唤醒等待时间最长的哪一个。
notifyAll() 一旦执行此方法,就会唤醒所有被wait的线程。

以上方法除了wait()、notify()和notifyAll()定义在Object这个类里边其他都定义在Thread里

2.关于notify()和notifyAll()的区别:

这里是引用如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制

3.sleep()方法和 yield()方法有什么区别?

(1) sleep()方法给其他线程运行机会不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会
(2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态
(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。

4.sleep() 和 wait() 两者都可以暂停线程那有什么区别?

1.类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法
2.是否释放锁:sleep() 不释放锁;wait() 释放锁。
3.用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
4.用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45737068/article/details/105717078