提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:以下是本篇文章正文内容,下面案例可供参考

程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行
一个进程之内可以分为一到多个线程。

二者对比

每个核(core)都可以调度运行线程,这时候线程可以是并行的。


共有四种方式可以创建线程,分别是:
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("MyThread run ...");
}
public static void main(String[] args) {
// 创建MyThread对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 调用start方法启动线程
t1.start();
t2.start();
}
}

public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyThread run ...");
}
public static void main(String[] args) {
// 创建MyRunnable对象
MyRunnable mr = new MyRunnable();
// 创建Thread对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
// 调用start方法启动线程
t1.start();
t2.start();
}
}

public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "ok";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建MyCallable对象
MyCallable mc = new MyCallable();
// 创建FutureTask
FutureTask<String> ft = new FutureTask<String>(mc);
// 创建Thread对象
Thread t1 = new Thread(ft);
Thread t2 = new Thread(ft);
// 调用start方法启动线程
t1.start();
// 调用ft的get方法获取执行结果
String res = ft.get();
// 输出
System.out.println(res);
}
}

public class MyExecutors implements Runnable{
@Override
public void run() {
System.out.println("MyThread run ...");
}
public static void main(String[] args) {
// 创建固定大小的线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(3);
// 提交任务,执行run
threadPool.submit(new MyExecutors());
// 关闭线程池
threadPool.shutdown();
}
}

start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run():封装了要被线程执行的代码,可以被调用多次。

线程的状态可以参考JDK中的Thread类中的枚举State

在idea中打开Thread类找到State


如果没有获取锁(synchronized或lock)进入阻塞状态,获得锁再切换为可执行状态
如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态

阻塞调用此方法的线程进入timed_waiting直到线程t执行完成后,此线程再继续执行
public class ThreadDemo01 {
public static void main(String[] args) {
// 创建线程:Lambda表达式定义了线程的执行内容
Thread t1 = new Thread(()->{
System.out.println("t1");
});
Thread t2 = new Thread(()->{
try {
t1.join(); // 加入线程t1,只有t1线程执行完毕之后,再次执行该线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t2");
});
Thread t3 = new Thread(()->{
try {
t2.join(); // 加入线程t1,只有t1线程执行完毕之后,再次执行该线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t3");
});
// 启动线程
t3.start();
t2.start();
t1.start();
}
}
运行结果:

测试不加join的结果

演示Java中多线程编程中的等待和唤醒机制,代码如下:
public class NotifyThread {
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建t1线程对象,并通过构造函数的参数传递了一个Runnable对象(Lambda表达式),还给线程指定了一个名称 "t1"
Thread t1 = new Thread(() -> {
synchronized (lock) { // 加锁同步块,使用 lock 对象作为锁。只有一个线程可以进入这个同步块,其他线程必须等待直到锁被释放。
System.out.println(Thread.currentThread().getName() + "...线程等待...");
try {
lock.wait(); // 等待操作,它让当前线程进入等待状态,并释放 lock 锁。线程将一直等待,直到另一个线程调用相同 lock 对象的 notify() 或 notifyAll() 方法,唤醒它。
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "...线程被唤醒了...");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "...线程等待...");
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "...线程被唤醒了...");
}
}, "t2");
t1.start();
t2.start();
Thread.sleep(2000);
synchronized (lock) {
lock.notify();
}
}
}
notify() 运行结果:

notifyAll() 运行结果:

共同点:
不同点:
wait方法 的调用必须先获取 wait对象的锁,配合synchronized使用,运行情况如下:

如果去掉了synchronized就会报错,运行如下:
