单词反转和数组去重,附经典面试题一份

博彦科技笔试:
给定字符,拼接成单词进行反转单词;

package org.example;public class Main {public static void main(String[] args) {char[] input = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', ' ', 'J', 'a', 'v', 'a'};String inputToString = new String(input);System.out.println("Input: " + inputToString);String[] words = inputToString.split(" ");String res = "";for (int i= words.length-1; i>=0; i--){res+=words[i];if (i != 0){res+=" ";}}char[] output = res.toCharArray();System.out.println("----------------------------------");for(char c: output)System.out.print(c);}}

起初只想到了用split进行分割,忘记了是String 类型才有的,结果是String 与char类型的数组转化,用到了.toCharArray();

在这里插入图片描述
保留两位小数的格式

package org.example;import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner  = new Scanner(System.in);Double d = scanner.nextDouble();System.out.println(String.format("%.2f",d));}}
package org.example;import java.text.DecimalFormat;
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);Double d = scanner.nextDouble();DecimalFormat df = new DecimalFormat("#.00");System.out.println(df.format(d));}
}

(北京)有限公司:

*1. package testpkg.p1;
public class ParentUtil{public int x = 420;protected int doStuff() { return x; }}package testpkg.p2;import testpkg.p1.ParentUtil;public class ChildUtil extends ParentUtil{public static void main(String [] args){new ChildUtil().callStuff();}void callStuff(){System.out.print("this " + this.doStuff() ); /* Line 18 */ParentUtil p = new ParentUtil();System.out.print(" parent " + p.doStuff() ); /* Line 20 */}}有错误吗

解释:
这个代码有两个包(testpkg.p1testpkg.p2)和继承关系。我们来逐步分析代码,看看是否有错误。

代码结构分析:

1. ParentUtil 类(位于 testpkg.p1 包)
package testpkg.p1;public class ParentUtil {public int x = 420;protected int doStuff() {return x;}
}
  • ParentUtil 类定义了一个公开字段 x 和一个受保护的 doStuff 方法。
  • doStuff 方法是 protected 的,意味着它只能在同一个包内或者子类中访问。
  • 没有错误,代码正确。
2. ChildUtil 类(位于 testpkg.p2 包)
package testpkg.p2;import testpkg.p1.ParentUtil;public class ChildUtil extends ParentUtil {public static void main(String[] args) {new ChildUtil().callStuff();}void callStuff() {System.out.print("this " + this.doStuff()); /* Line 18 */ParentUtil p = new ParentUtil();System.out.print(" parent " + p.doStuff()); /* Line 20 */}
}
  • ChildUtil 类继承自 ParentUtil
  • main 方法创建了 ChildUtil 实例并调用了 callStuff 方法。
  • callStuff 方法中,首先打印了 this.doStuff(),然后创建了一个 ParentUtil 对象并打印了 p.doStuff()

错误分析:

1. Line 18 (this.doStuff() 调用)
  • this.doStuff() 调用是合法的,因为 doStuffprotected 的,而 ChildUtilParentUtil 的子类。
  • protected 方法可以在子类中访问,所以 this.doStuff() 是合法的,并且能够访问父类的 doStuff 方法。
2. Line 20 (p.doStuff() 调用)
  • 在这一行,创建了一个 ParentUtil 类型的对象 p,然后尝试调用 p.doStuff()
  • 问题出在这里:doStuff 方法是 protected 的,它只能在同一个包内或子类中被访问。
  • pParentUtil 类型的对象,且 ParentUtilChildUtil 处于不同的包(分别在 testpkg.p1testpkg.p2 中)。因此,在 testpkg.p2 中直接访问 p.doStuff() 是不合法的。

错误的原因:

  • 由于 doStuffprotected,它无法在不同包的非子类中被访问,因此 p.doStuff() 会导致编译错误。

解决方案:

有两种可能的解决方法来解决 Line 20 的错误。

  1. 更改 doStuff 方法的访问修饰符
    如果希望在 testpkg.p2 包中访问 doStuff 方法,可以将 doStuff 方法的访问修饰符更改为 public,这样它就可以被其他包的类访问。

    public int doStuff() {return x;
    }
    
  2. 通过继承调用 doStuff
    如果你不希望修改 doStuff 的访问级别,可以通过子类对象来调用 doStuff 方法。例如,将 p 的类型改为 ChildUtil,这样就能通过子类访问受保护的方法。

    ParentUtil p = new ChildUtil();
    System.out.print(" parent " + p.doStuff());
    

    这种方法可以确保 doStuff 在子类中被访问,从而不会导致权限错误。

结论:

  • Line 18 没有错误,因为在子类中可以访问 protected 方法。
  • Line 20 存在错误,因为 doStuffprotected 的,ParentUtilChildUtil 在不同的包中,导致 p.doStuff() 无法访问。

通过上述两种解决方案中的任何一种,你可以修复代码中的错误。


  • *2. 哪个集合类允许扩大或缩小其大小,并提供对其元素的索引访问,但其方法不是同步的(synchronized)?

A.java.util.HashSet

B.java.util.LinkedHashSet

C.java.util.List

D.java.util.ArrayList

答案是 D. java.util.ArrayList

解释:

让我们逐一分析每个选项:

A. java.util.HashSet
  • HashSet 是一个集合类,它存储不重复的元素,并且没有提供按索引访问元素的功能。它是一个 无序集合,因此不能按索引访问元素。
  • 此外,HashSet 并没有提供同步机制,它的所有方法不是同步的。
B. java.util.LinkedHashSet
  • LinkedHashSet 是一个继承自 HashSet 的集合类,它也存储不重复的元素,并且保持元素插入的顺序。与 HashSet 一样,它不提供按索引访问元素的功能。
  • 同样,LinkedHashSet 也没有同步机制,方法也不是同步的。
C. java.util.List
  • List 是一个接口,它定义了一个有序的集合,并允许通过索引访问元素。它的子接口有 ArrayListLinkedList 等。虽然 List 确实提供索引访问,但这个选项本身是一个接口,不是一个具体的实现类,因此不适合作为答案。
D. java.util.ArrayList
  • ArrayList 是一个实现了 List 接口的集合类,它提供了可变大小的数组,并允许通过索引访问元素。
  • ArrayList 的大小是可以自动扩展和缩小的。它在添加或删除元素时,会动态调整其内部数组的大小。
  • ArrayList 的方法 不是同步的,这意味着它不是线程安全的,多个线程同时修改同一个 ArrayList 时需要额外的同步机制来保证线程安全。

结论:

ArrayList 是唯一符合题目描述的集合类,它允许扩展或缩小大小,提供索引访问,并且其方法不是同步的。

所以,正确答案是 D. java.util.ArrayList


3、哪种方法不会直接导致线程停止?

A.notify()

B.wait()

C. InputStream access

D. sleep()

正确答案是 A. notify()

解释:

让我们逐一分析每个选项:

A. notify()
  • notify() 方法是用于唤醒在同步代码块或方法中等待的线程,但它不会直接导致线程停止。它仅仅是唤醒等待的线程,并允许线程继续执行。线程在调用 notify() 时并不会停止,它只是将线程的状态从等待中唤醒,使其有机会重新获取锁并继续执行。

  • 总结notify() 不会直接导致线程停止,它的作用是唤醒其他线程。

B. wait()
  • wait() 方法是用于使当前线程进入等待状态,直到其他线程通过 notify()notifyAll() 唤醒它。调用 wait() 后,线程会被挂起并进入 等待队列,直到它被唤醒后才会继续执行。因此,wait() 会导致线程停顿,直到被唤醒。

  • 总结wait() 会直接导致线程停顿,直到被唤醒。

C. InputStream access
  • InputStream 访问(例如 read() 方法)会使当前线程在等待输入数据时阻塞,直到数据可用或发生错误。然而,阻塞并不意味着线程停止,它只是挂起当前线程,等待输入流中的数据。线程会在等待期间被阻塞,但它不会像 wait() 那样主动进入 “等待” 状态;它只是等待数据的到来。

  • 总结InputStream 的访问可能会阻塞线程,但它不会直接导致线程停止。

D. sleep()
  • sleep() 方法使当前线程暂停执行指定的时间。它不会释放锁,仅仅使当前线程进入 睡眠状态,在指定时间后自动恢复执行。虽然线程暂停,但线程并没有停止,它仍然存在,并会在 sleep() 时间到期后继续执行。

  • 总结sleep() 会使线程暂停一段时间,但不会直接停止线程,它会在时间结束后恢复执行。

结论:

  • notify() 只是唤醒等待的线程,并不会直接导致线程停止。其他方法(wait()sleep()InputStream access)都可能导致线程停止或挂起。

因此,正确答案是 A. notify()


4、

 class X implements Runnable
{public static void main(String args[]){/* Missing code? */}public void run() {}}下列哪种方法可以启动线程?A.Thread t = new Thread(X);B. Thread t = new Thread(X); t.start();C. X run = new X(); Thread t = new Thread(run); t.start();D. Thread t = new Thread(); x.run();

正确答案是 C. X run = new X(); Thread t = new Thread(run); t.start();

解释:

  1. A. Thread t = new Thread(X);

    • 这行代码是错误的。X 是一个类,不是一个对象。要将 Runnable 对象传递给 Thread 构造函数,必须创建 X 类的实例。所以,直接将 X 类传递给 Thread 构造函数会导致编译错误。
  2. B. Thread t = new Thread(X); t.start();

    • 这也是错误的,原因同上,Thread 的构造函数需要一个 Runnable 对象作为参数,而不能直接传递类名。应该传递 Runnable 实例。
  3. C. X run = new X(); Thread t = new Thread(run); t.start();

    • 这是正确的做法。首先,创建了一个 X 类的实例 run,它实现了 Runnable 接口。然后,将该实例作为参数传递给 Thread 构造函数,并调用 start() 启动线程。start() 方法会调用 run() 方法,开始线程的执行。
  4. D. Thread t = new Thread(); x.run();

    • 这行代码是错误的。首先,Thread 类的构造函数没有传递 Runnable 实例。此外,直接调用 x.run() 并不会启动新线程,而是会在当前线程中执行 run() 方法,导致线程未真正启动。

结论:

为了启动线程,正确的方式是先创建一个实现了 Runnable 接口的实例,然后将其传递给 Thread 类的构造函数,最后调用 start() 方法启动线程。

因此,正确答案是 C. X run = new X(); Thread t = new Thread(run); t.start();


5、

*5. 哪三个保证一个线程会离开运行状态
yield()

wait()

notify()

notifyAll()

sleep(1000)

aLiveThread.join()

Thread.killThread()

要确保一个线程离开运行状态并进入其他状态(如 等待状态阻塞状态终止状态 等),我们可以使用以下方法:

1. yield()

  • 作用yield() 是一个静态方法,表示当前线程主动让出 CPU 时间片,但不保证线程会立即停止运行。调用 yield() 后,当前线程会被挂起,操作系统会重新调度其他线程。然而,线程的状态将变为 就绪状态,而不是 阻塞状态
  • 结论yield() 只会让当前线程 暂停执行,但不会离开运行状态,线程会在稍后的时间恢复执行。

2. wait()

  • 作用wait() 会使当前线程进入 等待状态,直到它被其他线程唤醒(通常通过 notify()notifyAll())。在等待期间,线程不再占用 CPU,因此它会离开运行状态。
  • 结论wait() 确实会导致线程离开运行状态,进入 等待状态

3. notify()

  • 作用notify() 是唤醒一个在当前对象监视器上等待的线程,但不会直接影响当前线程的状态。它只是通知等待线程有条件去执行,并不会让当前线程离开运行状态。
  • 结论notify() 不会导致当前线程离开运行状态,它只会唤醒等待线程。

4. notifyAll()

  • 作用notifyAll() 是唤醒当前对象监视器上所有等待的线程,同样,它并不影响当前线程的状态。
  • 结论notifyAll() 只会唤醒所有等待线程,并不会让当前线程离开运行状态。

5. sleep(1000)

  • 作用sleep(1000) 使当前线程进入 阻塞状态,并且会在指定时间(此处为 1000 毫秒)后自动恢复执行。
  • 结论sleep() 会使线程离开运行状态,进入 阻塞状态,直到睡眠时间结束。

6. aLiveThread.join()

  • 作用join() 方法使当前线程等待 aLiveThread 执行完毕。如果 aLiveThread 还没有执行完,调用 join() 的线程会进入 阻塞状态,直到 aLiveThread 完成。
  • 结论join() 会让当前线程离开运行状态,进入 阻塞状态,直到目标线程执行完成。

7. Thread.killThread()

  • 作用Thread.killThread() 并不是 Java 中的有效方法。实际上,Java 中并没有提供直接杀死线程的 API。线程的停止应通过其他方式(如设置标志位或让线程自然终止)。
  • 结论:没有 killThread() 方法,不适用。

总结:

根据线程离开运行状态的标准,我们可以得到以下结论:

  • wait():会导致线程进入等待状态。
  • sleep(1000):会导致线程进入阻塞状态。
  • aLiveThread.join():会导致当前线程进入阻塞状态,直到目标线程执行完毕。

正确答案:

  • wait()
  • sleep(1000)
  • aLiveThread.join()

这三种方法能够确保一个线程离开运行状态。

线程的几种状态分别是?


6. int i = l, j = -1;switch (i){case 0, 1: j = 1; /* Line 4 */case 2: j = 2;default: j = 0;}System.out.println("j = " + j);有错误吗,结果是

错误分析:

这段代码存在语法错误:

int i = l, j = -1;switch (i) {case 0, 1: j = 1; /* Line 4 */case 2: j = 2;default: j = 0;}System.out.println("j = " + j);

1. 语法错误

  • 问题 1switch 语句中,case 标签通常只能列出一个常量值。Java 14 引入了 增强的 switch 语法(使用逗号分隔多个值),但只有从 Java 14 开始才支持 case 0, 1: 的写法,前提是启用了 switch 表达式 或使用了增强版 switch
  • 问题 2:在该代码中,l 是一个变量,而不是常量。如果 l 没有定义,这将导致编译错误。i 需要是一个有效的整数常量值。

2. 修正

  • 修改 1:首先,确保 ij 的定义是正确的,并且 i 的值是常量。假设 i = 1
  • 修改 2:如果使用传统的 switch 语法(即没有启用 Java 14+ 的增强 switch),每个 case 必须单独指定一个常量。

3. 代码修复

int i = 1, j = -1;switch (i) {case 0:case 1: j = 1; break; // Adding break to prevent fall-throughcase 2: j = 2;break;default: j = 0;
}System.out.println("j = " + j);

