Java天然支持多线程,在 java.lang 包实现了一些对多线程支持的类,Thread 以及 Runnable接口,Thread继承了Runnable接口。

image.png

1、创建线程

可以通过继承Thread或者继承Runable来实现多线程,使用start() 方法来启动线程,比如下面的代码

  1. // 继承Thread,重写run() 方法
  2. public class MyThread extends Thread {
  3. @Override
  4. public void run() { } // 重写Run 方法
  5. }
  6. // 实现Runnable 接口
  7. public class MyRunnable implements Runnable {
  8. @Override
  9. public void run() { }// 重写Run 方法
  10. }
  11. // 使用接口
  12. public static void main(String[] args) {
  13. Thread thread = new MyThread();
  14. thread.start();
  15. Thread runnableThread = new Thread(new MyRunnable(), "ThreadName");
  16. runnableThread.start();
  17. // 由于Runable是一种 FunctionalInterface,所以可以使用Lambda 表达式的方式实现
  18. // 但本质和runnableThread 并无区别
  19. Thread functionStyleThread = new Thread(() -> {}, "functionStyleThread");
  20. functionStyleThread.start();
  21. }
  • 手动调用run() 放和调用一个对象的普通方法没有什么本质上的区别,启动一个线程,使用start()方法,start()方法是一个原生的native方法,其方法签名为 public synchronized void start() 如果线程已经启动了,再次启动则会抛出 llegalThreadStateException 异常

2、线程优先级

上文中创建了两个线程,线程的执行是具有随机性的,由操作系统是否分配时间片来决定是否执行。我们可以通过设置操作线程的优先级来设置线程执行的优先级,优先级高的线程分配的时间片更高,优先级低的线程分配的时间片少。

计算密集型任务主要消耗大量CPU资源,不停进行计算。由于依靠CPU性能,一直占用CPU进行计算,也就说一般情况下能够采用多任务的数量等于CPU核心数。为了防止独占CPU,所以一般设置为低优先级。

IO密集型任务(磁盘读取,web服务)主要需要IO的读取,利用CPU的效率较低,大量时间花费在IO上。该种任高优先级比较适合。

在创建线程的时候,创建的线程会默认的继承父线程的一些属性,包括线程优先级,是否是守护线程,以及线程组等信息。线程的优先级并不是默认为5,而是继承父线程的优先级。其init()方法的部分代码如下:

  1. private void init(ThreadGroup g, Runnable target, String name,
  2. long stackSize, AccessControlContext acc,
  3. boolean inheritThreadLocals) {
  4. this.name = name;
  5. Thread parent = currentThread();
  6. this.group = g; // 设置线程组
  7. this.daemon = parent.isDaemon(); // 设置是否是守护线程,其值默认等于父线程的属性值
  8. this.priority = parent.getPriority();
  9. setPriority(priority); // 设置线程优先级,默认值等于父线程的优先级
  10. // 继承父线程的属性
  11. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  12. this.inheritableThreadLocals =
  13. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  14. // 生成线程的ID,方法签名为 private static synchronized long nextThreadID()
  15. tid = nextThreadID();
  16. }

3、线程优先级失效

在开发的过程中,我们并不不能依赖线程的优先级,来实现业务,这是因为在不同的操作系统中,对线程的规划有所差异,有的甚至会忽略线程的优先级,比如 MacOS 或者 Ubuntu。
下面的代码中,创建了两个线程,一个设置高优先级一个设置低优先级,观察最后的输出结果会发现,两个执行并没有太大的差别,所以线程的优先级在某些操作系统或者某些JVM实现上可能有所差异。

private static volatile boolean start = false;

private static volatile boolean end = false;

public static void main(String[] args) throws InterruptedException {
  PriorityThread runnable1 = new PriorityThread();
  PriorityThread runnable2 = new PriorityThread();

  // 设置线程优先级 
  // MIN_PRIORITY = 0; NORM_PRIORITY=5;  MAX_PRIORITY = 10
  runnable1.setPriority(Thread.MIN_PRIORITY);
  runnable2.setPriority(Thread.MAX_PRIORITY);

  runnable1.start();
  runnable2.start();

  start = true;
  TimeUnit.SECONDS.sleep(2);
  end = true;

  // 输出执行的结果
  System.out.println("count1 = " + runnable1.count);
  System.out.println("count2 = " + runnable2.count);
}

static class PriorityThread extends Thread {

  private Long count = 0L;

  @Override
  public void run() {
    // 没有开始,则一直让出线程执行的时间片
    while (!start) {
      Thread.yield();
    }

    // 开始执行,每次执行都让出时间片
    while (!end) {
      Thread.yield();
      count++;
    }
  }
}

根据上面的代码,我们预估,线程2由于优先级高,所以其count值应该会比线程1高,但是结果却是两者差别并不大。这因为笔者的操作系统MacOS,操作系统忽略了线程的优先级,所以在线程让出时间片的时候并不会有限分配给高优先级的线程来执行。读者可自定在不同的操作系统尝试上面的代码,观察不同的操作系统是否对线程优先级进行忽略。

count1 = 5531926
count2 = 5525194