关于线程的面试问题

1,为要有多线程?

现有的程序都是一个人在干活(单线程)–程序的执行效率低,为了提高程序的执行效率,我们可以让多个人共同去完成这一个任务就是多线程程序

1.2 什么是进程?什么 是线程?

进程:

1)进程是并发执行程序在执行过程中资源分配和管理的基本单位(资源分配的最小单位)。
2) 进程可以理解为一个应用程序的执行过程,应用程序一旦执行,就是一个进程。有的程序依赖一个进程,有的程序可以依赖很多进程
3)每个进程都有一个独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段和数据段。

线程:

是操作系统能够执行的最小单位,进程包含线程。
 一个进程的运行至少依赖一个线程-----单线程程序效率低
  一个进程的运行依赖多个线程-----多线程程序效率高
再举个非常贴切的例子,我们大多数人都用过扣扣吧,我们打开一个扣扣,其实就是开启了一个进程,然后我们发送一段文字,那就是开启了一个线程,我们再发送一个语音,那就是又开启了一个线程,那么在这个扣扣的进程中就有发文字和语言两个线程了,当然,可能还有其他的线程!

1.3 进程和线程的区别?

1)地址空间:同一进程的所有线程共享本进程的地址空间,而不同进程之间的地址空间是独立的。
2)资源拥有:同一进程的所有线程共享本进程的资源,如内存,CPU,IO等。进程之间的资源是独立的,如法共享。
3)执行过程:每个进程可以说就是一个可执行的应用程序,每个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。
4)健壮性:因为同一进程的所有线程共享此线程的资源,因此当一个线程发生奔溃时,此进程也会发生奔溃。但是各个进程之间的资源是独立的,因此当一个进程奔溃时,不会影响其他进程。因此进程比线程健壮。
  线程执行开销小,但不利于资源的管理与保护。
 进程的执行开销小,但是可以进行资源的管理与保护。进程可以跨机器前移。

1.4 进程与线程的取决条件?

因为进程是资源分配的基本单位,线程是程序执行的最小单位。以及进程与线程之间的健壮性来考虑。
1,在程序中,如果需要频繁创建和销毁就使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小.
2,如果需要程序更加的稳定安全时,可以选择进程.如果追求速度,就选择线程.

2. 多线程的创建方式

常规来说创建多线程的方式应该有三种:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 匿名内部类

在此之前很有必要说一下这个 main ,也就是在写Java程序中经常见到的主线程,代码表现形式就是

public static void main(String[] args){
do…
}

这个main叫做主线程,是程序的入口,而且是由JVM也就是Java虚拟机创建的.
下面具体说一下创建线程的三种方式
首先是继承自Thread类的方式,看代码

public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
	//3,设置线程名称
	t1.setName("灭霸");
	t2.setName("猪猪侠");
	//4,启动线程
	t1.start();
	t2.start();
 	 		}
	}
  class MyThread extends Thread{
	//1,把所有的业务,写在重写的run()里
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			//2,getName()线程名称
			System.out.println(getId()+super.getName()+"===="+i);
&emsp;&emsp;&emsp;			}	
	&emsp;	}
}

第二种创建线程的方式就是实现Runnable接口的方式


	public static void main(String[] args) {
	MyRunnable target = new MyRunnable();
	   Thread t1 = new Thread(target);
	   Thread t2 = new Thread(target);
	//3,设置线程名称
       t1.setName("灭霸");
	   t2.setName("猪猪侠");
	//4,启动线程
	t1.start();
	t2.start();
	}

}

class MyRunnable implements Runnable{
	//1,把所有的业务,写在重写的run()里
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			//2,Thread类里的静态currentThread()返回当前正在执行任务的线程对象的引用
			System.out.println(Thread.currentThread().getName()+"===="+i);
		}
		
	}
}

接下来看最后一种创建线程的方式,通过匿名内部类的方式


	public static void main(String[] args) {
		System.out.println("主线程在执行...");
		//方法一:
		new Thread(){     //1,继承Thread类
			public void run() { //2,重写run方法
				for(int i=0;i<100;i++) {  //3,将要执行的代码写在run方法中
					System.out.println("匿名内部类创建线程1...");
				}			
			}
		}.start();
		//方法二:
		new Thread(new Runnable() {//1.将Runnable的子类对象传递给Thread的构造方法

			@Override
			public void run() {
				for(int i=0;i<100;i++) {
					System.out.println("匿名内部类创建线程2...");
				}			
			}			
		}).start();//开启线程
	}

}

