java 高阶-并发探究02

Java线程基础

线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?

通常线程有哪几种使用方式?

基础线程机制有哪些?

线程的中断方式有哪些?

线程的互斥同步方式有哪些? 如何比较和选择?

线程之间有哪些协作方式?

线程状态转换

image

如上描述了java线程的生命周期状态

1.New

线程对象已经创建,但还没有调用 start() 方法。

线程处于 “New” 状态,直到 Thread.start() 方法被调用。

2.Runnable(就绪/运行状态)

当调用 Thread.start() 方法时,线程进入 “Runnable” 状态。

此状态意味着线程准备运行,可能已经在运行,也可能正在等待 CPU 调度。

线程有机会获得 CPU 并执行其任务,但并不意味着线程一定正在执行。

3.Blocked

阻塞状态

当线程试图获取一个锁(如通过 synchronized 关键字)时,线程进入 “Blocked” 状态。

线程进入阻塞状态是因为某些资源(如锁)被其他线程占用,当前线程无法继续执行。

4..Time Waiting(超时等待状态)

当线程调用 Thread.sleep() 方法时,它进入 “Time Waiting” 状态。

线程会在指定时间内暂停执行,直到时间到达或被其他线程中断。

5.Waiting(等待状态)

当线程调用 Object.wait() 方法时,它进入 “Waiting” 状态,等待其他线程调用 notify()notifyAll()

线程将一直处于等待状态,直到被显式唤醒。

6.Terminated(终止状态)

当线程执行完成或由于异常而终止时,线程进入 “Terminated” 状态。

这个状态表示线程的生命周期结束,不能再重新启动。

7.图状态转换
1
2
3
4
5
6
7
8
9
Thread.start():将线程从 "New" 状态转为 "Runnable" 状态。
synchronized:获取锁失败时,线程进入 "Blocked" 状态。
Thread.sleep():使线程进入 "Time Waiting" 状态。

Object.wait():使线程进入 "Waiting" 状态。
Object.notify() / notifyAll():唤醒等待状态中的线程,转到 "Runnable"


线程执行结束 或 异常:线程进入 "Terminated" 状态。

线程使用方法

image-20241023224050918

1.Runnable

Runnable 是一个功能性接口,定义了一个 run() 方法,任务逻辑写在这个方法中。

它不返回任何结果,也不能抛出检查型异常。

虽然实现了 Runnable 的类可以封装任务逻辑,但它本身不是线程,最后需要通过 Thread 对象来驱动任务执行。

1
2
3
4
5
6
7
8
9
10
11
12
public class runa implements Runnable{
@Override
public void run() {
System.out.println("Runnable is running");
}
public static void main(String[] args) {
runa r = new runa();
new Thread(new runa()).start();
}
}


Callable

Callable 接口类似于 Runnable,但它的 call() 方法可以返回一个结果并且可以抛出异常。

由于 Callable 可以有返回值,一般会通过 FutureTask 来包装并通过线程运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class runa implements Runnable{
public class call implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call is call");
return 1;
}
}
@Override
public void run() {
System.out.println("Runnable is running");
}
public void mainQ(String[] args) {
///
FutureTask<Integer> task = new FutureTask<>(new call());
new Thread(task).start();
try {
System.out.println(task.get());
} catch (Exception e) {
e.printStackTrace();
}

}
public static void main(String[] args) {
runa r = new runa();
r.mainQ(args);
}
}

3.Thread

直接继承 Thread 类并重写其 run() 方法,将任务逻辑写入其中。

这种方式相比实现接口,耦合性更高,因为继承 Thread 限制了类的多继承性。

1
2
3
4
5
6
7
8
9
10
11
public class myThread extends  Thread{
@Override
public void run() {
System.out.println("Thread is running");
}
}
public void mainto(String[] args) {
myThread t = new myThread();
t.start();
System.out.println("main is running");
}

实现 RunnableCallable 接口的类并不是线程本身,它们是定义了任务逻辑的类,必须通过 Thread 对象或线程池来执行。

Runnable 是没有返回值的,而 Callable 可以返回结果并抛出异常。

继承 Thread 是直接将类本身变为线程,但通常推荐使用 RunnableCallable,以避免不必要的类继承。

基础线程机制

Executor

Executor 是 Java 中用于管理和控制线程执行的一个接口,它是 Java 并发框架的一部分,简化了线程的使用和管理。

Executor 是一个简单的接口,定义了一个 execute(Runnable command) 方法,用于接受一个 Runnable 任务并将其执行。它并不负责任务的调度和管理,只是负责将任务提交给线程执行。

1
ExecutorService

ExecutorServiceExecutor 的一个子接口,提供了更高级的功能,包括:

  • 提交任务并获取结果。
  • 任务的调度和管理。
  • 优雅地关闭服务。

image-20241024102900042

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ExecutorServiceTest {
public static void main(String[] args) {
//创建可缓存线程池

ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<10;i++)
{
final int index = i;
service.submit(()->{
System.out.println("Executing task " + index + " by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
// 关闭线程池
service.shutdown();

}
}

Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。

使用 setDaemon() 方法将一个

线程设置为守护线程。


守护线程(Daemon Thread)是 Java 中的一种特殊线程,它的主要特性是:当所有用户线程(非守护线程)结束时,JVM 会自动退出,守护线程不会阻止 JVM 的退出。,即使还有守护线程在运行,JVM 也不会等待守护线程完成。。这种线程通常用于执行一些后台任务,如垃圾回收、监控等。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DaemonThreadExample {
public static void main(String[] args) {

Thread thread = new Thread(() -> {
while (true) {
System.out.println("Thread is running...");
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 3; i++) {
System.out.println("主线程正在运行...");
try {
Thread.sleep(500); // 模拟任务执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("主线程结束.");
}
}
sleep

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

1
2
3
4
5
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
yield

对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。


1
2
3
public void run() {
Thread.yield();
}

线程中断

中断的目的:中断机制允许一个线程通知另一个线程停止执行或执行某些操作。线程可以在适当的时候检查中断状态,并决定是否响应中断。

中断标志每个线程都有一个中断状态,表示该线程是否被请求中断。可以通过调用 Thread.interrupt() 方法来设置一个线程的中断标志。

检查中断状态

  • 使用 Thread.currentThread().isInterrupted() 方法检查当前线程的中断状态。
  • 使用 Thread.interrupted() 方法检查并清除当前线程的中断状态。
1.InterruptedException

当一个线程正在执行某些阻塞操作(如 Thread.sleep()Object.wait()LockSupport.park() 等)时,如果它被中断,会抛出 InterruptedException 异常。处理这个异常是非常重要的,因为这通常意味着线程应该停止执行。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。

InterruptedException 是一个检查型异常,表示线程在等待或阻塞状态时被中断。

Thread.sleep():如果线程在调用 Thread.sleep() 时被中断,会抛出 InterruptedException

Object.wait():如果线程在等待某个对象的监视器时被中断,也会抛出此异常。

LocklockInterruptibly() 方法中:如果线程尝试获取锁但被中断,同样会抛出 InterruptedException

1
2
3
对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。
------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DaemonThreadExample {
public static void main(String[] args) {

Thread thread = new Thread(() -> {

System.out.println("Thread is running...");
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
// 进行清理操作
// 可能需要保存状态、释放资源等

}

});

thread.start();
while (true) {

//线程阻塞阶段被打断
thread.interrupt();
}


}
}

interrupted()

如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。

但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class DaemonThreadExample {
public static void main(String[] args) {

Thread thread = new Thread(() -> {
//可以判断是否被中断了-可以提前结束线程
System.out.println("Thread is running...");
while (!interrupted())
{
System.out.println("Thread is running...");
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
// 进行清理操作
// 可能需要保存状态、释放资源等

}
}

});

thread.start();


//线程阻塞阶段被打断
thread.interrupt();


}
}

Executor 的中断操作

调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。//全部进入线程中继

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DaemonThreadExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdownNow();
System.out.println("Main run");

}
}
1
2
3
4
5
6
7
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at DaemonThreadExample.lambda$main$0(DaemonThreadExample.java:11)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

线程互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

1.synchronized

Synchronized 关键字是 Java 中最简单的实现互斥的方式。它自动加锁和解锁,确保同一时刻只有一个线程可以执行同步方法。

1
2
3
public  synchronized   void  increment() {
count++;
}

synchronized 保证了隐式锁定当前对象实例 (this),从而确保了线程安全。

同步代码块

果你不想整个方法都被锁住,而只是想对某一部分代码进行同步,可以使用同步代码块。在 Java 中,使用 synchronized 关键字对某个特定的对象或类进行加锁

1
2
3
4
5
6
7
8
9
10
    private int count=0;
private final Object o= new Object();

public void increment() {
synchronized (o)
{
count++;
}


这里使用了 synchronized(lock) 对代码块进行加锁,而不是整个方法。这样可以更灵活地控制锁的粒度,提升程序性能。

ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SafeCounter {
private int count=0;
private final Lock lock=new ReentrantLock();
public void increment() {
lock.lock();
try{
count++;
}finally {
lock.unlock();
}

}
public int getCount() {
return count;
}

public static void main(String[] args) throws InterruptedException {
SafeCounter s = new SafeCounter();
Runnable runnable = () -> {

s.increment();

};
Thread[] threads = new Thread[1000];
for(int i=0;i<1000;i++)
{
Thread thread1 = new Thread(runnable);
threads[i]=thread1;
thread1.start();
}
for (int i = 0; i < 1000; i++) {
threads[i].join(); // 等待每个线程结束
}


System.out.println(s.getCount());
}
}

3.比较

image-20241024110328270

线程之间协作

1.join

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

1
DaemonThreadExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

public class DaemonThreadExample {

private class A extends Thread {
@Override
public void run() {
System.out.println("A");
}
}

private class B extends Thread {

private A a;

B(A a) {
this.a = a;
}

@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}

public void test() {
A a = new A();
B b = new B(a);
b.start();
a.start();
}

public static void main(String[] args) {
DaemonThreadExample d = new DaemonThreadExample();
d.test();
}

}
2.wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DaemonThreadExample {

public synchronized void before() {
System.out.println("before");
notifyAll();// 唤醒所有线程
}

public synchronized void after() {
try {
wait();// 等待被唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}


public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
DaemonThreadExample example = new DaemonThreadExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());

}

}

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。
3.await() signal() signalAll()

ava.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

使用 Lock 来获取一个 Condition 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DaemonThreadExample {

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}

public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}


public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
DaemonThreadExample example = new DaemonThreadExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());

}

}

java 高阶-并发探究02
http://example.com/2024/10/23/java/java并发/java并发2/
作者
John Doe
发布于
2024年10月23日
许可协议