💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 一、概述 世间万物都可以同时完成很多工作。例如,人体可以同时进行呼吸、血液循环、思考问题等活动。用户既可以使用计算机听歌,也可以编写文档和发送邮件,而这些活动的完成可以同时进行。这种同时执行多个操作的“思想”在 Java 中被称为并发,而将并发完成的每一件事称为线程。 在 Java 中,并发机制非常重要,但并不是所有程序语言都支持线程。在以往的程序中,多以一个任务完成以后再进行下一个任务的模式进行,这样下一个任务的开始必须等待前一个任务的结束。Java 语言提供了并发机制,允许开发人员在程序中执行多个线程,每个线程完成一个功能,并与其他线程并发执行。这种机制被称为多线程。 > Windows 系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。 > 系统可以分配给每个进程一段有限的执行 CPU 的时间(也称为 CPU 时间片),CPU 在这段时间中执行某个进程,然后下一个时间段又跳到另一个进程中去执行。由于 CPU 切换的速度非常快,给使用者的感受就是这些任务似乎在同时运行,所以使用多线程技术后,可以在同一时间内运行更多不同种类的任务。 线程可以理解成是在进程中独立运行的子任务。比如,QQ.exe 运行时就有很多的子任务在同时运行。像好友视频、下载文件、传输数据、发送表情等,这些不同的任务或者说功能都可以同时运行,其中每一项任务完全可以理解成是“线程”在工作,传文件、听音乐、发送图片表情等功能都有对应的线程在后台默默地运行。 ## 二、线程的创建 ### 2.1 通过 Thread 类创建线程 ``` public class MyThread extends Thread { public void run() { System.out.println(getName() + " 线程正在运行"); } } ``` 当一个类继承 Thread 类后,就可以在该类中覆盖 run() 方法,将实现线程功能的代码写入 run() 方法中,然后调用 Thread 类的 start() 方法启动线程。 ``` public class ThreadTest { public static void main(String[] args) { System.out.println("主线程开始运行"); Thread thread = new MyThread(); thread.start(); // thread.run(); System.out.println("主线程运行结束"); } } ``` > 如果 start() 方法调用一个已经启动的线程,系统将会抛出 IllegalThreadStateException 异常。 ``` 主线程开始运行 主线程运行结束 Thread-0 线程正在运行 ``` MyThread 类中的 start() 方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的 run() 方法。这个过程其实就是让系统安排一个时间来调用 Thread 中的 run() 方法,也就是使线程得到运行,启动线程,具有异步执行的效果。 ``` 主线程开始运行 Thread-0 线程正在运行 主线程运行结束 ``` 如果调用代码 thread.run() 就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由 main 主线程来调用 run() 方法,也就是必须等 run() 方法中的代码执行完后才可以执行后面的代码。 ``` public class MyThread extends Thread { public MyThread() {} public MyThread(String name) { super(name); } public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + " 线程运行第" + i + "次"); } } } public class ThreadTest { public static void main(String[] args) { System.out.println("主线程开始运行"); MyThread mt = new MyThread("myThread"); t.start(); for (int i = 0; i < 100; i++) { System.out.println("主线程运行第" + i + "次"); } System.out.println("主线程运行结束"); } } ``` 从上面的运行结果来看,MyThread 类中 run() 方法执行的时间要比主线程晚。这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。同时也验证了线程是一个子任务,CPU 以不确定的方式,或者说以随机的时间来调用线程中的 run() 方法。 ``` public class MyThread extends Thread { private int i; public MyThread3(int i) { super(); this.i = i; } public void run() { System.out.println("当前数字:" + i); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { MyThread m = new MyThread(i); m.start(); } } } ``` 从运行结果中可以看到,虽然调用时数字是有序的,但是由于线程执行的随机性,导致输出的数字是无序的,而且每次顺序都不一样。除了异步调用之外,同步执行线程 start() 方法的顺序不代表线程启动的顺序。 ***** 【选择】下列选项中,哪两句的说法是错误的()(选择两项) ``` A 线程是比进程还要小的运行单位 B Thread 类位于 java.thread 包下 C run() 方法用于启动线程 D CPU 使用时间片轮转的工作方法,可以让多个程序轮流占用 CPU,达到同时运行的效果 ``` 【阅读】以下代码中,在(1)处加入哪条语句能成功启动线程() ``` public class ThreadOne extends Thread { public void run() { System.out.println("执行 ThreadOne 进程"); } } public class ThreadTest { public static void main(String[] args) { ThreadOne one = new ThreadOne(); // (1) } } ``` 【选择】通过 Thread 类创建线程时要()(选择三项) ``` A 继承 Thread 类 B 在子类中重写 run() 方法 C 实现 Runnable 接口 D 调用 start() 方法启动线程 ``` 【编程】通过继承 Thread 类的方式创建线程,并在线程体中通过循环打印输出如演示所示的内容。 ``` 打印机正在打印1 打印机正在打印2 打印机正在打印3 打印机正在打印4 打印机正在打印5 打印机正在打印6 打印机正在打印7 打印机正在打印8 打印机正在打印9 打印机正在打印10 ``` ### 2.2 实现 Runnable 接口创建线程 如果要创建的线程类已经有一个父类,这时就不能再继承 Thread 类,因为 Java 不支持多继承,所以可以通过实现 Runnable 接口来应对这样的情况。 > 从 JDK 的 API 中可以发现,实质上 Thread 类实现了 Runnable 接口,其中的 run() 方法正是对 Runnable 接口中 run() 方法的具体实现。 实现 Runnable 接口的程序会创建一个 Thread 对象,并将 Runnable 对象与 Thread 对象相关联。Thread 类有如下两个与 Runnable 有关的构造方法: ``` 1. public Thread(Runnable r) 2. public Thread(Runnable r, String name) ``` 使用 Runnable 接口启动线程的基本步骤如下: ``` 1. 创建一个 Runnable 对象。 2. 使用参数带 Runnable 对象的构造方法创建 Thread 实例。 3. 调用 start() 方法启动线程。 ``` :-: ![](http://cndpic.dodoke.com/388b7c911f81a97a6a99cf510e1a08a7) 【例题】案例演示如何实现 Runnable 接口,以及如何启动线程。 ``` public class MyRunnable implements Runnable { public void run() { System.out.println(Thread.currentThread().getName() + "运行中!"); } public static void main(String[] args) { Runnable rn = new MyRunnable(); Thread th1 = new Thread(rn); th1.start(); Thread th2 = new Thread(rn); th2.start(); } } ``` ``` public class MyRunnable implements Runnable { int i = 0; // 此时多个线程共用一个成员变量 public void run() { while(i < 10) { System.out.println(Thread.currentThread().getName() + "运行第" + (i++) + "次"); } } public static void main(String[] args) { Runnable rn = new MyRunnable(); Thread th1 = new Thread(rn); th1.start(); Thread th2 = new Thread(rn); th2.start(); } } ``` ***** 【选择】用 Runnable 接口创建线程的主要工作如下,它们正确的先后顺序为()(选择一项) ``` 1) 通过实现类的对象创建线程类的对象 2) 声明实现 Runnable 接口的类 3) 调用 start() 方法启动线程 4) 创建实现类的对象 5) 在实现类内实现 run() 方法 ``` ``` A 1-4-2-5-3 B 2-1-4-5-3 C 2-5-4-1-3 D 1-5-2-4-3 ``` 【编程】编写代码完成以下任务: 1. 通过实现 Runnable 接口的方式创建线程类 Cat 和 Dog,run() 方法实现的功能为:加入一个循环长度为3的循环,分别循环输出信息`A cat`和`A dog`。 2. 在测试类中分别创建 Cat 和 Dog 类的对象,启动两个线程。 3. 在测试类中创建一个循环长度为3的for循环,打印输出信息`main thread`。 ``` main thread main thread main thread Thread-1A dog Thread-1A dog Thread-1A dog Thread-0A cat Thread-0A cat Thread-0A cat ``` ## 三、线程的生命周期 ### 3.1 线程的状态和生命周期 线程具有生命周期,主要包括 7 种状态,分别是出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。 1. 出生状态:用户在创建线程时所处的状态,在用户使用该线程实例调用`start()`方法之前,线程都处于出生状态。 2. 就绪状态:也称可执行状态,当用户调用`start()`方法之后,线程处于就绪状态。 3. 运行状态:当线程得到系统资源后进入运行状态。 4. 等待状态:当处于运行状态下的线程调用 Thread 类的`wait()`方法时,该线程就会进入等待状态。进入等待状态的线程必须调用 Thread 类的`notify()`方法才能被唤醒。`notifyAll()`方法是将所有处于等待状态下的线程唤醒。 5. 休眠状态:当线程调用 Thread 类中的`sleep()`方法时,则会进入休眠状态。 6. 阻塞状态:如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时,线程进入就绪状态。对阻塞的线程来说,即使系统资源关闭,线程依然不能回到运行状态。 7. 死亡状态:当线程的`run()`方法执行完毕,线程进入死亡状态。 > 提示:一旦线程进入可执行状态,它会在就绪状态与运行状态下辗转,同时也可能进入等待状态、休眠状态、阻塞状态或死亡状态。 ***** 【选择】关于线程的状态和生命周期的说法,正确的是()(选择两项) ``` A 只有获取到 CPU 的使用权,线程才能从可运行状态转为运行状态 B 调用 start() 方法可以使线程处于可运行状态 C 如果正在运行的线程异常终止,则线程会处于阻塞状态 D 一个正在运行的线程,调用 join() 方法,则会处于终止状态 ``` ### 3.2 sleep 方法的使用 ``` public static void sleep(long millis) { ... } ``` * 指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。 * 这个正在执行的线程是指 Thread.currentThread() 返回的线程。 * 参数为休眠的时间,单位是毫秒。 ``` public class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "运行第" + i + "次"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class SleepTest { public static void main(String[] args) { Runnable r = new MyRunnable(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } } ``` ***** 【选择】以下说法错误的是()(选择一项) ``` A sleep() 方法的参数是以毫秒为单位的 B 使用实现 Runnable 接口的方式创建线程,一定要重写 run 方法 C 当创建线程对象,线程即进入创建状态 D 调用 sleep 方法时,不需要处理异常 ``` 【阅读】关于下列代码,说法正确的是() ``` public class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 3; i++) { System.out.println("正在运行" + i); Thread.sleep(500); } } } public class Test { public static void main(String[] args) { Runnable m = new MyRunnable(); Thread th = new Thread(m); th.start(); } } ``` 【编程】利用线程输出`a~z`的26个字母(横向输出),要求每隔一秒钟输出一个字母。 ``` abcdefghijklmnopqrstuvwxyz ``` ### 3.3 join 方法的使用 ``` 1. public final void join() 等待调用该方法的线程结束后才能执行。 2. public final void join(long millis) 等待该线程终止的最长时间为 millis 毫秒。 ``` ``` public class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "正在执行第" + i + "次"); } } } public class JoinTest { public static void main(String[] args) { Runnable r = new MyRunnable(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); try { t1.join(); } catch(InterruptedException e) { e.printStackTrace(); } t2.start(); } } ``` ***** 【选择】下列关于 Thread 类中的 join() 方法说法错误的是()(选择两项) ``` A 调用 join() 方法可以使其他线程由正在运行状态变成阻塞状态 B join() 方法可以通过 Thread 类名直接访问 C 子类中可以重写 join() 方法 D join() 方法的作用是等待调用该方法的线程结束后才能执行 ``` ### 3.4 线程的优先级 > Java 为线程类提供了10个优先级,优先级可以用整数1~10表示,超过范围会抛出异常。主线程优先级的默认值为 5。 * 优先级常量 ``` MAX_PRIORITY:线程的最高优先级 10 MIN_PRIORITY:线程的最低优先级 1 NORM_PRIORITY:线程的默认优先级 5 ``` * 使用 Thread 类中的 setPriority() 方法来设置线程的优先级。语法如下: ``` public final void setPriority(int newPriority); ``` * 使用 Thread 类中的 getPriority() 方法来获取线程的优先级。语法如下: ``` public final int getPriority(); ``` ``` public class MyRunnable implements Runnable { public void run() { System.out.println("优先级为" + Thread.currentThread().getPriority()); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "正在运行" + i + "次"); } } public static void main(String[] args) { int mainPriority = Thread.currentThread().getPriority(); // 获取主线程的优先级 System.out.println("主线程的优先级为" + mainPriority); Runnable r = new MyRunnable(); Thread t1 = new Thread(r, "线程1"); Thread t2 = new Thread(r, "线程2"); t1.setPriority(10); t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); } } ``` ***** 【选择】下列说法正确的是()(选择一项) ``` A 设置优先级的方法为 public int setPriority(int n) B 优先级可用1-10的整数表示 C 获取优先级的方法是 public void getPriorty() D 在 Java 中,优先级高的线程一定会比优先级低的线程先运行 ``` ## 四、线程同步 * 各个线程是通过竞争 CPU 时间而获得运行机会的 * 各个线程什么时候得到 CPU 时间,占用多久,是不可预测的 * 一个正在运行着的线程在什么地方被暂停是不确定的 ``` public class Bank { private int balance; public Bank(int balance) { this.balance = balance; } // getter setter ... @Override public String toString() { return "Bank{balance=" + balance + "}"; } // 存款 public void saveBalance(int value) { // 获取当前的账号余额 int balance = getBalance(); try { Thread.sleep(1000); } catch(InterruptedException e) { e.printStackTrace(); } setBalance(balance + value); // 输出存款后的账户余额 System.out.println("存款后的账户余额为" + getBalance()); } // 取款 public void drawBalance(int value) { int balance = getBalance(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } setBalance(balance - value); System.out.println("取款后的账户余额为" + getBalance()); } } public class SaveBalance implements Runnable { Bank bank; public SaveBalance(Bank bank) { this.bank = bank; } public void run() { bank.saveBalance(100); } } public class DrawBalance implements Runnable { Bank bank; public DrawBalance(Bank bank) { this.bank = bank; } public void run() { bank.drawBalance(200); } } public class BankTest { public static void main(String[] args) { Bank bank = new Bank(1000); Runnable sb = new SaveBalance(bank); Runnable db = new DrawBalance(bank); Thread save = new Thread(sb); Thread draw = new Thread(db); save.start(); draw.start(); try { save.join(); draw.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(bank); } } ``` 为了处理共享资源竞争,可以使用同步机制。所谓同步机制,指的是两个线程同时作用在一个对象上,应该保持对象数据的统一性和整体性。[](http://c.biancheng.net/java/)Java 提供 synchronized 关键字,为防止资源冲突提供了内置支持。 * 在一个类中,用 synchronized 关键字声明的方法为同步方法。格式如下: ~~~ public [static] synchronized 类型名称 方法名称() { ... } ~~~ * synchronized 也可以作用于代码块。对于同步块,synchronized 获取的是参数中的对象锁。格式如下: ``` synchronized(obj) { ... } ``` Java 有一个专门负责管理线程对象中同步方法访问的工具——同步模型监视器,它的原理是为每个具有同步代码的对象准备唯一的一把“锁”。当多个线程访问对象时,只有取得锁的线程才能进入同步方法,其他访问共享对象的线程停留在对象中等待。 ``` public synchronized void saveBalance(int value) { ... } public void drawBalance(int value) { synchronized(this) { ... } } ``` ***** 【选择】运行下列代码,结果是()(选择两项) ``` // Barber(理发师) 类 public class Barber { public void wash() { synchronized(this) { System.out.println("打湿头发"); System.out.println("洗头水"); System.out.println("冲洗"); } } public synchronized void cut() { System.out.println("剪短"); System.out.println("烫发"); } } public class Cut extends Thread { Barber b; public Cut(Barber b) { this.b = b; } public void run() { b.cut(); } } public class Wash extends Thread { Barber b; public Wash(Barber b) { this.b = b; } public void run() { b.wash(); } } public class Test { public static void main(String[] args) { Barber b = new Barber(); Cut cut = new Cut(b); Wash wash = new Wash(b); cut.start(); wash.start(); } } ``` ``` A 剪短 打湿头发 烫头 洗头水 冲洗 B 剪短 烫发 打湿头发 洗头水 冲洗 C 打湿头发 洗头水 冲洗 剪短 烫发 D 剪短 打湿头发 洗头水 冲洗 烫发 ``` 【选择】下列对关键字 synchronized 说法不正确的是()(选择一项) ``` A synchronized(同步),即协调不同线程之间的工作 B synchronized 关键字可以用在成员方法中 C 保证多个线程可以同时执行和结束 D 保证共享对象在同一时刻只能被一个线程访问 ``` ## 五、线程间通信 ``` wait(): 中断方法的执行,使线程等待 notify(): 唤醒处于等待的某一个线程,使其结束等待 notifyAll(): 唤醒所有处于等待的线程,使他们结束等待 ``` ``` public class Count { private int n; boolean flag = false; public synchronized int getN() throws InterruptedException{ if (!flag) wait(); System.out.println("消费:" + n); flag = false; notifyAll(); return n; } public synchronized void setN(int n) throws InterruptedException { if (flag) wait(); System.out.println("生产:" + n); this.n = n; flag = true; notifyAll(); } } public class Producer implements Runnable { private Count count; public Producer(Count count) { this.count = count; } public void run() { int i = 0; while (true) { try { count.setN(i++); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Consumer implements Runnable { private Count count; public Consumer(Count count) { this.count = count; } public void run() { while (true) { try { count.getN(); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class CountTest { public static void main(String[] args) { Count count = new Count(); new Thread(new Producer(count)).start(); new Thread(new Consumer(count)).start(); } } ``` ***** 【选择】以下说法错误的是()(选择一项) ``` A wait() 方法用于使线程等待 B notify() 方法用于唤醒一个线程 C notifyAll() 方法用于唤醒多个线程 D 使用 wait() 方法阻塞的线程,可以不用唤醒 ```