1、进程和线程
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>JUC</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
- logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--<configuration xmlns="http://ch.qos.logback/xml/ns/logback"-->
<!-- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"-->
<!-- xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">-->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{HH:mm:ss.SSS} [%t] %logger - %m%n</pattern>
</encoder>
</appender>
<logger name="c" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
1.1 程序----Program
1.2 进程----Process
- 进程的特点:
1.2.1.1 串行----Sequential
- 先做任务A,完成后再做任务B,完成后再做任务C,所有任务逐个完成。
- 共耗时:15+10+10 = 35分钟
1.2.1.2 并行----Parallel
-
三个任务同时开始,总耗时取决于需要时间最长的那个任务
-
总耗时:10 分钟
1.2.1.3 并发----concurrency
- 先做任务A,准备了5分钟后,在等待A完成的这段时间内就开始做任务B,任务B准备了2分钟,在等待B完成的过程中开始做任务C结束
- 共耗时:5 + 2 + 10 = 17 分钟
1.2.1.4 同步
- 需要等待结果返回,才能继续运行就是同步
- 同步在多线程中就是让多个线程步调一致
1.2.1.5 异步
- 不需要等待结果返回就能继续运行就是异步
1.3 线程----Thread
- 主线程与子线程
- 线程特点:
1.4 进程和线程的区别
2、线程的定义和创建
2.1 继承 Thread 类
- Java 应用程序的main函数是一个线程,是被JVM启动的时候调用,线程的名字叫main
- 实现一个线程,必须创建Thread实例,override run(),并且调用start()
- 在JVM启动后,实际上有多个线程,但是至少有一个非守护线程
- 当你调用一个线程start方法的时候,此时至少有两个线程,一个是调用你的线程,还有一个执行run方法的线程
- 线程的生命周期分为new,runnable,running,block,termate
- 调用线程的 start() 方法来启动线程,启动线程的实质是请求JVM运行相应的线程,这个线程具体在什么时候运行有线程调度器(Scheduler)决定。
- 注意:
1)start() 调用结束并不意味着子线程开始运行;
2)新开启的线程会执行run();
3)开启多个线程,start()调用的顺序并不一定就是线程启动的顺序;
4)多线程运行结果与代码执行顺序或调用顺序无关
使用多线程实现龟兔赛跑
/**
* 定义一个乌龟线程类
*/
public class TortoiseThread extends Thread{
/**
* 线程体:线程要执行的任务
*/
@Override
public void run() {
while(true) {
System.out.println("乌龟领先了,加油!" + this.getName() + " " + this.getPriority());
}
}
}
/**
* 创建一个线程对象,启动线程对象
*/
public class TestThread {
public static void main(String[] args) {
//启动乌龟线程
Thread thread = new TortoiseThread();
thread.setName("乌龟1线程");
//thread.run(); //普通的方法调用
thread.start(); //启动一个新的线程
Thread thread2 = new TortoiseThread();
thread2.setName("乌龟2线程");
thread2.start();
//兔子也在跑
Thread.currentThread().setName("兔子线程");
while(true) {
System.out.println("兔子领先了,加油!" + " " + Thread.currentThread().getName() + " " + Thread.currentThread().getPriority());
}
}
}
public class TryConcurrency {
public static void main(String[] args) {
/**
* 局部内部类
*
* 1、会产生两个线程同时并发运行,
* 1)启动start():main 线程;
* 2)执行run():当前线程。
* 故一个线程不能启动多次
* 2、
*/
new Thread("Read-Thread") {
@Override
public void run() {
println(Thread.currentThread().getName());
readFromDataBase();
}
}.start();
new Thread("Write-Thread") {
@Override
public void run() {
writeDataToFile();
}
}.start();
/*//内部类
// 线程执行的时候,会存在线程安全问题,CPU切换执行权
Thread t1 = new Thread("Custom-Thread") {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
println("Task 1==>" + i);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//启动线程
t1.start();
// main 线程
for (int j = 0; j < 1000; j++) {
println("Task 2==>" + j);
}*/
/*try {
Thread.sleep(1000 * 300L); // 阻塞方法
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//事情是一件一件处理,不能同时处理
/*readFromDataBase();
writeDataToFile();*/
}
//数据库中读取数据并处理
private static void readFromDataBase() {
try {
println("Begin read data from db.");
Thread.sleep(1000 * 10L);
println("Read data done and start handle it.");
} catch (InterruptedException e) {
e.printStackTrace();
}
println("The data handle finish and successfully.");
}
//写数据到文件
private static void writeDataToFile() {
try {
println("Begin write data to file.");
Thread.sleep(2000 * 10L);
println("Write data done and start handle it.");
} catch (InterruptedException e) {
e.printStackTrace();
}
println("The data handle finish and successfully.");
}
private static void println(String message) {
System.out.println(message);
}
}
/**
* 模板方法
*/
public class TemplateMethod {
// 模板方法,print() 必须定义成final的,子类就不可以重写了
public final void print(String message) {
System.out.println("###################");
wrapPrint(message);
System.out.println("###################");
}
protected void wrapPrint(String message) {
}
public static void main(String[] args) {
TemplateMethod t1 = new TemplateMethod() {
@Override
protected void wrapPrint(String message) {
System.out.println("*" + message + "*");
}
};
t1.print("Hello Thread");
TemplateMethod t2 = new TemplateMethod() {
@Override
protected void wrapPrint(String message) {
System.out.println("+" + message + "+");
}
};
t2.print("Hello Thread");
}
}
2.2 实现 Runnable 接口
- runnable:是一个函数式接口
- 当线程类已经有父类了,就不能用继承Thread类的形式创建线程,可以使用实现Runnable接口的形式
2.2.1 lambda 简化
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
// 创建线程的 lambda 简化写法
Runnable r = ()->log.debug("running");
Thread t = new Thread(r,"t2");
t.start();
}
}
2.2.2 使用多线程实现龟兔赛跑
// 方式一:
public class TortoiseRunnable implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("乌龟领先了,加油!" + " " + Thread.currentThread().getName() + " " + Thread.currentThread().getPriority());
}
}
}
public class TestThread {
public static void main(String[] args) {
//两个乌龟来跑
Runnable runnable = new TortoiseRunnable();
Thread thread1 = new Thread(runnable);
thread1.setName("乌龟1线程");
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
Thread thread2 = new Thread(runnable,"乌龟2线程");
thread2.start();
//兔子也在跑
while(true) {
System.out.println("兔子领先了,加油!" + " " + Thread.currentThread().getName() + " " + Thread.currentThread().getPriority());
}
}
}
// 方式二:
public class TestThread {
public static void main(String[] args) {
// 使用匿名内部类型创建乌龟线程
Runnable runnable = new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("乌龟领先了,加油!" + " " + Thread.currentThread().getName() + " " + Thread.currentThread().getPriority());
}
}
};
//两个乌龟来跑
Thread thread1 = new Thread(runnable);
thread1.setName("乌龟1线程");
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
Thread thread2 = new Thread(runnable,"乌龟2线程");
thread2.start();
//兔子也在跑
while(true) {
System.out.println("兔子领先了,加油!" + " " + Thread.currentThread().getName() + " " + Thread.currentThread().getPriority());
}
}
}
- 继承 Thread 类 和 实现 Runnable 接口的比较
2.3 实现Callable接口
- 与实现Runnable相比,Callable功能更强大些
- 方法名不同
- 可以有返回值,支持泛型的返回值
- 可以抛出检查异常
- 需要借助FutureTask,获取返回结果
import java.util.Random;
import java.util.concurrent.*;
public class RandomCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(6000);
return new Random().nextInt(10);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个线程对象
Callable<Integer> callable = new RandomCallable();
FutureTask<Integer> task = new FutureTask(callable);
Thread thread = new Thread(task);
//启动线程
thread.start();
//获取返回值
System.out.println(task.isDone()); // false
//int result = task.get();
int result = 0;
try {
result = task.get(3, TimeUnit.SECONDS); //超过3s,没有获得结果,就会报异常,但任务还未结束,isDone为false
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println(task.isDone()); // true
System.out.println(result);
}
}
2.4 Thread与Runnable的关系
2.5 多线程运行的现象
- 交替运行
- 谁先谁后,不由我们控制,由谁先抢占到CPU资源,谁先执行
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.TestMultiThread")
public class TestMultiThread {
public static void main(String[] args) {
new Thread(()->{
while (true) {
log.debug("running");
}
},"t1").start();
new Thread(()->{
while (true) {
log.debug("running");
}
},"t2").start();
}
}
2.6 查看进程线程的方法
2.6.1 windows
- 查看具体资源的进程:tasklist | findstr xxx
2.6.2 linux
2.6.3 java
2.7 原理值线程运行
2.7.1 栈与栈帧
- 多线程的栈帧是独立运行的,互不干扰。
2.7.2 栈帧图解
public class TestFrames {
public static void main(String[] args) {
method1(10);
}
public static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
2.7.3 线程上下文切换(Thread Context Switch)
2.8 常见方法
2.8.1 start() 与 run()
- 调用run()
- 调用start()
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
}
};
System.out.println(t1.getState());
t1.start();
// t1.start(); // start() 不能重复调用,否则报java.lang.IllegalThreadStateException
log.debug("do other things ...");
System.out.println(t1.getState());
}
}
输出
NEW
14:01:37.345 [main] c.Test - do other things ...
RUNNABLE
14:01:37.345 [t1] c.Test - t1
2.8.2 sleep() 与 yield()
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("t1 state: {}",t1.getState());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state: {}",t1.getState());
}
}
输出
14:11:19.210 [main] c.Test - t1 state: RUNNABLE
14:11:22.215 [main] c.Test - t1 state: TERMINATED