1.通过继承Thread类实现多线程
子类通过继承Thread父类并覆写其中的run方法。run方法实现线程需要完成的任务,最后在主类中实例化子类(即创建线程)并调用start()方法,让创建的线程工作。
案例1 售票员在票出售光前实现一直出售:
1.售票员售票速度太快了
2.多个售票员之间100张票出现售票混乱的情况,比如只有一张8号的票却同时卖出去了
为了解决这些问题我们就需要引入多线程常用的操作方法
*面试题:线程的run()和start()有什么区别?
- 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。
- start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
- start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。
- run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
2.多线程常用操作方法
- Thread.sleep():让当前线程休眠指定的时间,暂停执行,不释放锁资源。
- Thread.interrupt():中断当前线程,给线程发送中断信号,该线程需要处理中断信号来决定如何终止执行。
- Thread.currentThread().getName():获取当前线程的名称。
- Thread.currentThread().setName(String name):设置当前线程的名称。
- Thread.yield():暂停当前正在执行的线程,让其他具有相同优先级的线程有机会执行。
- Thread.join():等待指定线程终止执行,当前线程进入阻塞状态,直到指定线程执行结束。
- Thread.wait():在等待其他线程通知之前,使当前线程进入等待状态,并释放对象的监视器锁资源。
首先是解决售票员售票速度太快了问题,查看方法可以发现Thread.sleep()就能解决这个问题
注意Thread.sleep()会抛出一个异常interruptException表示线程睡眠被打断了。所以需要用try catch去处理这个可能发送的异常
修改后的案例代码2:
讯享网
接下来就是解决同步问题了,由上图可以看到虽然解决了卖得快得问题,但是并发问题并未解决,并发就是指多个线程同时调用资源,同时修改资源得情况。需要用到同步synchronized实现同步,这里只展示实现代码
案例代码:(售票员独立售卖电影票并且在随机时间内售出)
其他功能 案例:
1.线程中断
讯享网
就是指如果一个线程使用了Thread 对象.join();那么他就是老大,所有资源都独享,其他线程只能等待老大享受完成资源。

3.线程礼让
Thread.yield();线程会让出一些资源出来,在资源不紧缺时候再调用该程序。但还是可能会出现交替运行的情况
*面试题Thread 类中的 yield 方法有什么作用?
- yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
- 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
3.通过Runnable接口实现多线程
Runnable的接口代码:
案例1:(通过实现Runnable实现售票员售票)
案例代码2:
- 继承Thread类:这种方式是创建一个新的类,继承自Thread类,并重写它的run()方法来定义线程执行的逻辑。通过创建Thread类的实例对象,可以直接调用其start()方法来启动线程。这种方式的优点是代码简单,方便使用,但缺点是由于Java不支持多重继承,因此如果已经继承了其他类,则无法再使用这种方式创建线程。
- 实现Runnable接口:这种方式是创建一个实现Runnable接口的类,在该类中实现run()方法来定义线程执行的逻辑。然后,创建Thread类的实例对象时,将实现了Runnable接口的类的实例对象作为参数传递给Thread的构造函数。最后,调用Thread实例对象的start()方法来启动线程。这种方式的优点是避免了单继承的限制,提高了代码的灵活性和可复用性。
4.通过Lambda与Thread结合实现快速创建多线程
当然Thread构造函数的Task可以与Lambda表达式结合实现不需要构造子类就能够实现多线程
回顾以往的Lambda的知识,核心就两种实现方法
1.()->{方法体}
2.()->语句
案例3
5.通过实现Callable接口得到线程返回值
由于用Thread会有单继承限制,而用Runnable会有run方法无法获取返回值的缺点,所以为了获取返回值则使用Callable泛型,Callable的接口如下:
- FutureTask <返回类型> Task = new FutureTask<>(实例化创建的任务类的对象)
- new Thread(Task,"Threadname").start();
- task.get();获取返回值
- 返回值类型: Runnable接口的run()方法没有返回值,它的执行结果无法获取。而Callable接口的call()方法可以返回一个结果,并且可以通过Future对象获取该结果。
- 异常处理: Runnable接口的run()方法无法抛出受检查异常,只能在方法内部进行异常处理。而Callable接口的call()方法可以抛出受检查异常,需要在方法内部进行异常处理或者通过Future对象获取异常信息。
- 支持泛型: Callable接口是一个泛型接口,可以指定call()方法的返回类型。而Runnable接口不支持泛型,无法指定返回类型。
- 使用方式: Runnable接口通常与Thread类一起使用,通过创建Thread类的实例并传入一个Runnable对象来创建线程。Callable接口通常与ExecutorService线程池一起使用,通过提交Callable任务给线程池来创建线程,并通过Future对象获取任务的执行结果。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/5088.html