API文档 => java.lang => Thread,Runnable

线程的概述

创建线程的方式

  1. 创建一个Thread类,或者一个Thread子类的对象
  2. 创建一个实现Runnable接口的类的对象

Thread类
是一个线程类,位于java.lang包下
image.png

image.png

Runnable接口

  • 只有一个方法run()
  • Runnable是Java中用以实现线程的接口
  • 任何实现线程的类都必须实现该接口

线程的创建

通过Thread类创建线程

案例1:简单的案例

  • 这里有两个线程,主线程和副线程
  • 副线程运行的时间是随机的,因为它什么时候获得CPU使用权不一定
  • 线程不能多次启动,也就是说不能多次调用start方法 ```cpp package com.thread;

class MyThread extends Thread{ public void run(){ System.out.println(getName()+”该线程正在执行!”); } }

public class ThreadTest { public static void main(String[] args){ System.out.println(“主线程1”); MyThread myThread = new MyThread(); myThread.start(); System.out.println(“主线程2”); } }

  1. 主线程1<br />主线程2<br />Thread-0该线程正在执行!
  2. 案例2:复杂的案例<br />可以明显看出,线程运行的随机性
  3. ```cpp
  4. package com.thread;
  5. class MyThread extends Thread{
  6. public MyThread(String name){
  7. super(name);
  8. }
  9. public void run(){
  10. for (int i=0;i<10;i++){
  11. System.out.println(getName()+"正在执行"+i);
  12. }
  13. }
  14. }
  15. public class ThreadTest {
  16. public static void main(String[] args){
  17. MyThread myThread1 = new MyThread("线程1");
  18. MyThread myThread2 = new MyThread("线程2");
  19. myThread1.start();
  20. myThread2.start();
  21. }
  22. }

执行结果可能是这样:
主线程1
线程1正在执行0
线程1正在执行1
线程1正在执行2
线程1正在执行3
线程1正在执行4
主线程2
线程2正在执行0
线程2正在执行1
线程2正在执行2
线程2正在执行3
线程2正在执行4

执行结果也可能是这样:
主线程1
线程1正在执行0
线程1正在执行1
线程1正在执行2
线程1正在执行3
线程1正在执行4
线程2正在执行0
线程2正在执行1
线程2正在执行2
线程2正在执行3
线程2正在执行4
主线程2

实现Runnable接口创建线程

已经有通过Thread类创建线程的方式,为什么要还需要此方式呢?

  • Java不支持多继承

如果一个类已经继承了某个父类,那么就无法通过继承Thread类的方式创建线程

  • 不打算重写Thread类的其他方法

一般来说,重写run方法就够我们用了,不打算重写其他方法的话,还是实现Runnable接口的方式比较方便

案例1:简单的例子

  1. package com.runnable;
  2. class MyRunnable implements Runnable{
  3. @Override
  4. public void run() {
  5. System.out.println(Thread.currentThread().getName() + "正在运行!");
  6. }
  7. }
  8. public class RunnableTest {
  9. public static void main(String[] args){
  10. // 1. 定义Runnable类的对象
  11. MyRunnable myRunnable = new MyRunnable();
  12. // 2. 使用Runnable类的对象,创建线程类的对象
  13. Thread thread = new Thread(myRunnable,"主线程");
  14. // 3. 启动线程
  15. thread.start();
  16. }
  17. }

运行结果:主线程正在运行!

案例2-1:复杂的例子
两个线程都运行5次

  1. package com.runnable;
  2. class MyRunnable implements Runnable{
  3. @Override
  4. public void run() {
  5. int i=0;
  6. while (i<5){
  7. System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
  8. }
  9. }
  10. }
  11. public class RunnableTest {
  12. public static void main(String[] args){
  13. MyRunnable myRunnable1 = new MyRunnable();
  14. MyRunnable myRunnable2 = new MyRunnable();
  15. Thread thread1 = new Thread(myRunnable1);
  16. thread1.start();
  17. Thread thread2 = new Thread(myRunnable2);
  18. thread2.start();
  19. }
  20. }

Thread-0正在运行0
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
Thread-1正在运行1
Thread-1正在运行2
Thread-1正在运行3
Thread-1正在运行4