4. 结果分析

  1. i = 1 时,switch 会检查 case 1,并进入该分支。
  2. case 1 执行后将 j = 1,然后执行 break 语句,跳出 switch 语句。
  3. 因此,j = 1,最后 System.out.println("j = " + j); 打印出 j = 1

5. 增强 switch 语法(如果你使用 Java 14+):

如果你启用了增强 switch 语法,代码可以写成这样:

int i = 1, j = -1;switch (i) {case 0, 1: j = 1; break; // break is optional in switch expressionscase 2: j = 2;break;default: j = 0;
}System.out.println("j = " + j);

在这种语法下,case 0, 1: 表示 i01,都将执行 j = 1

总结:

  • 错误:原始代码中有一个语法错误,case 0, 1: 是增强 switch 语法的一部分,只有在 Java 14+ 启用该语法时才有效。
  • 修复后的结果:修正后的代码中,假设 i = 1,最终输出 j = 1

7. public class SwitchTest
{public static void main(String[] args){System.out.println("value =" + switchIt(4));}public static int switchIt(int x){int j = 1;switch (x){case l: j++;case 2: j++;case 3: j++;case 4: j++;case 5: j++;default: j++;}return j + x;}}

错误分析:

这段代码有几个问题,首先我们逐一分析:

1. case l: 语法错误

  • switch 语句中,l 是一个变量,而 switchcase 标签必须是常量。因此,case l: 会导致编译错误。
  • 如果 l 是一个数字常量(比如 l = 1;),则 case 应该是常量值 1,而不是变量名 l

2. 缺少 break 语句

  • 由于没有 break 语句,每个 case 会出现 “fall-through”(掉入下一个 case)的现象。这意味着,如果 x 为某个 case,程序将继续执行该 case 后面所有的代码,直到遇到 break(或者到达 switch 末尾)。

3. 修正代码

  • 我们假设 l 是一个常量 1,并且修正其他潜在问题。

修正后的代码:

public class SwitchTest {public static void main(String[] args) {System.out.println("value = " + switchIt(4));}public static int switchIt(int x) {int j = 1;switch (x) {case 1: // 修正了 l 为常量 1j++; // 进入 case 1 后,j 会加 1break; // 加入 break 防止继续 fall-throughcase 2:j++;break;case 3:j++;break;case 4:j++;break;case 5:j++;break;default:j++; // 默认情况下 j 也会增加break;}return j + x;}
}

4. 代码解释

  1. switchIt(4) 被调用时,x 的值是 4。
  2. switch 语句开始时,检查 x 的值是否匹配各个 case 标签。
  3. case 4: 匹配 x = 4,进入 case 4j 的值变为 2。
  4. 由于每个 case 后都有 break,程序会跳出 switch 语句,不会继续执行 case 5default
  5. 最终,switchIt(4) 返回 j + x,即 2 + 4 = 6

5. 输出

value = 6

6. 总结

  • 问题:原始代码中的 case l: 语法错误,且没有 break,会导致 fall-through
  • 修正:将 case l: 改为常量 case 1: ,并且为每个 case 添加 break 语句。
  • 结果:修正后的代码输出 value = 6

关于java.util.HashSet下列说法正确的是?

A.集合中的元素是有序的。

B. 集合保证是不可变的。

C.集合中的元素保证是唯一的。

D.使用唯一键访问集合中的元素。

关于 java.util.HashSet,正确的说法是 C

逐项分析:

  • A. 集合中的元素是有序的。

    • 错误HashSet 不保证元素的顺序。它是一个 无序 集合,意味着插入元素的顺序和遍历顺序可能不一致。如果你需要元素保持插入顺序,可以使用 LinkedHashSet
  • B. 集合保证是不可变的。

    • 错误HashSet 本身是可变的,可以添加和删除元素。如果你需要不可变的集合,可以使用 Collections.unmodifiableSet()Set.of() (在 Java 9 及以后版本)。
  • C. 集合中的元素保证是唯一的。

    • 正确HashSet 保证集合中的元素是唯一的。如果你试图向 HashSet 中添加重复的元素,它将不会被添加。重复元素的定义是根据元素的 equals() 方法和 hashCode() 方法来判定的。
  • D. 使用唯一键访问集合中的元素。

    • 错误HashSet 并不像 HashMap 那样使用键(key)来访问元素。HashSet 是基于集合的,没有 “键” 的概念,它只关心元素是否存在。HashMap 才是基于键值对的结构,提供基于键的访问。

正确答案:

C. 集合中的元素保证是唯一的。


9. public class RTExcept
{public static void throwit (){System.out.print("throwit ");throw new RuntimeException();}public static void main(String [] args){try{System.out.print("hello ");throwit();}catch (Exception re ){System.out.print("caught ");}finally{System.out.print("finally ");}System.out.println("after ");}}结果是

让我们逐步分析这段代码,理解它的执行过程,并得出结果。

代码分析:

public class RTExcept {public static void throwit() {System.out.print("throwit ");throw new RuntimeException(); // 抛出运行时异常}public static void main(String[] args) {try {System.out.print("hello ");throwit(); // 调用 throwit() 方法,它会抛出异常}catch (Exception re) {System.out.print("caught "); // 捕获异常并打印 "caught"}finally {System.out.print("finally "); // 无论如何都会执行,打印 "finally"}System.out.println("after "); // 这行代码在 finally 执行后执行}
}

执行流程:

  1. 进入 main 方法

    • 首先打印 "hello "
  2. 调用 throwit() 方法

    • throwit() 被调用,打印 "throwit "
    • 然后,throwit() 抛出一个 RuntimeException
  3. 异常被 catch 捕获

    • RuntimeExceptionException 类的子类,因此它被 catch 块中的 Exception 捕获。
    • 捕获异常后,打印 "caught "
  4. finally 块执行

    • 不管是否有异常,finally 块都会执行,打印 "finally "
  5. after 被打印

    • finally 块执行完后,程序继续执行 System.out.println("after ");,打印 "after"

输出结果:

hello throwit caught finally after 

解释:

  • "hello " 是在 try 块中执行时打印的。
  • "throwit " 是在调用 throwit() 方法时打印的。
  • "caught " 是在 catch 块中捕获异常后打印的。
  • "finally " 是在 finally 块中执行的。
  • "after " 是在 finally 块执行完后,main 方法中最后一行代码打印的。

因此,最终输出是:

hello throwit caught finally after 

10. class A
{public A(int x){}}class B extends A { }public class test{public static void main (String args []){A a = new B();System.out.println("complete");}}
### 代码分析:让我们逐步解析这段代码的行为:```java
class A {public A(int x) {// 构造函数 A,接受一个整数参数}
}class B extends A {// 类 B 继承自类 A// 由于类 A 的构造函数需要一个 int 参数,B 必须调用 A 的构造函数。
}public class test {public static void main(String args[]) {A a = new B();  // 创建一个 B 类型的对象,并赋值给 A 类型的引用System.out.println("complete");}
}

关键点分析:

  1. 类 A 的构造函数

    • class A 中定义了一个构造函数 public A(int x),它接受一个 int 参数。
    • 这意味着类 A 的对象必须通过传递一个整数来进行初始化。
  2. 类 B 继承自类 A

    • B 继承自 A,但没有定义自己的构造函数。
    • 因为类 B 没有显式定义构造函数,编译器会默认生成一个无参构造函数。
  3. 问题所在

    • class B 中,默认的无参构造函数会自动调用 super(),即 A 类的无参构造函数。
    • 然而,class A 中并没有提供无参构造函数,只有一个带 int 参数的构造函数。所以,B 的默认无参构造函数无法正常编译。
  4. 解决方法

    • 由于 A 类没有无参构造函数,类 B 必须显式调用 super(int x),并提供一个 int 参数给类 A 的构造函数。

修改后的代码:

class A {public A(int x) {// 构造函数 A,接受一个整数参数System.out.println("A's constructor with int: " + x);}
}class B extends A {public B() {super(10);  // 显式调用 A 的构造函数,传递一个整数System.out.println("B's constructor");}
}public class Test {public static void main(String args[]) {A a = new B();  // 创建 B 的对象,会调用 B 的构造函数System.out.println("complete");}
}

输出结果:

A's constructor with int: 10
B's constructor
complete

总结:

在原代码中,类 B 没有显式调用 super(int x),导致编译错误。解决方法是,在 B 的构造函数中显式调用 super(10),并传递一个合适的参数,这样程序就可以正常编译和运行。


11. String x = "xyz";
x.toUpperCase(); /* Line 2 */String y = x.replace('Y', 'y');y = y + "abc";System.out.println(y);

程序的输出是

让我们逐步分析这段代码,了解每一行的作用以及最终输出。

String x = "xyz";  // 第1行,定义一个字符串变量 x 并赋值为 "xyz"
x.toUpperCase();    // 第2行,调用 toUpperCase() 方法,但结果没有保存String y = x.replace('Y', 'y');  // 第3行,尝试将字符 'Y' 替换为 'y',但 'Y' 不在字符串中y = y + "abc";  // 第4行,将 "abc" 拼接到字符串 y 上System.out.println(y);  // 输出 y 的值

逐行解析:

  1. 第1行:

    String x = "xyz";
    
    • 这行定义了一个字符串 x,并将其初始化为 "xyz"
  2. 第2行:

    x.toUpperCase();
    
    • toUpperCase() 方法将返回一个将字符串全部转为大写的新字符串。
    • 但是,这里 没有将返回值赋给任何变量,所以这个操作没有实际影响。
    • 原字符串 x 依然是 "xyz",并没有改变。
  3. 第3行:

    String y = x.replace('Y', 'y');
    
    • replace('Y', 'y') 方法会尝试将 x 字符串中的字符 'Y' 替换为 'y'
    • 但是,字符串 "xyz" 中并没有字符 'Y'(它是小写的 'y'),因此 replace 操作不会做任何替换,y 仍然是 "xyz"
  4. 第4行:

    y = y + "abc";
    
    • 这行代码将 "abc" 字符串拼接到 y 字符串的末尾。
    • 由于 y"xyz",所以拼接后,y 的值变为 "xyzabc"
  5. 第5行:

    System.out.println(y);
    
    • 这行代码将打印 y 的值,即 "xyzabc"

输出结果:

xyzabc

总结:

  • x.toUpperCase(); 没有影响,因为返回的值没有被保存。
  • x.replace('Y', 'y'); 没有进行任何替换,因为 "xyz" 中没有大写字母 'Y'
  • 最终,y

13. public void foo( boolean a, boolean b)
{if( a ){System.out.println("A"); /* Line 5 */}else if(a && b) /* Line 7 */{System.out.println( "A && B");}else /* Line 11 */{if ( !b ){System.out.println( "notB") ;}else{System.out.println( "ELSE" ) ;}}}A.如果 a 为真且 b 为真,则输出为“A && BB.如果 a 为真且 b 为假,则输出为“notB”C.如果 a 为假且 b 为真,则输出为“ELSE”D.如果 a 为假且 b 为假,则输出为“ELSE”
让我们逐步分析这段代码,并确定每种情况下的输出。### 代码分析:public void foo(boolean a, boolean b) {if (a) {  // Line 3System.out.println("A");  // Line 5}else if (a && b) {  // Line 7System.out.println("A && B");}else {  // Line 11if (!b) {  System.out.println("notB"); }else {System.out.println("ELSE");}}
}

逐步分析:

1. 如果 a 为真且 b 为真
  • atrue 时,进入第一个 if (a) 语句块。
  • 无论 b 是什么值,if (a) 会执行,输出 "A"

输出:

A
2. 如果 a 为真且 b 为假
  • atruebfalse 时,进入第一个 if (a) 语句块。
  • 无论 b 的值,if (a) 会执行,输出 "A"

输出:

A
3. 如果 a 为假且 b 为真
  • afalsebtrue 时,进入 else if (a && b)
  • 由于 afalsea && b 会返回 false,因此跳过这个 else if 块,进入 else 语句。
  • else 语句中,检查 if (!b)
    • !bfalse,因为 btrue
    • 所以执行 else 块,输出 "ELSE"

输出:

ELSE
4. 如果 a 为假且 b 为假
  • afalsebfalse 时,进入 else if (a && b)
  • 同样地,a && bfalse,跳过这个 else if,进入 else 块。
  • else 语句中,检查 if (!b)
    • !btrue,因为 bfalse
    • 所以执行 if (!b) 块,输出 "notB"

输出:

notB

总结选项:

  • A: 如果 a 为真且 b 为真,则输出为 "A",因为 if (a) 会被执行,无论 b 是否为真。
  • B: 如果 a 为真且 b 为假,则输出为 "A",因为 if (a) 会被执行。
  • C: 如果 a 为假且 b 为真,则输出为 "ELSE",因为进入 else 块后执行 "ELSE"
  • D: 如果 a 为假且 b 为假,则输出为 "notB",因为 if (!b) 会被执行。

最终答案:

  • A: 错误 — 因为 a 为真时,输出 "A"
  • B: 错误 — 因为 a 为真时,输出 "A",不需要检查 b
  • C: 正确 — 当 a 为假且 b 为真时,输出 "ELSE"
  • D: 正确 — 当 a 为假且 b 为假时,输出 "notB"

*14. class Bitwise
{public static void main(String [] args){int x = 11 & 9;int y = x ^ 3;System.out.println( y | 12 );}}

让我们逐步分析这段代码,理解每个位运算的操作,并确定最终的输出。

代码分析:

class Bitwise {public static void main(String [] args) {int x = 11 & 9;  // 第1步int y = x ^ 3;   // 第2步System.out.println(y | 12);  // 第3步}
}

步骤 1: x = 11 & 9

  • 这是一个按位与 (&) 操作。

  • 首先,我们将 119 转换为二进制:

    • 11 的二进制表示是 1011
    • 9 的二进制表示是 1001
    1011
    1001
    ----
    1001   // 结果是按位与操作后的值
    

    解释:按位与 (&) 操作是逐位比较两个二进制数的对应位。如果两个对应位都为 1,则结果为 1,否则为 0。所以:

    1011
    & 1001
    -----
    1001   // 结果是 9
    
  • 因此,x = 9

步骤 2: y = x ^ 3

  • 这是一个按位异或 (^) 操作。

  • x 现在是 9,我们将 93 转换为二进制:

    • 9 的二进制表示是 1001
    • 3 的二进制表示是 0011
    1001
    0011
    ----
    1010   // 结果是按位异或操作后的值
    

    解释:按位异或 (^) 操作是逐位比较两个二进制数的对应位。如果对应位相同,则结果为 0;如果不同,则结果为 1。所以上述操作结果为:

    1001
    ^ 0011
    -----
    1010   // 结果是 10
    
  • 因此,y = 10

步骤 3: y | 12

  • 这是一个按位或 (|) 操作。

  • y 现在是 10,我们将 1012 转换为二进制:

    • 10 的二进制表示是 1010
    • 12 的二进制表示是 1100
    1010
    1100
    ----
    1110   // 结果是按位或操作后的值
    

    解释:按位或 (|) 操作是逐位比较两个二进制数的对应位。如果两个对应位中至少有一个为 1,则结果为 1,否则为 0。所以:

    1010
    | 1100
    -----
    1110   // 结果是 14
    
  • 因此,y | 12 = 14

最终输出:

程序的最终输出是 14

结论:

14

15. public class X
{public static void main(String [] args){String names [] = new String[5];for (int x=0; x < args.length; x++)names[x] = args[x];System.out.println(names[2]);}}运行如下命令:java X a b程序的输出是?

让我们逐步分析这段代码,并解释程序的执行过程。

代码分析:

public class X {public static void main(String [] args) {String names [] = new String[5];  // 第1步for (int x = 0; x < args.length; x++)  // 第2步names[x] = args[x];  // 第3步System.out.println(names[2]);  // 第4步}
}

步骤 1: String names[] = new String[5];

  • 这里定义了一个 String 类型的数组 names,并为它分配了长度为 5 的空间。
  • 这意味着数组 names 可以存储 5 个字符串元素,初始化时,数组中的每个元素的值是 null

步骤 2: for (int x = 0; x < args.length; x++)

  • 这个循环遍历命令行参数 args,并将这些参数依次赋值给 names 数组。
  • args.length 是传入程序的命令行参数的数量。根据问题描述,命令行参数是 ab

步骤 3: names[x] = args[x];

  • 这个循环的作用是将 args 数组中的元素复制到 names 数组的对应位置。

对于命令行输入 java X a b

  • args[0]"a"args[1]"b"args.length 是 2。
  • 因此,names[0] = "a"names[1] = "b",其他位置(names[2]names[3]names[4])仍然是 null

步骤 4: System.out.println(names[2]);

  • 这里,程序打印 names[2]
  • 根据上面的分析,names[2] 没有被赋值,因此它的值是 null

运行命令:

命令是 java X a b,即传入了两个命令行参数:ab。因此:

  • args[0]"a"
  • args[1]"b"
  • args[2]args[3]args[4] 默认值为 null

结果:

程序会打印 names[2],但是 names[2]null,因此输出:

null

结论:

null

*16. 您希望一个类可以访问同一个包中另一个类的成员。 实现这一目标的最严格的访问是什么?

A.public

B. private

C. protected

D. default access

在Java中,类的成员(如字段、方法等)的访问权限通过访问修饰符进行控制。问题要求你选择最严格的访问权限,以便一个类能够访问同一个包中的另一个类的成员。

我们逐一分析选项:

A. public

  • public 访问修饰符表示成员可以被任何其他类访问,不受包或继承结构的限制。
  • 这意味着一个类可以从任何包中访问该成员,因此这不是最严格的访问权限。

B. private

  • private 访问修饰符表示成员只能在其所属类内部访问,其他类(即使在同一个包中)也无法访问。
  • 因此,private 不是一个适合在同一个包中访问的权限。它是最严格的访问控制,但不符合问题中的需求。

C. protected

  • protected 访问修饰符意味着成员可以被同一个包中的类以及所有继承该类的子类访问。
  • 但对于同一个包中的类,protected 的权限并不严格,因此它不是最严格的访问修饰符。

D. default access (包访问权限)

  • 当没有显式指定访问修饰符时,Java 中的成员具有 默认访问权限,即“包访问权限”(也称为 包级私有)。
  • 这意味着成员只能被同一个包中的其他类访问,不能被包外的类访问,也不能被子类在不同包中访问。
  • 这是一种相对严格的访问权限,因为它限制了类成员的访问范围,但仍允许同一包中的其他类访问。

正确答案:

最严格的访问控制是 private,但问题要求的是“同一个包中”能够访问成员。为了能够在同一个包中访问而不暴露给其他包,最严格的控制是 默认访问权限(即没有指定访问修饰符)。

因此,最严格的访问权限,同时允许同一个包内的其他类访问,是 D. default access


17. 程序的输出是?public class Test107 implements Runnable{private int x;private int y;public static void main(String args[]){Test107 that = new Test107();(new Thread(that)).start();(new Thread(that)).start();}public synchronized void run(){for(int i = 0; i < 10; i++){x++;y++;System.out.println("x = " + x + ", y = " + y); /* Line 17 */}}} A.编译错误B. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5... 但输出将由同时运行的两个线程产生.C. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5... 但输出将由第一个线程产生,然后由另一个线程产生。 这是由同步代码保证的。D. 以如下顺序打印: x = 1 y = 2 x = 3 y = 4 x = 5 y = 6 x = 7 y = 8...

代码分析

我们来看这段代码:

public class Test107 implements Runnable {private int x;private int y;public static void main(String args[]) {Test107 that = new Test107();(new Thread(that)).start();  // 创建并启动第一个线程(new Thread(that)).start();  // 创建并启动第二个线程}public synchronized void run() {for (int i = 0; i < 10; i++) {x++;   // 增加 x 的值y++;   // 增加 y 的值System.out.println("x = " + x + ", y = " + y);  // 打印当前的 x 和 y}}
}

关键点:

  • Test107 实现了 Runnable 接口,并且 run() 方法是 synchronized 的。
  • main 方法中创建了两个线程,并分别启动它们。
  • 这两个线程共享同一个 Test107 实例,因此它们会同时执行 run() 方法中的代码。
  • 由于 run() 方法是 synchronized 的,只有一个线程可以在任何时刻进入 run() 方法,另一个线程必须等待当前线程执行完毕后才能进入。

分析每个选项:

A. 编译错误
  • 代码不会编译出错,因为 Test107 正确实现了 Runnable 接口,且同步机制也正确使用。因此,选项 A 不是正确答案。
B. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5… 但输出将由同时运行的两个线程产生。
  • 这个描述不完全准确。虽然 run() 方法在多个线程中执行,但由于 run()synchronized 的,所以 两个线程不能同时执行 run() 方法。它们会交替执行,且每次只有一个线程可以打印 xy 的值。因此,输出将会由一个线程按顺序打印,另一个线程在第一个线程执行完当前循环的迭代后才会执行。
C. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5… 但输出将由第一个线程产生,然后由另一个线程产生。这是由同步代码保证的。
  • 这个选项描述了同步的行为是正确的。由于 run() 方法是同步的,两个线程会交替执行每个循环,而每个线程对 xy 的增加都是互斥的。
  • 因此,输出将由一个线程先连续打印出 10 次 x = n, y = n,然后第二个线程打印接下来的 10 次输出。最终的输出顺序将是由两个线程交替产生的。
D. 以如下顺序打印: x = 1 y = 2 x = 3 y = 4 x = 5 y = 6 x = 7 y = 8…
  • 这个选项是不正确的。由于每个线程每次都递增 xy,它们会同步递增,而不是 xy 交替递增。因此,xy 的值会是相等的,而不是交替的。

结论

正确的答案是 C

因为 run() 方法是 synchronized 的,两个线程会交替执行,并且每次执行时都会按顺序打印出 xy 的递增值,而不会发生并发问题。

在Java中,synchronized 是一个关键字,用于实现 同步(synchronization)。它用于控制对共享资源的访问,以确保多个线程在执行某段代码时,能够按照一定的顺序进行,从而避免并发问题。

1. synchronized 的作用

当一个方法或代码块被声明为 synchronized 时,Java 会确保 同一时刻只有一个线程 能够执行这段同步代码。其他线程必须等待当前线程执行完毕才能进入同步方法或同步代码块。

这对于防止多个线程同时访问并修改共享资源(比如变量、数据结构等)时引发的数据不一致或竞争条件(race condition)非常重要。

2. 如何使用 synchronized

a. 同步方法

synchronized 可以用于方法的声明,表示该方法是同步的。

public synchronized void exampleMethod() {// 只有一个线程可以在任何时刻执行这个方法x++;  // 访问共享资源y++;System.out.println(x + ", " + y);
}

在这种情况下,同一时刻只能有一个线程 进入 exampleMethod() 方法。其他线程在方法执行完之前,必须等待。

b. 同步代码块

synchronized 还可以用于方法内部的代码块。通过指定一个对象锁,线程在执行同步代码块时,必须首先获得该对象的锁。

public void exampleMethod() {synchronized(this) {// 只有一个线程可以在任何时刻执行这个代码块x++;y++;System.out.println(x + ", " + y);}
}

在这个例子中,只有获得 this 对象锁的线程才能执行 synchronized 代码块。如果多个线程同时调用该方法,它们必须争夺 this 对象的锁。

c. 静态同步方法

synchronized 还可以用于类的静态方法,表示该方法在类级别上进行同步。

public static synchronized void exampleStaticMethod() {// 同样,只有一个线程可以执行这个静态同步方法
}

静态同步方法使用 类对象(而不是实例对象)作为锁。

3. 为什么使用 synchronized

Java中的多线程程序常常需要访问共享资源。如果多个线程同时访问共享资源且没有适当的同步机制,就可能会发生并发问题,比如:

  • 数据不一致:多个线程同时修改共享变量,导致无法预测的结果。
  • 竞态条件(Race Condition):两个线程竞争某些资源,导致程序表现出不稳定的行为。

synchronized 通过使得同一时刻只有一个线程能执行关键代码,从而避免这些并发问题。

4. synchronized 的工作原理

当多个线程试图执行一个同步方法时,锁机制会确保同一时刻只有一个线程可以进入该方法或代码块。锁可以是:

  • 实例锁:针对对象实例(this),通常是方法中的同步块或非静态方法。
  • 类锁:针对类本身,通常是静态方法。
举例说明:

假设有两个线程 T1T2,它们同时调用一个同步方法。由于同步方法的锁机制,T1 会先获取锁并执行该方法,而 T2 必须等到 T1 执行完后才能进入该方法。

5. 性能考虑

虽然 synchronized 关键字能有效解决线程同步问题,但它也带来了一些性能开销。因为:

  • 获取和释放锁需要时间。
  • 当多个线程频繁访问同步方法时,会出现线程竞争,可能导致线程的阻塞和上下文切换,影响性能。

6. 小结

  • synchronized 是一种用于实现线程同步的机制,确保在多线程环境下对共享资源的访问是安全的。
  • 它可以用在方法和代码块上。
  • 使用 synchronized 会引入一定的性能开销,因此需要小心使用,尤其是在高并发场景中。

下列说法正确的是

A.有时明确抛出 AssertionError 是一种很好的做法。

B. 私有 getter() 和 setter() 方法不应使用断言来验证参数。

C. 如果在 try-catch 块中抛出 AssertionError,finally 块将被绕过。

D. 使用 catch (AssertionException ae) 块处理断言语句失败是正确的

我们来看一下每个选项的分析,并解释正确与否:

A. 有时明确抛出 AssertionError 是一种很好的做法。

  • 错误。一般来说,断言(assertion) 用于开发阶段进行内部错误检查,确认程序的假设条件,而不是用作正常的错误处理机制。AssertionError 通常是在程序不符合预期时自动抛出的,而不应该主动抛出。主动抛出 AssertionError 会混淆代码的意图和错误的处理方式,可能会让代码不易理解和维护。

    正确做法

    • assert 语句本身用于断言条件,不应该通过代码显式抛出 AssertionError。如果需要进行错误处理,应使用适当的异常(如 IllegalArgumentException 等)。

B. 私有 getter() 和 setter() 方法不应使用断言来验证参数。

  • 正确getter()setter() 方法通常是公开访问对象状态的方式,而断言的目的是用于验证假设条件,通常是在开发过程中用于调试和检测内部错误。对于公共方法的参数验证,应该使用异常处理来进行,而不是使用断言。
    • 断言 在生产环境中默认是关闭的,所以依赖断言进行参数验证会导致程序行为不可预测。应该使用如 IllegalArgumentException 等标准异常来验证方法参数。

C. 如果在 try-catch 块中抛出 AssertionError,finally 块将被绕过。

  • 错误。无论 try 块中的代码抛出什么异常,finally 块总是会被执行。finally 块无论是否发生异常都会执行,除非程序在执行期间被终止(如调用 System.exit() 或发生严重错误导致JVM崩溃)。

    • 所以,即使在 try-catch 块中抛出 AssertionErrorfinally 仍然会执行。finally 块的主要目的是释放资源等清理工作。

D. 使用 catch (AssertionException ae) 块处理断言语句失败是正确的

  • 错误。Java 中并没有 AssertionException 这个异常类。assert 语句失败时会抛出一个 AssertionError,而不是 AssertionException。因此,应该使用 catch (AssertionError ae) 来捕获断言失败抛出的错误,而不是 AssertionException

    另外,断言失败的目的是标识程序中的逻辑错误,通常不应该通过 try-catch 块来捕获它。一般情况下,AssertionError 应该是由程序员在调试时用来检查假设错误的,正常的错误处理应该使用合适的异常类型。

总结:

  • A. 错误:不应显式抛出 AssertionError
  • B. 正确:不应使用断言来验证 gettersetter 方法的参数。
  • C. 错误finally 块无论如何都会执行。
  • D. 错误:应该捕获 AssertionError,而不是 AssertionException

正确的答案是:B


下列说法正确的是

A.调用 Runtime.gc() 将导致符合条件的对象被垃圾回收。

B.垃圾收集器使用标记和清除算法。

C. 如果一个对象可以从一个活动线程中访问,它就不能被垃圾回收。

D. 如果对象 1 引用对象 2,则对象 2 不能被垃圾回收

让我们分析每个选项,找出正确的说法。

A. 调用 Runtime.gc() 将导致符合条件的对象被垃圾回收。

  • 错误Runtime.gc() 提示 JVM 进行垃圾回收,但它并不是强制性的,并不能保证垃圾回收一定会发生。调用 gc() 方法只是给垃圾收集器发出了建议,实际上,是否执行垃圾回收、何时执行垃圾回收完全取决于垃圾收集器的实现和当前的内存状况。垃圾回收的决定是由 JVM 根据内存压力来做的,而不是单纯由 gc() 方法的调用决定

B. 垃圾收集器使用标记和清除算法。

  • 正确标记-清除(Mark-and-Sweep)是垃圾回收的一种经典算法。其过程通常如下:

    1. 标记阶段:首先标记所有活动对象,这些对象是可达的(即从根对象可访问的对象)。
    2. 清除阶段:在标记之后,垃圾收集器会回收那些没有被标记的对象,即不可达的对象。

    这种算法的优点是简单易懂,但也有一些缺点,比如会导致内存碎片问题。现代的垃圾收集器(如 G1、ZGC 等)通常在标记-清除的基础上进行了优化。

C. 如果一个对象可以从一个活动线程中访问,它就不能被垃圾回收。

  • 正确。如果一个对象是可达的(即可以通过活动线程或者其他任何途径访问到该对象),那么它不会被垃圾回收器回收。垃圾回收的主要依据是对象是否可达(reachable)。如果对象不再被任何活动线程或其他对象引用,那么它就变成了不可达对象,有可能会被垃圾回收。

D. 如果对象 1 引用对象 2,则对象 2 不能被垃圾回收。

  • 正确。如果对象 1 引用了对象 2,这意味着对象 2 是可达的。因此,对象 2 在垃圾回收时不会被回收,直到对象 1 不再引用对象 2 为止。只有那些没有任何引用(即不可达)的对象,才会被垃圾回收。

总结:

  • A. 错误Runtime.gc() 只是一个建议,不保证垃圾回收会发生。
  • B. 正确:垃圾收集器使用标记和清除算法。
  • C. 正确:如果对象可以从活动线程中访问,它就不能被垃圾回收。
  • D. 正确:如果对象 1 引用对象 2,对象 2 就不会被垃圾回收。

正确的答案是:B, C, D


20. public class Test
{public void foo(){assert false; /* Line 5 */assert false; /* Line 6 */}public void bar(){while(true){assert false; /* Line 12 */}assert false; /* Line 14 */}}哪一个会导致编译失败A.Line 5B. Line 6C. Line 12D. Line 14

让我们仔细分析代码中涉及断言(assert)的部分。

public class Test
{public void foo(){assert false; /* Line 5 */assert false; /* Line 6 */}public void bar(){while(true){assert false; /* Line 12 */}assert false; /* Line 14 */}
}

断言(assert)的工作原理

在 Java 中,断言是通过 assert 语句实现的,通常用于在开发过程中检查程序的假设条件。断言默认在运行时是禁用的,只有通过 -ea-enableassertions)选项显式启用时,断言才会执行。如果没有启用断言,assert 语句将不会影响程序的行为。

编译阶段

  • assert 语句本身并不会导致编译错误。编译器会接受包含 assert 语句的代码,只要没有语法错误。

运行时

  • 只有在运行时启用断言时,assert 语句才会生效。否则,它们会被忽略。

逐行分析

  1. Line 5 和 Line 6 (assert false;)

    • 这些是标准的断言语句,不会导致编译错误。无论是启用断言还是禁用断言,都会被编译器接受。
  2. Line 12 (assert false;)

    • while(true) 循环内部,assert false 不会导致编译失败。assert 语句本身是有效的。循环体内部的断言会在循环中每次执行时被执行,但它不会导致编译错误。
  3. Line 14 (assert false;)

    • 这一行是位于 while(true) 循环外部的断言。这不会导致编译错误,除非有语法问题或者错误的使用方式。但是在 Java 中,assert 语句本身并不会因其位置或上下文而导致编译失败。

结论

没有一行代码会导致编译错误。Java 编译器会接受所有的 assert 语句。关键在于是否启用了断言(-ea),但即使不启用,assert 语句本身也不会阻止程序编译。

正确答案是: 没有选项会导致编译失败


  1. 请用java实现一个函数。输入是一个数组,数组的元素都是数字,里面的数字有重复出现的。函数返回一个新的数组,是对输入数组去重的结果??????
package org.example;import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;public class RemoveDuplicates {public static int[] removeDuplicates(int[] inputArray) {// 创建一个 HashSet 来去重Set<Integer> uniqueSet = new HashSet<>();// 将数组元素添加到 HashSet 中,自动去除重复元素for (int num : inputArray) {uniqueSet.add(num);}// 将 Set 转换为新的数组int[] resultArray = new int[uniqueSet.size()];int index = 0;for (int num : uniqueSet) {resultArray[index++] = num;}// 返回去重后的数组return resultArray;}public static void main(String[] args) {// 示例输入
//        int[] inputArray = {1, 2, 2, 3, 4, 4, 5, 5, 5};Scanner scanner  = new Scanner(System.in);System.out.println("请输入数组长度");int n  = scanner.nextInt();int[] inputArray = new int[n];System.out.println("请输入数组元素");for (int i = 0; i < n; i++) {inputArray[i] = scanner.nextInt();}// 调用去重函数int[] resultArray = removeDuplicates(inputArray);// 输出结果System.out.print("去重后的数组: ");for (int num : resultArray) {System.out.print(num + " ");}}
}

在 Java 中,线程的生命周期包括多个不同的状态。Java 线程的状态是通过 Thread.State 枚举类来定义的。下面是 Java 线程的几种常见状态:

1. NEW (新建)

  • 线程刚创建,但还未开始执行时的状态。
  • 线程对象被创建之后,调用 start() 方法之前,线程处于此状态。
  • 例如:
    Thread thread = new Thread();
    System.out.println(thread.getState()); // 输出:NEW
    

2. RUNNABLE (可运行)

  • 线程已经启动,但还未开始执行,或者正在执行的状态。
  • 线程在操作系统的线程调度器中处于等待执行的队列中。当 CPU 分配时间片后,线程将变为 RUNNABLE 状态。
  • 注意:Java 中没有明确的 Running 状态,只有 RUNNABLE 状态。即使线程正在运行,它仍然处于 RUNNABLE 状态。
  • 例如,线程调用 start() 后进入 RUNNABLE 状态。
    Thread thread = new Thread(() -> System.out.println("Running..."));
    thread.start(); // 线程进入 RUNNABLE 状态
    

3. BLOCKED (阻塞)

  • 线程因尝试获取一个对象的锁而被阻塞,直到能够成功获取锁。
  • 当一个线程想访问同步方法或同步块时,如果其他线程已经持有锁,当前线程将被阻塞,直到锁可用为止。
  • 例如:
    synchronized (lock) {// 当前线程如果无法获得 lock 锁,就会进入 BLOCKED 状态
    }
    

4. WAITING (等待)

  • 线程正在等待其他线程执行特定操作,通常是调用 Object.wait() 方法后进入此状态,或者调用 Thread.join() 等方法等待其他线程完成。
  • 线程进入 WAITING 状态后,必须等待其他线程通过 notify()notifyAll() 方法唤醒它。
  • 例如:
    synchronized (lock) {lock.wait(); // 当前线程进入 WAITING 状态
    }
    

5. TIMED_WAITING (定时等待)

  • 线程在一个固定的时间段内等待。线程调用 Thread.sleep(milliseconds)Object.wait(time)Thread.join(time) 等方法时进入此状态。
  • 线程会等待指定的时间,超时后自动回到 RUNNABLE 状态。
  • 例如:
    Thread.sleep(1000); // 线程进入 TIMED_WAITING 状态,等待 1000 毫秒
    

6. TERMINATED (已终止)

  • 线程执行完毕或由于异常终止时的状态。
  • 当线程的 run() 方法执行完毕或线程因未捕获的异常退出时,它进入 TERMINATED 状态。
  • 线程不能再返回到其他状态,已经结束生命周期。
  • 例如:
    Thread thread = new Thread(() -> {// 执行完毕
    });
    thread.start();
    // 等待线程终止
    thread.join(); // 线程进入 TERMINATED 状态
    

总结:线程状态的转换

  • NEWstart()RUNNABLE(进入就绪队列等待 CPU 分配)
  • RUNNABLEsynchronizedjoin() 等 → BLOCKED
  • RUNNABLEwait()WAITING
  • RUNNABLEsleep()join(time) 等 → TIMED_WAITING
  • RUNNABLE → 执行完毕或因异常退出 → TERMINATED

状态图

    NEW|vRUNNABLE <-- BLOCKED|          ^v          |WAITING <---- TIMED_WAITING|vTERMINATED

这些是 Java 线程生命周期中的主要状态,它们由 JVM 和操作系统的线程调度机制共同管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/59990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【51单片机】UART串口通信原理 + 使用

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 串口硬件电路UART串口相关寄存器 编码单片机通过串口发送数据电脑通过串口发送数据控制LED灯 串口 串口是一种应用十分广泛的通讯接…

构建智能防线 灵途科技光电感知助力轨交全向安全防护

10月27日&#xff0c;在南京南站至紫金山东站间的高铁联络线上&#xff0c;一头野猪侵入轨道&#xff0c;与D5515次列车相撞&#xff0c;导致设备故障停车。 事故不仅造成南京南站部分列车晚点&#xff0c;还在故障排查过程中导致随车机械师因被邻线限速通过的列车碰撞而不幸身…

不使用递归的决策树生成算法

不使用递归的决策树生成算法 利用队列 queue &#xff0c;实现层次遍历&#xff08;广度优先遍历&#xff09;&#xff0c;逐步处理每个节点来建立子树结构。再构建一个辅助队列&#xff0c;将每个节点存储到 nodes_to_process 列表中&#xff0c;以便在树生成完成后可以反向遍…

【PB】 使用for循环,循环次数比较多时,datastore 获取数据异常的问题。

以往在使用datastore时&#xff0c;不注意及时销毁&#xff0c;毕竟一次处理数据&#xff0c;数量很少。 本次碰到一个问题&#xff0c;批量处理数据&#xff0c;for循环次数在1000次左右&#xff0c;每个for循环处理 3 个函数&#xff0c;每个函数中有3-4个datastore&#xff…

自动驾驶系列—自动驾驶如何实现厘米级定位?深入解读GPS/RTK技术与应用

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

DevOps业务价值流:需求设计最佳实践

DevOps实践正推动着产品快速迭代与高质量交付&#xff0c;但需求设计作为产品开发的关键起点&#xff0c;往往被忽视。它不仅是收集与分析需求的过程&#xff0c;更是将需求转化为可实施产品特性的核心。本文深入探讨DevOps业务价值流中的需求设计&#xff0c;从调研、整理、原…

【MySQL】数据库整合攻略 :表操作技巧与详解

前言&#xff1a;本节内容讲述表的操作&#xff0c; 对表结构的操作。 是对表结构中的字段的增删查改以及表本身的创建以及删除。 ps&#xff1a;本节内容本节内容适合安装了MySQL的友友们进行观看&#xff0c; 实操更有利于记住哦。 目录 创建表 查看表结构 修改表结构 …

python可视化进阶

引用&#xff1a; 首先需要安装 plotnine from plotnine import* import joypy数据可视化进阶操作 3.1 类别数据可视化 【例3-1】——绘制简单条形图 【代码框3-1】——绘制简单条形图 # 图3-1的绘制代码 import pandas as pd import matplotlib.pyplot as plt from cvxpy …

使用 GitHub Actions 部署到开发服务器的详细指南

使用 GitHub Actions 部署到开发服务器的详细指南 在本篇博客中&#xff0c;我们将介绍如何使用 GitHub Actions 实现自动化部署&#xff0c;将代码从 GitHub 仓库的 dev 分支自动部署到开发服务器。通过这种方式&#xff0c;可以确保每次在 dev 分支推送代码时&#xff0c;服…

反汇编命令学习以及分析越界和空指针问题

1,反汇编命令行 (1)move 语法格式:mov destination, source例如: mov eax,0x1 ;将立即数1复制到eax寄存器。立即数到寄存器mov [ebx],eax ;将eax寄存器的值复制到ebx寄存器指向的内存地址,寄存器到内存mov eax,ebx ;将ebx寄存器的值复制到eax,寄存器到寄存器mov ea…

冒泡选择法(c基础)

适合对象c语言初学者。 冒泡选择法 作用对一个数组进行排序。&#xff08;介绍一下数组(c基础)(详细版)-CSDN博客&#xff09; 核心要点 1: 数组元素个数 sz 2: 比较后的交换。 核心思路 进行&#xff08;sz - 1&#xff09;趟&#xff0c;每一趟把最大数的放到末尾。其…

Shell脚本语法随笔

文章目录 1、编写 Shell 脚本文件1_脚本结构2_示例3_执行脚本 2、变量的定义与使用1_定义变量2_只读变量3_接受用户输入4_删除变量名5_变量作用域 3、字符串处理1_双引号 vs 单引号2_示例 4、条件判断&运算符1_数值比较2_case示例3_算数运算符4_逻辑运算符5_字符串运算符6_…

量子计算及其在密码学中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 量子计算及其在密码学中的应用 量子计算及其在密码学中的应用 量子计算及其在密码学中的应用 引言 量子计算概述 定义与原理 发展…

【论文笔记】Wings: Learning Multimodal LLMs without Text-only Forgetting

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Wings: Learning Multimod…

数据类型底层

计算机的工作原理 CPU 找数据 数据通过地址信息来标记 高级语言:在内存中"分配"空间用变量来标识 所以变量一定是存在地址的 例如: int a10; //a就是变量名用来对地址进行标识 0x100对这个地址标识必备常识:8bit1byte 常见的数据类型: char short int long //…

【Leecode】Leecode刷题之路第45天之跳跃游戏II

题目出处 45-跳跃游戏II-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 45-跳跃游戏II-官方解法 这道题是典型的贪心算法&#xff0c;通过局部最优解得到全局最优解。以下两种方法都是…

【Allure】mac下环境配置

安装 1.Mac 可以使用 brew 安装 allure&#xff0c;安装命令如下 brew install allure 2.与 pytest 结合需要安装 allure-pytest 插件&#xff1a; pip install allure-pytest3.查看allure版本 allure --version

conan2 c/c++包管理菜鸟入门

以官网教程为例。 首先下载官网示例&#xff1a; git clone https://github.com/conan-io/examples2.git cd examples2/tutorial/consuming_packages/simple_cmake_project 然后进入该示例教程 先 conan profile detect 检测一下当前编译器环境是否配置&#xff0c; 然后…

20221428欧阳慕蓉 第九周预习报告

AI对学习内容的总结 第九章的内容主要围绕进程和系统调用的概念&#xff0c;以及如何在C程序中使用这些概念来创建和管理进程。以下是本章的主要内容总结&#xff1a; 系统调用&#xff08;System Calls&#xff09;&#xff1a; 系统调用是C程序用来与操作系统内核交互的函数…

C#语言发展历史

前言 C#是微软公司在2000年6月发布的一种新的编程语言&#xff0c;主要由安德斯海尔斯伯格&#xff08;Anders Hejlsberg&#xff09;主持开发&#xff0c;它是第一个面向组件的编程语言&#xff0c;其源码会编译成msil再运行。 C#最初有个更酷的名字&#xff0c;叫做COOL。微软…