Educoder头歌—Java多线程基础常用函数

简介: Java多线程基础常用函数

第1关:线程的状态与调度

任务描述

本关任务:学习本关知识完成选择题。

相关知识

为了完成本关你需要掌握:

1.线程的状态与调度;

2.线程执行的优先级。

线程的状态与调度

如果看懂下图,你对线程的了解就会更上一层楼。

911a4ebdd7e01b3bbee5db2ebd96ab5f.png

  1. 当我们使用new关键字新建一个线程,这个时候线程就进入了新建状态(New),也就是图中未启动状态;
  2. 调用start方法启动线程,这个时候就进入了可运行状态,也就是就绪状态(Runnable);
  3. 就绪状态获取了CPU资源,开始执行run方法,就进入了运行状态(Running);
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种;
  • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁);
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中;
  • 其他阻塞:运行的线程执行sleep()join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁);
  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程优先级

我们知道,多个线程的执行是随机的,在一个时段执行哪个线程是看哪一个线程在此时拥有CPU的执行权限。

Java中线程有优先级,优先级高的线程会获得较多的运行机会。

Java线程的优先级用整数表示,取值范围是1~10Thread类有以下三个静态常量:

staticintMAX_PRIORITY线程可以具有的最高优先级,取值为10。staticintMIN_PRIORITY线程可以具有的最低优先级,取值为1。staticintNORM_PRIORITY分配给线程的默认优先级,取值为5。

如果要设置和获取线程的优先级,可以使用Thread类的setPriority()getPriority()方法。

每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。


关于线程调度与优先级你还需要了解:

线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

编程要求

略 ####测试说明 略

  • 1、有三种原因可以导致线程不能运行,它们是( ABC
    A、等待
    B、阻塞
    C、休眠
    D、挂起及由于I/O操作而阻塞
  • 2、Java语言中提供了一个( D )线程,自动回收动态分配的内存。
    A、异步
    B、消费者
    C、守护
    D、垃圾收集
  • 3、当( A )方法终止时,能使线程进入死亡状态
    A、run
    B、setPriority
    C、yield
    D、sleep
  • 4、用( B )方法可以改变线程的优先级。
    A、run
    B、setPriority
    C、yield
    D、sleep
  • 5、线程通过( D )方法可以休眠一段时间,然后恢复运行
    A、run
    B、setPriority
    C、yield
    D、sleep
  • 6、下列关于线程的说法正确的是( ABD
    A、如果线程死亡,它便不能运行
    B、在Java中,高优先级的可运行线程会抢占低优先级线程
    C、线程可以用yield方法使低优先级的线程运行
    D、一个线程可以调用yield方法使其他线程有机会运行

第2关:常用函数(一)

任务描述

本关任务:获取子线程执行的结果并输出。

相关知识2

本关你需要掌握sleepjoin函数的用法。

sleep()函数

sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。

使用方式很简单在线程的内部使用Thread.sleep(millis)即可。

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用,目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

join()函数

join函数的定义是指:等待线程终止

我们在运行线程的时候可能会遇到,在主线程中运行子线程,主线程需要获取子线程最终执行结果的情况。

但是有很多时候子线程进行了很多耗时的操作,主线程往往先于子线程结束,这个时候主线程就获取不到子线程的最终执行结果了。

使用join函数,可以解决这一问题。

我们通过两个示例来理解:

不使用join函数的版本:

classMyThreadextendsThread {
privateStringname;
publicMyThread(Stringname) {
this.name=name;
    }
publicvoidrun() {
System.out.println("子线程开始运行");
for (inti=0; i<5; i++) {
System.out.println("子线程"+name+"运行"+i);
try {
Thread.sleep(30);
            } catch (InterruptedExceptione) {
e.printStackT\frace();
            }
        }
System.out.println("子线程结束");
    }
}
publicclassTest {
publicstaticvoidmain(String[] args) {
Threadt=newMyThread("子线程A");
t.start();
for (inti=0; i<5; i++) {
System.out.println("主线程执行"+i);
        }
System.out.println("主线程结束");
    }
}

输出结果(每次都不一样):

主线程执行0

主线程执行1

主线程执行2

主线程执行3

主线程执行4

主线程结束

子线程开始运行

子线程子线程A运行0

子线程子线程A运行1

子线程子线程A运行2

子线程子线程A运行3

子线程子线程A运行4

子线程结束

可以发现每次运行,主线程都是先于子线程结束的。

使用join函数:

packagestep1;
classMyThreadextendsThread {
privateStringname;
publicMyThread(Stringname) {
this.name=name;
    }
publicvoidrun() {
System.out.println("子线程开始运行");
for (inti=0; i<5; i++) {
System.out.println("子线程"+name+"运行"+i);
try {
Thread.sleep(30);
            } catch (InterruptedExceptione) {
e.printStackT\frace();
            }
        }
System.out.println("子线程结束");
    }
}
publicclassTest {
publicstaticvoidmain(String[] args) {
Threadt=newMyThread("子线程A");
t.start();
for (inti=0; i<5; i++) {
System.out.println("主线程执行"+i);
        }
try {
t.join();
        } catch (Exceptione) {
e.printStackT\frace();
        }
System.out.println("主线程结束");
    }
}

输出结果:

主线程执行1

主线程执行2

主线程执行3

子线程开始运行

主线程执行4

子线程子线程A运行0

子线程子线程A运行1

子线程子线程A运行2

子线程子线程A运行3

子线程子线程A运行4

子线程结束

主线程结束

可以发现无论运行多少次,主线程都会等待子线程结束之后在结束。

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

  • 创建自定义线程,实现求第num项斐波那契数列的值num0开始,并且在main函数中获取子线程最终计算的结果。

测试说明

补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。

输入:5

输出:子线程计算结果为:5

输入:8

输出:子线程计算结果为:21

输入:10

输出:子线程计算结果为:55


开始你的任务吧,祝你成功!


代码示例

packagestep2;
importjava.util.Scanner;
publicclassTask {
publicstaticvoidmain(String[] args) {
Scannersc=newScanner(System.in);
intnum=sc.nextInt();
// 请在此添加实现代码/********** Begin **********/MyThreadmt=newMyThread(num);
mt.start();
try {
mt.join();
        } catch (InterruptedExceptione) {
// TODO 自动生成的 catch 块e.printStackTrace();
        }
/********** End **********/    }
}
//请在此添加实现代码/********** Begin **********/classMyThreadextendsThread {
intnum;
publicMyThread(intnum) {
this.num=num;
    }
@Overridepublicvoidrun() {
// TODO 自动生成的方法存根System.out.println("子线程计算结果为:"+num(num));
    }
publicintnum(intn) {
if (n<3) {
return1;
        } elsereturnnum(n-1) +num(n-2);
    }
}
/********** End **********/

第3关:常用函数(二)

任务描述

本关任务:使用三个线程交替打印5EDU

相关知识

为了完成本关任务你需要掌握:

1.yield函数的用法;

2.wait函数的用法;

3.其他常用函数的使用。

yield 函数

yield函数可以理解为“让步”,它的作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

示例:

publicclassTask {
publicstaticvoidmain(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
MyThreadmTh1=newMyThread("A");
MyThreadmTh2=newMyThread("B");
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+"主线程运行结束!");
    }
}
classMyThreadextendsThread  {
privateStringname;
publicMyThread(Stringname) {
this.name=name;
    }
publicvoidrun() {
for (inti=0; i<50; i++) {
System.out.println(name+"线程"+"执行"+i);
if(i==10){
this.yield();
            }
        }
    }
}

运行结果有两种情况:

第一种情况:A(线程)当执行到10时会让掉CPU时间,这时B(线程)抢到CPU时间并执行。