案例2-2:复杂的例子
运行结果与案例2-1是一致的,两个线程都运行5次

  1. package com.runnable;
  2. class MyRunnable implements Runnable{
  3. @Override
  4. public void run() {
  5. int i=0;
  6. while (i<5){
  7. System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
  8. }
  9. }
  10. }
  11. public class RunnableTest {
  12. public static void main(String[] args){
  13. MyRunnable myRunnable = new MyRunnable();
  14. Thread thread1 = new Thread(myRunnable);
  15. thread1.start();
  16. Thread thread2 = new Thread(myRunnable);
  17. thread2.start();
  18. }
  19. }

Thread-0正在运行0
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
Thread-1正在运行1
Thread-1正在运行2
Thread-1正在运行3
Thread-1正在运行4

案例2-3:复杂的例子
运行结果与案例2-1,2-2是一致的,两个线程都运行5次

  1. package com.runnable;
  2. class MyRunnable implements Runnable{
  3. int i = 0;
  4. @Override
  5. public void run() {
  6. while (i<5){
  7. System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
  8. }
  9. }
  10. }
  11. public class RunnableTest {
  12. public static void main(String[] args){
  13. MyRunnable myRunnable1 = new MyRunnable();
  14. MyRunnable myRunnable2 = new MyRunnable();
  15. Thread thread1 = new Thread(myRunnable1);
  16. thread1.start();
  17. Thread thread2 = new Thread(myRunnable2);
  18. thread2.start();
  19. }
  20. }

Thread-0正在运行0
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
Thread-1正在运行1
Thread-1正在运行2
Thread-1正在运行3
Thread-1正在运行4

案例2-4:复杂的例子
运行结果与案例2-1,2-2,2-3不一致,两个线程加起来运行5次
线程thread1和线程thread2共享同一个资源myRunnable,所以加起来运行5次之后就结束了。
也就是说,多个线程可以处理同一个资源

  1. package com.runnable;
  2. class MyRunnable implements Runnable{
  3. int i = 0;
  4. @Override
  5. public void run() {
  6. while (i<5){
  7. System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
  8. }
  9. }
  10. }
  11. public class RunnableTest {
  12. public static void main(String[] args){
  13. MyRunnable myRunnable = new MyRunnable();
  14. Thread thread1 = new Thread(myRunnable);
  15. thread1.start();
  16. Thread thread2 = new Thread(myRunnable);
  17. thread2.start();
  18. }
  19. }

Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0

线程的状态和生命周期

获取cpu使用权的时间是不一定的
#Java已经不建议使用stop方法
深度截图_选择区域_20191107221743.png