这里要注意的是,匿名内部类要写在方法中,这里写在住方法中
在这里插入图片描述
也就是说在线程对象中是有一个run方法的,为什么执行线程不可以直接调用这个run方法呢?而要调用start开启线程呢?
其实也很好理解,如果直接调用run方法的情况下,那么这跟平常的类又有什么区别呢?要知道线程是独立的额一个线程,如果直接调用run方法的话不就等同于直接执行这个方法,就类似一个普通的类,然后调用它的一个方法似的,可是,这里可是线程啊。
说的官方一点,对于线程而言,有一个线程规划器的概念,可以理解为就是专门管理线程执行的一个玩意,只有当你调用start,才会将这个线程交给线程规划器去管理,只有交给了线程规划器,才能真正算得上是一个线程,否则,就是一个普通的类,而非线程.
以上说创建线程的三种方式,那么,到底使用哪种比较好呢?实际情况中可能使用实现Runnable接口的方式可能多一点,为什么?
也很简单,因为在Java中,类只能是单继承的,所以如果使用Thread类的方式的话就不能再继承自其他的类了,这在实际开发中势必会带来一些局限性,但是使用实现Runnable接口的方式就可以避免这一局限性,因为接口是支持多实现的,而且还可以再继承其它的类,这样的话,灵活性就高出很多.

到此要知道的几个知识点

什么是线程? 什么是进程?
线程和进程的区别?
创建线程的三种方式
为什么不调用run
使用哪种创建线程的方式更好,为什么

3.线程常用的API

在这里插入图片描述
在这段代码中,跟之前写的创建线程代码没什么区别,就是在打印的时候添加了一个

Thread.currentThread().getName()

很好理解,就是得到当前线程的名称的,看输出结果
在这里插入图片描述
下面再介绍另外一个方法:isAlive()

这个方法是用来判断当前线程是否处于活动状态,那么首先需要知道什么是“活动状态”

所谓的活动状态就是线程已经启动且尚未停止,根据这个理解,看一段代码,以一个线程为例

/**
 * 创建线程的第一种方式
 * 继承自Thread类
 */
class A extends Thread{
    @Override
    public void run() {
        System.out.println("正在执行线程A。。。。"+Thread.currentThread().getName());
        System.out.println("线程的活动状态是:"+Thread.currentThread().isAlive());

    }

}

然后执行

        A a = new A();
        System.out.println("此时线程的状态是:"+Thread.currentThread().isAlive());
        a.start();
        System.out.println("此时线程的状态是:"+Thread.currentThread().isAlive());

重新看一下执行结果
在这里插入图片描述
对照着代码再看执行结果,能够看出对于线程a来说只有当调用了start方法,线程才开始执行,也就是处于活动状态。

此外还有一个比较熟悉的方法就是sleep(),是让线程暂时休眠的一个方法,这里要注意的是是让当前正在执行的线程暂停执行,下面看一个具体的例子

  //执行线程A
        A a = new A();
        a.start();
        System.out.println(System.currentTimeMillis());
        try {
            Thread.sleep(2000);
            System.out.println(System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

代码当中添加了以下代码来让线程休眠2秒

hread.sleep(2000);

然后看打印输出结果
在这里插入图片描述
线程在执行的过程中暂停了2000毫秒也就是2秒钟的时间,这在平常的开发中也有一些特殊的用处,需要用到的时候能够写出来即可。
以上都是在介绍线程的一些常用API,其实还有一个也应该知晓,那就是getId(),这个是用来活的线程的唯一标示的,比如有如下用法
在这里插入图片描述
看打印输出结果
在这里插入图片描述
得出的线程ID则可以作为判定此线程的唯一标示

4,线程的停止

对于线程,它有这样的生命周期,就是新建,就绪,运行,阻塞和消亡

4.1 线程的状态/线程的生命周期
  新建状态: 刚new出来的线程是新建状态
  就绪状态:等待CPU的调度 (即使此时已经调用了start的方法,线程也不会立马执行,必须等到jvm调用run方法线程才会真正的执行,而当前状态则为就绪状态)
  运行状态:正式开始执行任务,就是调用了run方法之后
   阻塞状态:在运行状态如果调用了sleep方法就会处于阻塞状态
  消亡状态(理想状态):也就是线程被停止了

着重说说线程的消亡:调用stop方法可以将线程停止掉,但是,现如今,stop已经不推荐使用了,大多数停止一个线程将采用Thread.interrupt()这个方法。
只要调用了这个方法,线程就会停止,其实,调用了interrupt只相当于给当前线程打上了一个停止的标记,而此时,线程其实并没有真正的停止,而这其中很明显,缺少一些步骤

/**
 * 创建线程的我第一种方式
 * 继承自Thread类
 */
class A extends Thread{
    @Override
    public void run() {
        System.out.println("正在执行线程A。。。。"+Thread.currentThread().getName());
        for (int i=0;i<10;i++){
            System.out.println(i);

        }

    }

}

//执行线程A
        A a = new A();
        a.start();
        a.interrupt();

如果看到上面的代码,会不会以为线程会被停止掉呢?
实际答案是不会,以上并没有真正的去停止线程,而是打上了一个停止的标记,那该怎么做,这里需要加上一个判断
有了如下执行线程的代码

 //执行线程A
      A a = new A();
      a.start();
      //此处打上一个停止的标记
      a.interrupt();

并且已经调用了interrupt,相当于已经给此线程打上了中断的标志,但是此时线程并没有停止,还需要做如下的判断
在这里插入图片描述
在这里插入图片描述
什么意思呢?也就是说在for循环之后的代码还是会执行的,这样来看,这个线程好像就没有真的被停止掉,那么,该怎么处理这种情况呢?

/**
 * 创建线程的我第一种方式
 * 继承自Thread类
 */
class A extends Thread{
    @Override
    public void run() {
        System.out.println("正在执行线程A。。。。"+Thread.currentThread().getName());
        try {
            for (int i=0;i<10;i++){
                if (this.isInterrupted()){
                    System.out.println("线程已经停止");
                    System.out.println("当前线程的状态:"+this.isInterrupted());
                    throw new InterruptedException();
                }
                System.out.println(i);
            }
            System.out.println("此处还会被执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

以上就是一个处理方法,我们在判断到中断标志之后抛出一个中断异常,然后再捕获这个异常,这样就能避免for循环之后的鳄鱼局继续被执行的情况,也就是真正的停止掉线程。

所以对于线程的停止,一个好的方法就是上面这种抛异常的方式了。

5. 线程的安全问题

那么为什么会出现线程安全问题呢?简单来说,及时多个线程同时访问一个共享变量的情况下就会出现线程安全的问题,简单来个例子看一下
售票案例
需求:让四个窗口,一起卖出100张票

package cn.tedu.thread;
		//模拟多线程售票  -- 继承Thread
		public class Test4_Tickets {
			public static void main(String[] args) {
				//4,启动线程
				MyTickets t1 = new MyTickets();
				MyTickets t2 = new MyTickets();
				MyTickets t3 = new MyTickets();
				MyTickets t4 = new MyTickets();
				t1.start();
				t2.start();
				t3.start();
				t4.start();
				//--问题1:总共需要卖出100张票,但是,现在却卖出了400张票.为什么?
				//成员变量tickets,每次实例化时,都会跟着对象初始化一次.4个对象就各自拥有一个tickets,总共变成了400张
				//--问题1的解决方案:把共享资源tickets加static修饰,变成全局唯一全局共享
				//目前来看,程序中的4个线程完美的配合着卖了100张票.
				//但是,数据最终有没有安全隐患--让程序休眠一会儿
			}
		}
		//1,创建多线程类-extends Thread
		class MyTickets extends Thread{
			//--需求:让四个窗口,一起卖出100张票
			static int tickets = 100 ;//定义变量,记录票数
			//2,开始卖票--把业务放入run()
			@Override
			public void run() {
				//一直卖票,卖完结束
				while(true) {//死循环----配置设置好出口!!
					if(tickets>0) {
						//!!! 5,验证多线程中,数据是否安全,都要接受这次考验
						//问题2: 超卖: 卖出了0号票,甚至-1号票
						//问题3: 重卖: 同一张票卖给了好几个
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						//3,打印线程名称
						System.out.println(getName()+"===="+tickets--);
					}else {
						break;//合理的出口!!
					}
				}
			}
		}

运行结果:

在这里插入图片描述
问题总结:
–1,超卖:卖出了0 -1 -2号票
–2,重卖:把同一张票卖给了好几个人

问题分析
	--1,代码
		package cn.tedu.thread;
		//模拟多线程售票  -- 实现Runnable
		public class Test5_Tickets2 {
			public static void main(String[] args) {
				//4,测试
				MyTickets2 target = new MyTickets2();
				Thread t1 = new Thread(target);
				Thread t2 = new Thread(target);
				Thread t3 = new Thread(target);
				Thread t4 = new Thread(target);
				t1.start();
				t2.start();
				t3.start();
				t4.start();
			}
		}
		//1,创建多线程类--implements Runnable
		class MyTickets2 implements Runnable{
			//需求:让四个窗口,一起卖出100张票
			int tickets = 100 ;//定义变量,记录票数
			//2,开始卖票--把业务放入run()
			@Override
			public void run() {
				while(true) {//死循环----配置设置好出口!!
					if(tickets>0) {
						//让程序休眠一会儿,来检测多线程中数据安全隐患
						//问题1: 超卖: 卖出了0号票,甚至-1号票,-2号票
						//问题2: 重卖: 同一张票卖给了好几个人
						try {
				//当tickets=1时,满足了判断条件,t1 t2 t3 t4都准备卖票,一进来4个人就都睡了
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						//3,打印线程名称--Thread.currentThread()
				//超卖的原因:
				//假设t1先醒,此时tickets=1,执行tickets--,输出1,把tickets自减变成0,刚变完
				//假设t2也醒了,此时tickets=0,	执行tickets--,输出0,把tickets自减变成-1,刚变完
				//假设t4也醒了,此时tickets=-1,执行tickets--,输出-1,把tickets自减变成-2,刚变完
				//假设t3也醒了,此时tickets=-2,执行tickets--,输出-2,把tickets自减变成-3		
				
				//重卖的原因:
				//假设t1先醒,此时	tickets=28,执行tickets--,输出28,来没来的及自减变成27呢...
				//假设t2也醒了,此时tickets=28,执行tickets--,输出28,来没来的及自减变成27呢...
				//假设t4也醒了,此时tickets=28,执行tickets--,输出28,把tickets自减变成27
				//假设t4也醒了,此时tickets=27,执行tickets--,输出27....		
				System.out.println(Thread.currentThread().getName()+"=="+tickets--);
					}else {
						break;//合理的出口!!
					}
				}
			}
		}

解决方案—同步锁
什么是同步锁?
同步锁就是把有可能出现问题的代码包起来,一次只让一个线程执行。通过sychronized关键字实现同步。当多个对象操作共享数据时,可以使用同步锁解决线程安全问题。
在这里插入图片描述
  使用关键字synchronized表示同步.使多个线程需要排队访问共享资源,而不是发生抢占现象.
  --实现了同步后,好处是:共享数据安全了.坏处是:效率低了.–牺牲了效率,实现了安全.
   synchronized可以修饰方法,也可以修饰代码块.
–同步方法–使用的锁对象是this
  synchronized public void eat(){}
–同步代码块–锁对象可以任意
  synchronized(对象){
   需要同步的代码;
  }

--implements Runnable
			package cn.tedu.thread;
			//解决 线程安全隐患
			public class Test1_Lock {
			    public static void main(String[] args) {
			        //4,测试
			        MyTickets2 target = new MyTickets2();
			        Thread t1 = new Thread(target);
			        Thread t2 = new Thread(target);
			        Thread t3 = new Thread(target);
			        Thread t4 = new Thread(target);
			        t1.start();
			        t2.start();
			        t3.start();
			        t4.start();
			    }
			}
			class MyTickets2 implements Runnable{
			    //需求:让四个窗口,一起卖出100张票
			    int tickets = 100 ;
			    Object obj = new Object();
			    String s = "jack";
			    //目前程序中,由于在多线程编程中,出现了多个线程抢占资源而造成的数据错乱.
			    //加锁来解决数据安全隐患问题.考虑以下两个问题:
			    //1,锁的位置:把会有问题的代码锁起来,从问题源头开始,到结束为止,都包起来
			    //2,锁的对象:代码块里使用锁,需要考虑锁对象是谁?可以是任意对象,只要是同一个对象就行
			    //3,同步锁也可以直接锁方法,默认是用的锁对象是this
			    //4,如果共享资源是 一个 静态资源,锁对象必须是 类名.class!!!
			//  synchronized public void run() {
			    @Override
			    public void run() {
			        while (true) {
			//            synchronized (new Object()) {//还有问题,四个线程就产生了4个对象!!
			//            synchronized (obj) {//从头到尾都是只有一个对象!!!
			//            synchronized (s) {//从头到尾都是只有一个对象!!!
			            synchronized (this) {//从头到尾都是只有一个对象!!!
			                if (tickets > 0) {
			                    try {
			                        Thread.sleep(10);
			                    } catch (InterruptedException e) {
			                        e.printStackTrace();
			                    }
			                    //3,打印线程名称--Thread.currentThread()
			                    System.out.println(Thread.currentThread().getName() + "==" + tickets--);
			                } else {
			                    break;//合理的出口!!
			                }
			            }
			        }
			    }
			}
--extends Thread
			package cn.tedu.thread;
			//模拟多线程售票  -- 继承Thread
			public class Test4_Tickets {
				public static void main(String[] args) {
					//4,启动线程
					MyTickets t1 = new MyTickets();
					MyTickets t2 = new MyTickets();
					MyTickets t3 = new MyTickets();
					MyTickets t4 = new MyTickets();
					t1.start();
					t2.start();
					t3.start();
					t4.start();
					//--问题1:总共需要卖出100张票,但是,现在却卖出了400张票.为什么?
					//成员变量tickets,每次实例化时,都会跟着对象初始化一次.4个对象就各自拥有一个tickets,总共变成了400张
					//--问题1的解决方案:把共享资源tickets加static修饰,变成全局唯一全局共享
					//目前来看,程序中的4个线程完美的配合着卖了100张票.
					//但是,数据最终有没有安全隐患--让程序休眠一会儿
				}
			}
			//1,创建多线程类-extends Thread
			class MyTickets extends Thread{
				//--需求:让四个窗口,一起卖出100张票
				static int tickets = 100 ;//定义变量,记录票数
				//2,开始卖票--把业务放入run()
				@Override
				public void run() {
					//一直卖票,卖完结束
					while(true) {//死循环----配置设置好出口!!
						//如果共享资源是 一个 静态资源,锁对象必须是 类名.class!!!
						synchronized (MyTickets.class) {
							if(tickets>0) {
								//!!! 5,验证多线程中,数据是否安全,都要接受这次考验
								//问题2: 超卖: 卖出了0号票,甚至-1号票
								//问题3: 重卖: 同一张票卖给了好几个人
								try {
									Thread.sleep(10);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
								//3,打印线程名称
								System.out.println(getName()+"===="+tickets--);
							}else {
								break;//合理的出口!!
							}	
						}
					}
				}
			}

7,线程之间的通信wait和notify

线程不是一个个独立的个体,线程与线程之间是可以进行互相通信和协作的。那么关于线程之间的通信,主要学习的就是等待和通知了,也就是说在学习线程之间的通信这块,可能打交道最多的就是wait()和notify()这两个方法了。
 在线程之间的通信中,wait就代表让此线程进入等待状态,之后的代码将不再执行,这里有一个前提就是,必须是在获得同步锁的前提下,Java为每一个Object对象都实现了wait和notify,但是不是随随便便就能调用的,比如这样

        String s = new String();
        try {
            s.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

不过,能调用归能调用,出不出错就是另外一回事,例如上面这段代码,调用了wait这个方法,然后执行
在这里插入图片描述
在这里插入图片描述
结果出错,这是为什么?因为在使用wait和notify是有一个前提的,那就是事先必须已经获得了同步锁,以下才是正确的使用方式


        try {
            String s = new String();
            synchronized (s){
                s.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

以上再次执行就不会报错了,因为已经加上了同步锁,这个只是为了说明对于wait和notify调用的前提是必须已经获得同步锁。
 也许还不是很清楚,那就记住一句话:只能在同步代码块和同步方法中调用wait和notify。

接下来继续看一个例子


    public static void main(String[] args) {
        Object lock = new Object();
        MyThread myThread = new MyThread(lock);
        Thread thread = new Thread(myThread);
        thread.start();
        thread.setName("线程A");

    }
}

class MyThread implements Runnable{
private Object lock;
public MyThread(Object lock){
    this.lock = lock;
}
    @Override
    public void run() {
        System.out.println("线程开始工作"+Thread.currentThread().getName());
        synchronized (lock){
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程结束工作"+Thread.currentThread().getName());
    }
}

首先从自定义的MyThread线程开始看,首先创建了一个object对象作为锁对象,然后在同步代码块中调用了wait方法,调用此方法的目的是让此线程处于等待状态,这样一来,之后的代码就不会再执行,同时调用此wait方法将线程置于等待状态的时候已经释放了持有的锁,先看一下以上代码的执行结果吧
在这里插入图片描述
可以看得到,此时程序处于执行中状态,这是因为自定义的线程被处于等待队列的原因

那么如何让这个线程继续执行剩下的代码呢?那就要使用notify这个方法了,再创建一个线程

    private Object lock;
    public OtherThread(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        System.out.println("线程开始工作"+Thread.currentThread().getName());
        synchronized (lock){
            lock.notify();
        }
        System.out.println("线程结束工作"+Thread.currentThread().getName());
    }
}

这里再同步代码块中则调用了notify来发起一个通知,发起一个什么通知呢?就是通知到那些调用了wait处于等待状态的线程,告诉他们你们可以执行啦,并且调用了notify的线程不会像调用了wait的线程那样立马释放掉锁,而是会将线程执行完毕才会释放锁,然后之前处于等待状态的线程拿到释放的锁继续执行剩下的代码,所以这里就有一个知识点,那就是这个锁必须是同一个锁,保证是同一个锁的关键点就是这些代码了

    public OtherThread(Object lock){
        this.lock = lock;
    }

然后执行这个线程

        //同一个锁
        Object lock = new Object();
        
        MyThread myThread = new MyThread(lock);
        Thread thread = new Thread(myThread);
        thread.start();
        thread.setName("线程A");
        
        OtherThread otherThread = new OtherThread(lock);
        otherThread.start();
        otherThread.setName("线程B");

    }

紧接着执行程序
在这里插入图片描述
结果正如分析的一样!

以上只是等待通知的一种情况,那就是一个线程处于等待状态,然后一个线程发起通知,紧接着处于等待的那个线程拿到发起通知的线程的锁,继而继续执行。

当然,还有这么一种情况,那就是有很多个线程处于等待状态,然后一个线程发起通知,这样的话就会随机通知处于等待状态中的一个线程,其实还有一个方法叫做notifyAll是用来通知所有的等待线程的,这样的货,处于等待状态的这些个线程,谁的优先级高,谁就会得到这个通知,从而拿到锁。

8、线程间通信join(二)

首先来说这个jon有什么用,join是一个方法,线程可以调用这个方法,当在主线程中执行一个子线程,如果这个子线程执行结束会得到一个值,而在主线程中会用到这个值,但是实际的情况是,很有可能主线程已经执行完了,子线程还在执行,这样就无法得到这个值了,该怎么办

使用join就可以解决这个问题,只需要让子线程调用join方法,这样就会在执行完子线程之后才会执行主线程,说简单点,就是子线程调用了join方法之后,就必须等子线程执行完成之后才能干其他的事

使用起来也很简单

另外对于join还有这样的写法就是join(long),在代码中的表现形式就是如下

这样的话就会使得线程等待2秒之后执行,要知道单独使用这样的代码

是必须等到子线程结束之后才会执行其他的代码,但是如果是这样的话

两秒之后就会执行其它的代码了,看到这里,这个功能似乎跟这个有点像

但是两者有个本质上的区别就是join的话会释放锁,而sleep则不会,这个在具体的场景中则会有具体的应用

猜你喜欢

转载自blog.csdn.net/GTC_GZ/article/details/108680386
今日推荐