三分钟上手java多线程控制技术

2,802 阅读4分钟

java相比python、php等脚本语言一个非常重要的区别就是java拥有真正的多线程控制技术,掌握多线程技术对于使用java语言至关重要

默认行为

  • 主线程会等待所有子线程执行完成后退出
  • 多个线程无序执行

创建线程

实现Runnable接口,在线程中执行的代码放到Runnable接口实现中,为实现类起个名字比如MyRunner:

public class MyRunner implements Runnable {
    @Override
    public void run() {
      // 在线程中执行的代码
      System.out.println("running in a thread, name: " + Thread.currentThread().getName());
    }
}

创建线程对象java.lang.Thread,并将MyRunner作为线程执行体,然后调用Thread.start()方法启动线程

public class MyThread {
    public static void main(String[] args) {
      new Thread(new MyRunner()).start();
      new Thread(new MyRunner()).start();
      new Thread(new MyRunner()).start();
      new Thread(new MyRunner()).start();

      System.out.println("Main thread exit");
    }
}

执行效果参考如下

可以看到这里主线程代码先执行完毕然后进入等待,子线程执行完成后,主线程退出

暂停线程

可以使用Thread.sleep()方法暂停线程的执行,比如暂停5秒

  try {
    Thread.sleep(5000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }

可以看到线程调用Thread.sleep进入暂停状态后,可能在暂停过程中产生异常,这个异常表示收到了终止暂停状态的信号

终止线程的暂停状态

调用线程对象(java.lang.Thread)的interrupt()方法可以终止处于暂停状态的线程,使其恢复成执行状态

线程执行体MyRunner.java:

public class MyRunner implements Runnable {
    @Override
    public void run() {
      try {
        Thread.sleep(5000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      // 在线程中执行的代码
      System.out.println("running in a thread, name: " + Thread.currentThread().getName());
    }
}

调用线程的interrupt方法终止线程的sleep状态MyThread.java

public class MyThread {
    public static void main(String[] args) {
      Thread thread = new Thread(new MyRunner());
      thread.start();
      thread.interrupt();

      System.out.println("Main thread exit");
    }
}

执行效果如下:

可以看到线程的sleep被终止,并收到了InterruptedException异常

可以使用Thread.currentThread().isInterrupted()手动检查当前线程是否被中断过

主动等待子线程执行完毕

使用线程去下载一个文件,然后主线程在执行完一段业务逻辑后,需要使用线程下载的文件,这个时候,线程不一定已经将文件下载完成,主线程就需要等待子线程执行完成了

MyThread.java 模拟文件下载线程

public class MyRunner implements Runnable {
    @Override
    public void run() {
      System.out.println("1. start download file...");
      try {
        Thread.sleep(5000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("2. download file complete");
    }
}

主线程使用join()方法等待线程执行完成

Thread thread = new Thread(new MyRunner());
thread.start();

try {
  thread.join();
} catch (InterruptedException e) {
  e.printStackTrace();
}
System.out.println("3. process downloaded file");

执行效果参考如下:

线程间状态通知技术wait()和notify()

线程1: 下载文件 线程2: 在文件下载完成后,对文件进行处理

DownloadStatus.java在线程间传递文件下载状态

public class DownloadStatus {
  private boolean isFinish = false;

  public boolean isFinish() {
    return isFinish;
  }

  public void setFinish(boolean finish) {
    isFinish = finish;
  }
}

下载文件线程 DownloadThread.java

public class DownloadThread implements Runnable {

  private DownloadStatus status;

  public DownloadThread(DownloadStatus status) {
    this.status = status;
  }

  @Override
  public void run() {
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("file in thread download finished.");
    status.setFinish(true);
    synchronized (status) {
      status.notifyAll();
    }
  }
}

可以看到文件下载完成后将下载完成标志isFinish置为true,并调用了状态对象的notifyAll()方法

处理文件线程

DownloadStatus status = new DownloadStatus();

Thread thread = new Thread(new DownloadThread(status));
thread.start();
new Thread(() -> {
  while (!status.isFinish()) {
    synchronized (status) {
      try {
        status.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
  System.out.println("process download file");
}).start();

文件线程中启动死循环检测文件是否下载完成 while !status.isFinish(),并在循环体中调用wait()方法等待下载状态对象的通知

使用notifyAll()和wait()进行状态变更通知是为了避免死循环占用过多的cpu资源

多线程保证变量安全技术

多线程变量安全指多个线程对同一个变量进行修改,其修改后的结果符合预期

1. 每个线程使用独立的变量,在全部线程执行完成后,对每个线程中的变量进行汇总 2. 对变量进行加锁,加可重入锁

lock = new ReentrantLock();
lock.lock();
// 对变量进行修改的code
lock.unlock();

3. 使用synchronized关键字

synchronized(需要检测的对象) {
    // 对变量进行修改的code
}

4. volatile关键字:使cpu不将内存中的变量放到cpu缓存中,避免多线程变量可见性问题

private volatile bool isFinished;

5. atomic系列类, java.util.concurrent.atomic

private AtomicInteger count;

一些注意的点

  1. notify通知一个wait()的线程,notifyAll通知所有wait的线程
  2. 多线程集合对象可以使用synchronized集合保证线程安全 Collection<Integer> intList = Collections.synchronizedCollection(new ArrayList<>())
  3. 多线程hash表对象可以使用ConcurrentHashMap保证线程安全 Map<Integer, String> map = new ConcurrentHashMap<>()

参考资料

  1. www.cnblogs.com/duanxz/p/50…
  2. blog.csdn.net/onlybless/a…
  3. www.cnblogs.com/mengdd/arch…