第一章 多线程入门之基础
<<java并发编程之美>>第一章总结
一、
进程:代码在数据集合上一次执行过程,方法区,堆,常量池是共享资源,进程是资源分配的基本单位
线程:进程的一个执行路径,程序计数器和栈是私有资源,线程是cpu资源调度的基本单位
启动main函数相当于启动JVM进程,main函数是main线程
1、线程实现
线程实现有三种方式:继承Thread,实现Runnable,实现Callable三种
//单继承,多个线程执行相同的逻辑只需要多个任务代码
calss MyThread extends Thead{
@override
public void run(){
}
}
//多个线程执行相同的逻辑只需要一个任务代码,可以通过参数进行任务区分
public class Downloader implements Runnable {
private String url;
public Downloader(String url) {
this.url = url;
}
public void run() {
//根据不同的url 实现不同地下载逻辑
}
}
// 创建多个下载任务
Downloader task1 = new Downloader("http://example.com/file1");
Downloader task2 = new Downloader("http://example.com/file2");
Downloader task3 = new Downloader("http://example.com/file3");
// 创建线程并启动
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
//callable有返回值,并且可以抛出异常
public class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
// 执行任务,并返回结果
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
public class Main {
public static void main(String[] args) throws Exception {
// 创建Callable对象
Callable<Integer> myCallable = new MyCallable();
// 创建FutureTask对象
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
// 创建线程并启动
Thread thread = new Thread(futureTask);
thread.start();
// 获取任务结果
int result = futureTask.get();
System.out.println("Result: " + result);
}
}
2、线程的基本方法
sleep
不占用cpu资源,不释放锁
yield
t1的run方法里面执行Thread.yield()方法意味着t1此时会让出cpu,让自己变成就绪状态,此时cpu会从就绪队列里面挑出一个优先级最高的线程优先执行。
join
在t2的代码执行t1.join()那么t2被阻塞直到t1运行结束,t2使用wait进行阻塞
wait
必须先获得锁,不占用cpu资源,释放锁 ,可以使用notify和interrupt唤醒
//虚假唤醒:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒
while(条件){
o.wait();
}
interrupt
//interrupt()main线程里面可以调用t1.interrupt()将t1的线程中断标志设为true
//如果t1线程因为join(),wait(),或者sleep()方法发生阻塞,那么t1线程会因为主线程调用了t1.interrupt()方法报错
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//如果当前线程被中断则退出循环
while (!Thread.currentThread().isInterrupted())
System.out.println(Thread.currentThread() + " hello");
}
});
//启动子线程
thread.start();
//主线程休眠1s,以便中断前让子线程输出
Thread.sleep(1000);
//中断子线程
System.out.println("main thread interrupt thread");
thread.interrupt();
//等待子线程执行完毕
thread.join();
System.out.println("main is over");
}
//isInterrupted()实例方法不会清除中断标志
//类方法,获取的是当前线程的中断标志,即使在main线程里面调用了t1.interrupted()方法获得也是main线程的中断标志会自动清除线程的中断标志
park和unpark
park和unpark以线程为单位进行等待和唤醒,unpark可以先于park执行。park不会自动释放锁,只能被中断或者唤醒。
import java.util.concurrent.locks.LockSupport;
public class ParkUnparkExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("Thread 1 started");
// 线程 1 调用 park 方法等待
LockSupport.park();
System.out.println("Thread 1 resumed");
});
Thread t2 = new Thread(() -> {
System.out.println("Thread 2 started");
// 线程 2 调用 unpark 方法唤醒线程 1
LockSupport.unpark(t1);
});
t1.start();
t2.start();
}
}
3、线程基本概念
线程状态
- NEW:线程被创建完但是没有t.start()
- RUNNABLE:t.satrt()之后就是RUNNABLE,分为READY和RUNNING两种
- BLOCKED:阻塞状态不会占用cpu资源,线程等待锁释放和IO阻塞
- WAITING:当前线程执行到wait()方法或者LoclSuppot().unpark()方法
- TIMED_WAITING:Thread.sleep(time),Object.wait(time)或者LockSupport.parkNacos(time)方法限时等待
- TREMINATE:线程结束任务或者线程执行过程中发生了异常
上下文切换
当前线程时间片用完之后就会处于就绪状态
死锁
互斥条件,环路等待条件,请求并持有条件,不可剥夺条件,我们可以破换的是环路等待条件和请求并持有条件,用 jps 定位进程 id,再用 jstack id 定位死锁,找到死锁的线程去查看源码,解决优化。
活锁
线程1和线程2都试图互相让步,导致它们在不断地切换执行,但是最终没有一个线程能够进展,陷入活锁
public class LiveLockExample {
public static void main(String[] args) {
final Object lock = new Object();
boolean flag = true;
Thread thread1 = new Thread(new Runnable() {
public void run() {
while (flag) {
synchronized (lock) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore
}
System.out.println("Thread 1 wants to yield");
Thread.yield();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
while (flag) {
synchronized (lock) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore
}
System.out.println("Thread 2 wants to yield");
Thread.yield();
}
}
}
});
thread1.start();
thread2.start();
}
}
饥饿
线程的优先级过低,导致始终得不到CPU的调度执行
守护线程
线程分为守护线程和用户线程,用户线程结束,JVM就会退出
t.setDaemon(true)