开始

线程: 线程是操作系统能够进行运算调度的最小单位, 线程被包含在进程之中, 是进程中的实际运作单位

线程可被描述为应用软件中互相独立, 可以同时运行的功能

并发/并行

  • 并发: 同一时刻, 有多个指令在单个 CPU 上交替执行
  • 并行: 同一时刻, 有多个指令在多个 CPU 上同时执行

Java 多线程

Thread 类

继承 Thread 类, 重写 run() 方法

public class JThread extends Thread {

  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      System.out.println("This is " + this.getName());
    }
  }
}
JThread thread1 = new JThread();

// 为线程命名
thread1.setName("Thread 1");

// 开启线程
thread1.start();
线程对象会有默认名字, 格式为 Thread-[number]

Thread 的常用成员方法

方法返回值说明
getName()String获取线程名称
setName(String name)void设置线程名称
static currentThread()Thread获取当前线程
static sleep(long time)void线程休眠, 单位为毫秒
setPriority()void设置线程优先级
getPriority()int获取优先级
setDaemon(boolean on)void设置为守护进程
static yield()void让出线程
static join()void插入线程

Runnable Interface

实现 Runnable 接口

public class JRunnable implements Runnable {
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      System.out.println("This is " + Thread.currentThread().getName());
    }
  }
}

可通过 Thread 类提供的静态方法 currentThread() 获取当前线程对象

将实现的接口放入 Thread 对象中并运行

Runnable jr = new JRunnable();

Thread t1 = new Thread(jr);
Thread t2 = new Thread(jr);

t1.setName("Thread 1");
t2.setName("Thread 2");

t1.start();
t2.start();

Callable Interface

ThreadRunnable 不同, Callable 的线程函数可返回值

实现 Callable 接口

public class JCallable implements Callable<Integer> {
  @Override
  public Integer call() throws Exception {
    int sum = 0;
    for (int i = 1; i < 100; i++) {
      sum += i;
    }
    return sum;
  }
}

创建 Callable 的实现对象, 然后创建 FeatureTask 对象和 Thread 对象

开启线程, 并通过 FeatureTask 获取线程运行结果

JCallable jCallable = new JCallable();

FutureTask<Integer> futureTask = new FutureTask<>(jCallable);
Thread thread = new Thread(futureTask);

thread.start();
Integer result = futureTask.get();

线程优先级

线程的调度式方式

  • 抢占式调度 (Java 采用的调度方式)
  • 非抢占式调度

setPriority() 方法设置线程优先级, 优先级从 1 ~ 10, 分十级, 线程默认优先级为5

优先级越高, 线程能抢到 CPU 的概率越大

守护线程

当其它的非守护线程执行完毕后, 守护线程会陆续结束

假设线程一运行时长为 2 秒, 线程二(守护线程)运行时间为 10 秒, 两个线程同时启动, 随着线程一运行完毕后, 线程二就会跟着关闭(不是立即关闭), 无论线程二是否运行完它的任务

更多

生命周期

线程生命周期.png


线程安全

一个例子:

为一个线程对象设置 static 公共静态变量 count, run() 方法是 while 循环自增 count (期间打印 count ), 直到 count > 100 停止循环;

同时创建三个这样的线程, 并开始:

public class JLockThread extends Thread {

  private static int count = 0;

  public JLockThread() {
  }

  public JLockThread(String name) {
    super(name);
  }

  @Override
  public void run() {
    while (true) {
      try {
        Thread.sleep(10);
      } catch (Exception e) {
        e.printStackTrace();
      }

      if (count >= 100) break;

      count++;
      System.out.println(getName() + " | " + count);
    }
  }
}
若是写 while(count < 100), 数字不会超出 100

出现问题:

  • count 会重复出现相同的数字
  • 循环结束, count 会超出 100

原因: 由于线程随时会抢夺 CPU 执行权, 单个线程的每个代码段执行权都有可能被其它线程抢走, 由于可能会出现三个进程同时执行在 count 自增的代码上 , 导致数字重复和数字超出


同步代码块 synchronized

为避免这个问题, 采用线程锁 synchronized 来限制线程执行权, 以此避免这个安全问题

通过线程锁, 其它线程必须等待当前线程执行完毕后, 才可执行

// 锁对象, 必须唯一
Object lock = new Object();

synchronized (lock) {
  // 同步代码块
  ...
}

改进代码:

public class JLockThread extends Thread {

  private static int count = 0;
  private final static Object lock = new Object();

  public JLockThread() {
  }

  public JLockThread(String name) {
    super(name);
  }

  @Override
  public void run() {
    while (true) {
      // 这里的锁对象 lock 也可以换成 JLockThread.class
      synchronized (lock) {
        try {
          Thread.sleep(10);
        } catch (Exception e) {
          e.printStackTrace();
        }

        if (count >= 100) break;

        count++;
        System.out.println(getName() + " | " + count);
      }
    }

  }
}
锁对象, 必须唯一

同步方法

同步方法就是将修饰符 synchronized 加到方法上, 用于锁住方法中的所有代码

synchronized void func(){
  ...
}

同步方法的锁对象不能被指定, 锁对象视情况而定:

  • 方法非静态 - 锁对象为 this
  • 方法静态 - 锁对象为当前类的字节码文件对象
public class JLockThread extends Thread {

  private static int count = 0;

  public JLockThread() {
  }

  public JLockThread(String name) {
    super(name);
  }

  static synchronized boolean plus() {
    try {
      Thread.sleep(10);
    } catch (Exception e) {
      e.printStackTrace();
    }

    if (count >= 100) {
      return true;
    } else {
      count++;
      System.out.println(Thread.currentThread().getName() + " | " + count);
      return false;
    }
  }

  @Override
  public void run() {
    while (true) {
      if (plus()) break;
    }
  }
}

Lock Interface

synchronized 修饰符不能手动释放锁, 因此 JDK5 引入 Lock 接口, 用于手动获得和释放锁

Lock 接口功能直接实例化, 可用它的实现类 ReentrantLock 来实例化

使用 Lock 要额外注意锁的释放, 一般做法是将 .unlock 方法放到 finally 代码块中, 保证锁能够完全释放

finally 有个特性, 就是无论如何 Java 最终都会执行里面的代码

死锁

不要写嵌套锁, 这样能最大程度的避免死锁

导致死锁的常见原因:

  • 资源互斥
  • 资源占有等待
  • 不可抢占
  • 循环等待

其它

线程安全的对象:

StringBuilderStringBuffer 为例, 两个对象的成员方法一模一样, 其不同就是线程安全性

  • StringBuffer 线程安全
  • StringBuilder 线程不安全

如何选择?

单线程环境用 StringBuilder, 多线程用 StringBuffer, 就这么简单


该部分仅登录用户可见

最后修改:2025 年 07 月 21 日
如果觉得我的文章对你有用,请随意赞赏