第二种情况:B(线程)当执行到10时会让掉CPU时间,这时A(线程)抢到CPU时间并执行。

我们还可以考虑在其中加入setPriority函数改变线程优先级从而改变线程的执行顺序。

wait 函数

要弄明白wait函数我们首先需要了解线程锁的概念。 线程锁:其实就像我们日常生活中的锁,如果一个房子上了锁,别人就进不去,在Java中也类似,如果一段代码取得了锁,那么其他地方就不能运行这段代码,只能等待锁的释放。

这里我们会涉及到两个函数,1.wait(),2.notify()。这两个函数都是Object类自带的函数。

在下面的例子中我们会使用synchronized(Obj)来实现线程同步,同步的概念后面会讲到,这里不理解没关系,不影响对于wait函数的理解。

从功能上来说:

  • wait()就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行;
  • 相应的notify()就是对对象锁的唤醒操作。

但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。

Thread.sleep()Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

纸上得来终觉浅,欲知此事须躬行,我们还是通过实例来学习这些函数的用法。

问题:建立两个线程,A线程打印5A,B线程打印5B,要求线程同时运行,交替打印5AB

这个问题用Objectwait()notify()就可以很方便的解决。代码如下:

publicclassMyThreadimplementsRunnable {   
privateStringname;   
privateObjectprev;   
privateObjectself;   
privateMyThread(Stringname, Objectprev, Objectself) {   
this.name=name;   
this.prev=prev;   
this.self=self;   
    }   
publicvoidrun() {   
intcount=5;   
while (count>0) {   
synchronized (prev) {   
synchronized (self) {   
System.out.print(name);   
count--;  
self.notify();   
                }   
try {   
prev.wait();   
                } catch (InterruptedExceptione) {   
e.printStackTrace();   
                }   
            }   
        }  
System.exit(0);//退出jvm    }   
publicstaticvoidmain(String[] args) throwsException {   
Objecta=newObject();   
Objectb=newObject();   
MyThreadta=newMyThread("A", b, a);   
MyThreadtb=newMyThread("B", a, b);   
newThread(ta).start();
Thread.sleep(100);  //确保按顺序A、B执行newThread(tb).start();
Thread.sleep(100);  
        }   
}

运行程序,结果为:

ABABABABAB
线程常用函数总结

下表列出的是线程类常用的函数:

函数 描述
public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
public final void setName(String name) 改变线程名称,使之与参数 name 相同。
public final void setPriority(int priority) 更改线程的优先级。
public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒
public void interrupt() 中断线程。
public final boolean isAlive() 测试线程是否处于活动状态。
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

  • 建立三个线程,A线程打印5E,B线程打印5DC线程打印5U,要求线程同时运行,交替打印5EDU

测试说明

补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。


开始你的任务吧,祝你成功!

代码示例

packagestep3;
publicclassMyThreadimplementsRunnable {
// 请在此添加实现代码/********** Begin **********/privateStringname;
privateObjectprev;
privateObjectself;
privateMyThread(Stringname, Objectprev, Objectself) {
this.name=name;
this.prev=prev;
this.self=self;
    }
publicvoidrun() {
intcount=5;
while (count>0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
                }
try {
prev.wait();
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            }
        }
System.exit(0);// 退出jvm    }
publicstaticvoidmain(String[] args) throwsException {
Objecta=newObject();
Objectb=newObject();
Objectc=newObject();
MyThreadta=newMyThread("E", c, b);
MyThreadtb=newMyThread("D", b, a);
MyThreadtc=newMyThread("U", a, c);
newThread(ta).start();
Thread.sleep(100); // 确保按顺序A、B执行newThread(tb).start();
Thread.sleep(100);
newThread(tc).start();
    }
/********** End **********/}

输出:

EDUEDUEDUEDUEDU

目录
相关文章
|
2天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
32 17
|
12天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
14天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
14天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
15天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
41 3
|
15天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
98 2
|
23天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
46 6
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
2月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####