sleep方法的使用

  • Thread类的方法 pubic static void sleep(long millis)
  • 作用是在指定毫秒数内让正在执行的线程休眠(暂停执行)
  • 参数为休眠的时间,单位是毫秒
  1. package com.sleep;
  2. class MyThread implements Runnable{
  3. @Override
  4. public void run() {
  5. for (int i=1;i<10;i++){
  6. System.out.println(Thread.currentThread().getName()+"执行第"+i+"次");
  7. try{
  8. Thread.sleep(1000);
  9. }catch (InterruptedException e){
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. }
  15. public class SleepDemo {
  16. public static void main(String[] args){
  17. MyThread myThread = new MyThread();
  18. Thread thread = new Thread(myThread);
  19. thread.start();
  20. }
  21. }

大概每隔1秒输出一次
为什么说是大概每隔1秒呢?Thread.sleep(1000);的意思是暂停线程1秒,暂停之后线程状态会切换到可运行(Runnable)状态,而在可运行状态下获取cpu使用权的时机是不一定的,也许是马上,可有可能是30ms之后,所以这里说大概每隔1秒。

join方法的使用

  • Thread类的方法 pubic final void join(long millis)
  • 作用是等待调用该方法的线程结束后主线程才能执行,也就是说,可以使主他线程由正在运行状态变成阻塞状态
  • 参数为抢占时间

如果不写参数,那么调用该方法的线程结束后执行其他线程
如果写了参数,那么该时间结束后执行其他线程

  1. package com.thread;
  2. class MyThread extends Thread{
  3. public void run(){
  4. for (int i=0;i<5;i++){
  5. System.out.println(getName()+"正在执行"+i);
  6. }
  7. }
  8. }
  9. public class ThreadTest {
  10. public static void main(String[] args){
  11. MyThread myThread = new MyThread();
  12. myThread.start();
  13. try {
  14. myThread.join();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println("主线程运行结束");
  19. }
  20. }

Thread-0正在执行0
Thread-0正在执行1
Thread-0正在执行2
Thread-0正在执行3
Thread-0正在执行4
主线程运行结束

重点:可以看到,一定是Thread-0线程结束之后,才会执行主线程的


线程的优先级

  • Java为线程类提供了10个优先级
  • 优先级可以用整数1-10表示,超过范围会抛出异常
  • 主线程默认优先级为5
  • 优先级常量

MAX_PRIORITY:线程的最高优先级10
MIN_PRIORITY:线程的最低优先级1
NORMAL_PRIORITY:线程的默认优先级5

  • 优先级相关的方法

image.png

  1. package com.thread;
  2. class MyThread extends Thread{
  3. private String name;
  4. public MyThread(String name){
  5. this.name = name;
  6. }
  7. public void run(){
  8. for (int i=0;i<5;i++){
  9. System.out.println("线程" + name + "正在执行"+i);
  10. }
  11. }
  12. }
  13. public class ThreadTest {
  14. public static void main(String[] args){
  15. // 获取主线程的优先级
  16. int mainPriority = Thread.currentThread().getPriority();
  17. System.out.println("主线程的优先级为" + mainPriority);
  18. MyThread myThread1 = new MyThread("线程1");
  19. MyThread myThread2 = new MyThread("线程2");
  20. myThread1.setPriority(Thread.MAX_PRIORITY); // 跟设置为10是一样的
  21. myThread2.setPriority(Thread.MIN_PRIORITY); // 跟设置为1是一样的
  22. myThread2.start();
  23. myThread1.start();
  24. System.out.println("线程1的优先级为:" + myThread1.getPriority());
  25. System.out.println("线程2的优先级为:" + myThread2.getPriority());
  26. }
  27. }

主线程的优先级为5
线程线程2正在执行0
线程线程2正在执行1
线程线程2正在执行2
线程线程2正在执行3
线程线程2正在执行4
线程1的优先级为:10
线程2的优先级为:1
线程线程1正在执行0
线程线程1正在执行1
线程线程1正在执行2
线程线程1正在执行3
线程线程1正在执行4

重点:虽然线程1的优先级比较高,但是并不是线程1线运行,这个CPU,操作系统都有关系。不过,虽然执行结果可能是随机的,但是大大提高了优先级高的线程优先执行的概率。

线程同步

普通线程(非同步)的特点

  • 各个线程是通过竞争CPU时间而获得运行机会的
  • 各线程什么时候得到CPU时间,占用多久,是不可预测的
  • 一个正在运行着的线程在什么地方暂停是不确定的

普通线程(非同步)因为具有以上特点,所以当两个线程操作一个共享对象的时候,可能会出现问题。
让我们通过一个银行的存取款例子,来说明这种可能发生的问题。

问题描述

Bank.java

  1. package com.process;
  2. public class Bank {
  3. private String account;
  4. private int balance;
  5. public Bank(String account, int balance) {
  6. this.account = account;
  7. this.balance = balance;
  8. }
  9. public String getAccount() {
  10. return account;
  11. }
  12. public void setAccount(String account) {
  13. this.account = account;
  14. }
  15. public int getBalance() {
  16. return balance;
  17. }
  18. public void setBalance(int balance) {
  19. this.balance = balance;
  20. }
  21. @Override
  22. public String toString(){
  23. return "账号:"+account+",余额:"+balance;
  24. }
  25. // 存款
  26. public void saveAccount(){
  27. int balance = getBalance();
  28. balance += 100;
  29. setBalance(balance);
  30. System.out.println("存款后的余额为:"+balance);
  31. }
  32. // 取款
  33. public void drawAccount(){
  34. int balance = getBalance();
  35. balance -= 200;
  36. // 通过sleep方法模拟被其他线程中断的情况
  37. try {
  38. Thread.sleep(1000);
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. setBalance(balance);
  43. System.out.println("取款后的余额为:"+balance);
  44. }
  45. }

SaveAccount.java

  1. package com.process;
  2. public class SaveAccount implements Runnable{
  3. Bank bank;
  4. public SaveAccount(Bank bank){
  5. this.bank = bank;
  6. }
  7. @Override
  8. public void run() {
  9. bank.saveAccount();
  10. }
  11. }

DrawAccount.java

  1. package com.process;
  2. public class DrawAccount implements Runnable {
  3. Bank bank;
  4. public DrawAccount(Bank bank) {
  5. this.bank = bank;
  6. }
  7. @Override
  8. public void run() {
  9. bank.drawAccount();
  10. }
  11. }

Test.java

  1. package com.process;
  2. public class Test {
  3. public static void main(String[] args){
  4. // 创建账户,给定余额为1000
  5. Bank bank = new Bank("song",1000);
  6. // 创建线程对象
  7. SaveAccount saveAccount = new SaveAccount(bank);
  8. DrawAccount drawAccount = new DrawAccount(bank);
  9. Thread save = new Thread(saveAccount);
  10. Thread draw = new Thread(drawAccount);
  11. draw.start();
  12. save.start();
  13. try {
  14. draw.join();
  15. save.join();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. // 以上的join()是为了让这个输出语句最后执行
  20. System.out.println(bank);
  21. }
  22. }

join()方法在主方法中,只能保证调用join()方法的线程执行完才能执行主线程,但是影响不了主方法中的另一个线程。也就是说,draw.join()方法可以保证draw()方法执行完后,再执行主线程,但是不影响save线程的执行。

运行结果如下,这显然是不对的,银行系统出现这种问题,后果将是灾难性的。
存款后的余额为:1100
取款后的余额为:800
账号:song,余额:800

我们来分析下为什么会发生这种情况,主要原因是因为线程中断而没有及时更新银行对象的余额。

  1. 先取款,取款线程中发生的事情是这样的:

函数的balance值更新为800,被中断

  1. 再存款,存款线程中发生的事情是这样的

取得银行对象的balance值为1000(因为取款线程被中断了,所以余额还未更新)
函数的balance值更新为1100
银行对象的balance值更新为1100
输出,即“存款后的余额为:1100”

  1. 回到取款线程

银行对象的balance值更新为800
输出,即“取款后的余额为:800”

  1. 回到主程序,输出“账号:song,余额:800”

那该怎么防止这种情况的发生呢?这时候就需要用到线程同步了
保证在存款或取款的时候,不允许其他线程对账户余额进行操作
需要将Bank对象进行锁定
使用同步关键字synchronized,实现共享对象在同一时刻只能被一个线程访问

synchronized关键字

成员方法 public synchronized void saveAccount(){}
静态方法 public static synchronized void saveAccount(){}
语句块 synchronized(obj){ … }

解决方案

  1. package com.process;
  2. public class Bank {
  3. private String account;
  4. private int balance;
  5. 。。。无修改,省略。。。
  6. // 存款
  7. public synchronized void saveAccount(){
  8. int balance = getBalance();
  9. balance += 100;
  10. setBalance(balance);
  11. System.out.println("存款后的余额为:"+balance);
  12. }
  13. // 取款
  14. public void drawAccount(){
  15. synchronized (this){
  16. int balance = getBalance();
  17. balance -= 200;
  18. try {
  19. Thread.sleep(1000);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. setBalance(balance);
  24. System.out.println("取款后的余额为:"+balance);
  25. }
  26. }
  27. }

运行结果如下:
取款后的余额为:800
存款后的余额为:900
账号:song,余额:900

或者

存款后的余额为:1100
取款后的余额为:900
账号:song,余额:900

线程间通信

wait()方法:中断方法的执行,使线程等待
notify()方法:唤醒处于等待的某一个线程,使其结束等待
notifyAll()方法:唤醒处于等待的所有线程,是它们结束等待

Queue.java

  1. package com.queue;
  2. public class Queue {
  3. private int n;
  4. boolean isActive = false;
  5. public synchronized int getN() {
  6. if(!isActive){
  7. try {
  8. wait();
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. System.out.println("消费:"+n);
  14. isActive = false;
  15. notifyAll(); // 避免死锁问题
  16. return n;
  17. }
  18. public synchronized void setN(int n) {
  19. if (isActive){
  20. try {
  21. wait();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. System.out.println("生产:"+n);
  27. this.n = n;
  28. isActive = true;
  29. notifyAll(); // 避免死锁问题
  30. }
  31. }

Consumer.java

  1. package com.queue;
  2. public class Consumer implements Runnable {
  3. Queue queue;
  4. public Consumer(Queue queue) {
  5. this.queue = queue;
  6. }
  7. @Override
  8. public void run() {
  9. while (true){
  10. queue.getN();
  11. try {
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. }

Producer.java

  1. package com.queue;
  2. public class Producer implements Runnable{
  3. Queue queue;
  4. public Producer(Queue queue) {
  5. this.queue = queue;
  6. }
  7. @Override
  8. public void run() {
  9. int i=0;
  10. while (true){
  11. queue.setN(i++);
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. }

Test.java

  1. package com.queue;
  2. public class Test {
  3. public static void main(String[] args){
  4. Queue queue = new Queue();
  5. new Thread(new Producer(queue)).start();
  6. new Thread(new Consumer(queue)).start();
  7. }
  8. }