文章目录
一、进程和线程
1. 进程和线程
2. 并行和并发
3. 应用
二、Java多线程
1. 创建和运行线程
1、方法一:直接使用Thread
2、方法二:使用Runnable配合使用Thread
3、Lambda表达式来简化代码:
看一下Runnable接口中的抽象方法:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
如果一个接口中有@FunctionalInterface这个注解,就说明可以使用Lambda表达式来简化代码,如果一个接口中有多个抽象方法,那样没有办法使用Lambda表达式简化。
选中Runnable按住alt+enter可以将手动创建的任务改成Lambda表达式。
4、两种创建线程方法的关系:
继承Thread方法,重写了run()方法,会直接执行子类的run()方法。
实现Runnable接口,实现了Thread类中的run()方法。
//定义一个Runnable接口对象
private Runnable target;
//如果传入了Runnable接口对象target
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//如果target!=null,就调用Runnable接口中的方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
5、方法三:使用FutureTask配合使用Thread
看一下底层源码,间接实现了Runnable接口,可以返回任务的执行结果
public class FutureTask<V> implements RunnableFuture<V> {
........
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
2. 查看进程和线程的方法
3. 线程运行的原理
4. start & run
直接调用 run 是在主线程中执行了 run,没有启动新的线程
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
@Slf4j(topic = "c.Test5")
public class Test5 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
System.out.println(t1.getState());//NEW
t1.start();
System.out.println(t1.getState());//RUNNABLE
}
}
5. sleep & yield
(1)调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞):
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
//让t1线程休眠
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
//还没等到t1休眠主线程就执行了这行代码,所以结果为Runnable
log.debug("t1 state: {}", t1.getState());//RUNNABLE
//在main方法中调用sleep()方法是让主线程休眠
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当主线程休眠0.5s后,t1线程肯定进入了休眠状态,打印结果为TIMED_WAITING
log.debug("t1 state: {}", t1.getState());//TIMED_WAITING
}
}
(2)其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException:
@Slf4j(topic = "c.Test7")
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("enter sleep...");
//让t1线程睡眠2s
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
t1.start();
//主线程睡眠1s,sleep()方法写在哪个线程中就让哪个线程睡眠
Thread.sleep(1000);
log.debug("interrupt...");
/**
* 调用线程t1的interrupt方法,将睡眠中的线程t1唤醒
* 唤醒后就会抛出InterruptedException,然后执行catch代码块
* 如果没有唤醒,过了睡眠时间也会醒,但是可以使用interrupt()方法打断
*/
t1.interrupt();
}
}
(3)睡眠结束后的线程未必会立刻得到执行,以为未必能的大CPU的使用权。
(4)建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性。
@Slf4j(topic = "c.Test8")
public class Test8 {
public static void main(String[] args) throws InterruptedException {
log.debug("enter");
//睡眠1s,可以选择时间单位
TimeUnit.SECONDS.sleep(1);
log.debug("end");
}
}
6. 线程优先级
演示yield和线程优先级:
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
for (;;) {
System.out.println("---->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for (;;) {
//得到CPU的时间片之后,让时间片让给其他线程,那么这个线程得到的时间片就少
//Thread.yield();
System.out.println(" ---->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
7. join
1、join()方法的作用:等待线程指向结束
因为主线程和线程 t1 是并发执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0:
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
log.debug("结果为:{}", r);//r=0
log.debug("结束");
}
}
用 join,加在 t1.start() 之后,输出r=10:
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
//等待调用join()方法的线程运行结束,这里会等待t1线程运行结束再往下执行
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
2、join()方法的应用之同步:
以调用方角度来讲,如果需要等待结果返回,才能继续运行就是同步,不需要等待结果返回,就能继续运行就是异步。
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
//t1线程等待1s
t1.join();
log.debug("t2 join end");
//t2线程等待1s,因为t1线程执行时,t2线程已经等待了1s
t2.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
让t1线程和t2线程的等待顺序调整一下:
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
//t2线程等待2s
t2.join();
log.debug("t2 join end");
//t1线程不用等待,早就醒了
t1.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
3、有时效的join():
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(2);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
log.debug("join begin");
//等待时间大于线程睡眠线程
t1.join(3000);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
8. interrupt
0、如何理解interrupt:
很多人认为Java设置线程中断就是表示线程停止了,不往前执行了:
Thread.currentThread().interrupt()
其实不是这样的,线程中断只是一个状态而已,true表示已中断,false表示未中断
//获取线程中断状态,如果中断了返回true,否则返回false
Thread.currentThread().isInterrupted()
该如何让线程真正停止不往前执行呢?真正让线程停止下来(阻塞),Java提供了一个较为底层的并发工具类:LockSupport,该类常用的方法有两个:
park(Object blocker) 表示阻塞指定线程,参数blocker当前线程对象
unpark(Thread thread) 唤醒指定线程,参数thread指定线程对象
1、打断sleep,wait,join的线程,这几个线程会让线程处于阻塞状态,打断他们后会清空打断状态。
设置线程中断不影响线程的继续执行,但是线程设置中断后,线程内调用了wait、jion、sleep方法中的一种, 立马抛出一个 InterruptedException,且中断标志被清除,重新设置为false。
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000); // wait, join
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
//线程中断只是一个状态而已,true表示已中断,false表示未中断,清除了打断状态
log.debug("打断标记:{}", t1.isInterrupted());//false
}
}
2、打断正常状态的线程,不会清空打断状态:
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
//interrupted为true时,退出循环
if(interrupted) {
log.debug("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
//让主线程睡眠1s
Thread.sleep(1000);
log.debug("interrupt");
//打断t1线程
t1.interrupt();
}
}
3、打断park线程,不会清空打断状态:
打断 park 线程, 不会清空打断状态,park线程会让线程处于阻塞状态,不再向下执行:
@Slf4j(topic = "c.Test14")
public class Test14 {
public static void main(String[] args) throws InterruptedException {
test3();
}
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());//true
}, "t1");
t1.start();
sleep(1);
t1.interrupt();
}
}
打断标记为true时,park就会失效,此时需要将打断标记设置为false:
@Slf4j(topic = "c.Test14")
public class Test14 {
public static void main(String[] args) throws InterruptedException {
test3();
}
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
//将打断标记设置为false,让park生效
log.debug("打断状态:{}", Thread.interrupted());
LockSupport.park();
}, "t1");
t1.start();
sleep(1);
t1.interrupt();
}
}
9. 不推荐的方法
下面的方法不会释放锁,造成线程死锁,所以不推荐使用:
10. 主线程和守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
log.debug("结束");
}, "t1");
//只要主线程的代码执行结束了,即使守护线程中的代码没有执行结束,java进程也结束
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
}
11. 线程的5种状态
可运行状态即就绪状态,不能直接进入阻塞状态,只用运行状态才会进入阻塞状态: