Java线程相关问题

2019/02/23 JAVA

Java线程相关问题

1.Java正确的停止一个线程

interrupt()方法在调用该方法的线程种打一个停止标志,将线程状态置为“中断”。

当线程处于中断状态时,如果再由wait,sleep以及join三个方法引起阻塞,那么JVM会将线程的中断标志重新置为false,并抛出InterruptedException异常,然后可以捕获该异常进行各种处理,比如退出线程。

当线程处于中断状态时,再调用interrupted()或者isInterrupted()方法,手动抛出异常并捕获,然后退出线程。

interrupt()方法

interrupt()方法是在调用该方法的线程中打一个停止标志,并不是真的停止线程。

public class MyThread extends Thread {
   public void run(){
      super.run();
      for(int i=0; i<500000; i++){
         System.out.println("i="+(i+1));
      }
   }
}

public class Run {
   public static void main(String args[]){
      Thread thread = new MyThread();
      thread.start();
      try {
         Thread.sleep(2000);
         thread.interrupt();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

即使调用了interrupt()方法,for循环依然会执行完。

判断线程是否停止状态

Thread.java类提供两个方法:

this.interrupted():作用于当前线程,并清除该线程的中断状态;

this.isInterrupted():作用于调用该方法的线程对象所对应的线程,不会清楚线程的中断状态。

停止线程–抛异常

在线程中用for循环判断线程是否是停止状态,如果是停止状态,则后面的代码不再运行,并且for循环后面的语句也不会再执行,因为判断线程是停止状态时抛出异常,并捕获了该异常。

public class MyThread extends Thread {
   public void run(){
      super.run();
      try {
         for(int i=0; i<500000; i++){
            if(this.interrupted()) {
               System.out.println("线程已经终止, for循环不再执行");
               throw new InterruptedException();
            }
            System.out.println("i="+(i+1));
         }

         System.out.println("这是for循环外面的语句,也会被执行");
      } catch (InterruptedException e) {
         System.out.println("进入MyThread.java类中的catch了。。。");
         e.printStackTrace();
      }
   }
}

public class Run {
   public static void main(String args[]){
      Thread thread = new MyThread();
      thread.start();
      try {
         Thread.sleep(2000);
         thread.interrupt();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

2.Callable,Future,FutureTask

2.1 Callable

相对于Runnable,在执行完任务后,会有返回结果。

一般配合ExecutorService使用,ExecutorService接口中声明了若干个submit方法的重载版本:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

2.2 Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消,查询是否完成,获取结果。Future只是一个接口,无法直接创建对象,因此有了FutureTask。

2.3 FutureTask

FutureTask类实现了RunnableFuture接口,RunnableFuture接口又继承自Runnable和Future接口。所以它即可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

2.4 Example

2.4.1 Callable+Future
public class Test {
   public static void main(String[] args) {
      ExecutorService executor = Executors.newCachedThreadPool();
      Task task = new Task();
      Future<Integer> result = executor.submit(task);
      executor.shutdown();

      try {
         Thread.sleep(1000);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      System.out.println("主线程在执行任务");

      try {
         System.out.println("task运行结果"+result.get());
      } catch (Exception e) {
         e.printStackTrace();
      }

      System.out.println("所有任务执行完毕");
   }
}
class Task implements Callable<Integer>{
   @Override
   public Integer call() throws Exception {
      System.out.println("子线程在进行计算");
      Thread.sleep(3000);
      int sum = 0;
      for(int i=0;i<100;i++)
         sum += i;
      return sum;
   }
}
2.4.2 Callable+FutureTask
public class Test {
   public static void main(String[] args) {
      //第一种方式
      ExecutorService executor = Executors.newCachedThreadPool();
      Task task = new Task();
      FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
      executor.submit(futureTask);
      executor.shutdown();

      //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
       /*Task task = new Task();
       FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
       Thread thread = new Thread(futureTask);
       thread.start();*/

      try {
         Thread.sleep(1000);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      System.out.println("主线程在执行任务");

      try {
         System.out.println("task运行结果"+futureTask.get());
      } catch (Exception e) {
         e.printStackTrace();
      }

      System.out.println("所有任务执行完毕");
   }
}
class Task implements Callable<Integer>{
   @Override
   public Integer call() throws Exception {
      System.out.println("子线程在进行计算");
      Thread.sleep(3000);
      int sum = 0;
      for(int i=0;i<100;i++)
         sum += i;
      return sum;
   }
}

3.检测一个线程是否拥有锁

Thread类中的holdsLock() 方法,返回true表示当前线程拥有某个具体对象的锁。

4.提交任务时,线程队列已满

线程池的线程=MaxPoolSize,且拒绝策略是AbortPolicy(默认)的前提下 ,如果线程池队列被占满,一个任务不能被调度执行,那么ThreadPoolExecutor.submit()方法将会抛出一个RejectedExecutionExeception异常。

5.三个线程T1,T2,T3,怎么保证它们按顺序执行

  1. join()方法在一个线程中启动另一个线程,另外一个线程完成之后该线程继续执行。为确保三个线程顺序执行,应该先启动最后一个,T3调用T2,T2调用T1,这样T1就会先完成而T3最后完成。
public static void main(String[] args) {
    try {
        T3 t3= t.new T3("T3");
        t3.start();//启动t3线程
        t3.join();//阻塞主线程,执行完t3再返回

        T2 t2= t.new T2("T2");
        t2.start();//启动t3线程
        t2.join();//阻塞主线程,执行完t3再返回

        T1 t1= t.new T1("T1");
        t1.start();//启动t3线程
        t1.join();//阻塞主线程,执行完t3再返回

    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  1. 单个线程池(SingleThreadExecutor)来实现,一个线程里,执行三个任务。
ExecutorService executor = Executors.newSingleThreadExecutor();

executor.submit(t3);
executor.submit(t2);
executor.submit(t1);
executor.shutdown();
  1. 顺序在线程中创建实例。
public static void main(String[] args) {
    new T1().start();
    new T2().start();
    new T3().start();
}

6.多个线程之间共享数据

6.1 多个线程执行的代码相同

使用同一个Runnable对象,这个Runnable对象有那个共享数据。

6.2 多个线程执行的代码不同

将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象去完成。

将这些Runnable对象作为一个类的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

将共享数据封装在另外一个对象中,每个线程对共享对象的操作方法也分配到那个对象去完成,对象作为外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

7.内存屏障/内存栅栏

一旦内存数据被推送到缓存,就会有消息协议来确保所有的缓存会对所有的共享数据同步并保持一致,这个使内存数据对CPU核可见的计数被称为内存屏障或内存栅栏。

7.1 内存屏障提供两个功能

1.通过确保从另一个CPU来看屏障的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;

2.可以实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。

7.2 内存屏障的分类

Intel CPU X86处理器的架构中有Store BarrierLoad BarrierFull Barrier

7.2.1 Store Barrier

store屏障,x86的sfence指令,强制所有在store屏障指令之前的store指令,都在该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到CPU缓存。这会使得程序状态对其它CPU可见,这样其它CPU可以根据需要介入。

7.2.2 Load Barrier

Load屏障,x86上的mfence指令,强制所有在load屏障指令之后的load指令,都在该load屏障指令执行之后被执行,并且一直等到load缓冲区被该CPU读完才能执行之后的load指令。这使得从其它CPU暴露出来的程序状态对该CPU可见,这之后CPU可以进行后续处理。

7.2.3 Full Barrier

Full屏障,复合了load和store屏障的功能。

7.3 Java内存模型中的应用

Java内存模型中volatile变量在写操作之后会插入一个store屏障,在读操作之前会插入一个load屏障;一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。

原子指令和Software Locks

原子指令,如x86上的lock指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU;

Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。

7.4 内存屏障的性能影响

内存屏障阻碍了CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失。为了达到最佳性能,最好把要解决的问题 模块化,这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障。采用这个方法可以让处理器不受限的执行一个任务单元。合理的内存屏障组合还有一个好处:缓冲区在第一次被刷后开销会减少,因为再填充该缓冲区不需要额外工作了。

参考:

JAVA高并发多线程必须懂的50个问题

java—interrupt、interrupted和isInterrupted的区别

如何正确地停止一个线程?

Java并发编程:Callable、Future和FutureTask

三个线程T1,T2,T3.保证顺序执行的三种方法

Condition保证ABC按顺序打印

内存屏障或内存栅栏

Search

    Table of Contents