并发编程(三)初级1分钟看懂Java多线程与高并发

预告准备:一共分为三个部分。初级为了便于理解可以直接看第三部分,中级看所有,高级查缺补漏。

一、Java中的多线程

应用程序中有两种类型的线程 - 用户线程和守护程序线程。当我们启动一个应用程序时,main是第一个创建的用户线程,我们可以创建多个用户线程以及守护进程线程。执行所有用户线程后,JVM将终止该程序。

我们可以为不同的线程设置不同的优先级,但它不保证优先级较高的线程将比低优先级线程首先执行。线程调度程序是操作系统实现的一部分,当线程启动时,它的执行由线程调度程序控制,JVM对其执行没有任何控制。

我们可以通过实现Runnable接口或扩展Thread Class来创建Threads。

Thread t = new Thread(new Runnable(){
    @Override
    public void run() {
    }
});

上面是一个创建新Thread的单行语句,这里我们创建Runnable作为Anonymous Class
使用Java 8 lambda表达式,我们也可以在java中创建Thread,因为Runnable是一个功能接口。

Thread t = new Thread(() -> {System.out.println("My Runnable");});
t.start();

二、Java中的线程

  1. Java线程示例 并发编程(三)初级1分钟看懂Java多线程与高并发并发编程(三)初级1分钟看懂Java多线程与高并发

  2. Java线程sleep 并发编程(三)初级1分钟看懂Java多线程与高并发

  3. Java线程join
    并发编程(三)初级1分钟看懂Java多线程与高并发

  4. Java线程states并发编程(三)初级1分钟看懂Java多线程与高并发

  5. Java线程wait,notify和notifyAll 并发编程(三)初级1分钟看懂Java多线程与高并发

  6. Java线程安全 和Synchronization锁

线程安全:Java中的线程安全是一个非常重要的主题。Java使用Java Threads提供多线程环境支持,我们知道从相同的Object共享对象变量创建的多个线程,当线程用于读取和更新共享数据时,这可能导致数据不一致。数据不一致的原因是因为更新任何字段值不是原子过程,它需要三个步骤; 首先读取当前值,第二个读取必要的操作以获取更新的值,第三个将更新的值分配给字段引用。

如何保证线程安全
并发编程(三)初级1分钟看懂Java多线程与高并发

  1. 单例类中的线程安全性

一个类只能有一个实例称之为单例。
步骤三步:
1、将构造函数私有化
2、在类的内部创建实例
3、提供获取唯一实例的方法
使用静态类.doSomething()体现的是基于对象,而使用单例设计模式体现的是面向对象。

单例模式-懒汉式


public class Java3y {

    // 1.将构造函数私有化,不可以通过new的方式来创建对象
    private Java3y(){}

    // 2.1先不创建对象,等用到的时候再创建
    private static Java3y java3y = null;

    // 2.1调用到这个方法了,证明是要被用到的了
    public static Java3y getJava3y() {

        // 3. 如果这个对象引用为null,我们就创建并返回出去
        if (java3y == null) {
            java3y = new Java3y();
        }

        return java3y;
    }
}

单线程可以,多线程环境下不安全

加锁
public class Java3y {

    // 1.将构造函数私有化,不可以通过new的方式来创建对象
    private Java3y(){}

    // 2.1先不创建对象,等用到的时候再创建
    private static Java3y java3y = null;

    // 2.1调用到这个方法了,证明是要被用到的了
    public static synchronized Java3y getJava3y() {

        // 3. 如果这个对象引用为null,我们就创建并返回出去
        if (java3y == null) {
            java3y = new Java3y();
        }

        return java3y;
    }
}
优点:
1、线程安全性得到保证。
2、客户端应用可以传递参数
3、实现了懒惰初始化
缺点:
1、由于锁定开销导致性能下降。
2、初始化实例变量后不需要的不必要的同步

那么如何提高性能?

双重检测机制(DCL)懒汉式
public class Java3y {


    private Java3y() {
    }

    private static Java3y java3y = null;


    public static Java3y getJava3y() {
        if (java3y == null) {
            // 将锁的范围缩小,提高性能
            synchronized (Java3y.class) {
                java3y = new Java3y();
            }
        }
        return java3y;
    }
}
线程A和线程B同时调用getJava3y()方法,他们同时判断java==null,得出的结果都是为null,所以进入了if代码块了
此时线程A得到CPU的控制权-->进入同步代码块-->创建对象-->返回对象
线程A完成了以后,此时线程B得到了CPU的控制权。同样是-->进入同步代码块-->创建对象-->返回对象
很明显的是:Java3y类返回了不止一个实例!所以上面的代码是不行的!



public class Java3y {


    private Java3y() {
    }

    private static Java3y java3y = null;

    public static Java3y getJava3y() {
        if (java3y == null) {

            // 将锁的范围缩小,提高性能
            synchronized (Java3y.class) {

                // 再判断一次是否为null
                if (java3y == null) {
                    java3y = new Java3y();
                }
            }
        }
        return java3y;
    }
}
重排序问题


public class Java3y {
    private Java3y() {
    }

    private static volatile Java3y java3y = null;

    public static Java3y getJava3y() {
        if (java3y == null) {

            // 将锁的范围缩小,提高性能
            synchronized (Java3y.class) {

                // 再判断一次是否为null
                if (java3y == null) {
                    java3y = new Java3y();
                }
            }
        }
        return java3y;
    }
}
volatile 实现可见性

package com.designpatterns;

public class ASingleton {

	private static volatile ASingleton instance;
	private static Object mutex = new Object();

	private ASingleton() {
	}

	public static ASingleton getInstance() {
		ASingleton result = instance;
		if (result == null) {
			synchronized (mutex) {
				result = instance;
				if (result == null)
					instance = result = new ASingleton();
			}
		}
		return result;
	}

}
局部变量result似乎没必要。但它可以提高我们代码的性能。在实例已经初始化的情况下(大多数情况下),volatile字段只被访问一次(由于“return result;”而不是“return instance;”)。这可以将方法的整体性能提高多达25%

单例模式-饿汉式


public class Java3y {

    // 1.将构造函数私有化,不可以通过new的方式来创建对象
    private Java3y(){}

    // 2.在类的内部创建自行实例
    private static final Java3y java3y = new Java3y();

    // 3.提供获取唯一实例的方法
    public static Java3y getJava3y() {
        return java3y;
    }
}
一上来就创建对象了,如果该实例从始至终都没被使用过,则会造成内存浪费
单线程可以,多线程环境下不安全

在类加载时创建实例变量
优点:
1、线程安全无需同步
2、易于实施
缺点:
1、早期创建可能未在应用程序中使用的资源。
2、客户端应用程序无法传递任何参数,因此我们无法重用它。例如,具有用于数据库连接的通用单例类,其中客户端应用程序提供数据库服务器属性,枚举类排除掉
并发编程(三)初级1分钟看懂Java多线程与高并发
并发编程(三)初级1分钟看懂Java多线程与高并发

在if循环和volatile变量中使用synchronized块

package com.designpatterns;

public class ASingleton {

	private static volatile ASingleton instance;
	private static Object mutex = new Object();

	private ASingleton() {
	}

	public static ASingleton getInstance() {
		ASingleton result = instance;
		if (result == null) {
			synchronized (mutex) {
				result = instance;
				if (result == null)
					instance = result = new ASingleton();
			}
		}
		return result;
	}

}
优点:
1、线程安全性得到保证
2、客户端应用可以传递参数
3、实现了懒惰初始化
4、同步开销很小,仅当变量为null时才适用于前几个线程
缺点:
1、额外的条件
  1. Java中的守护程序线程
DaemonThread具有线程休眠的while循环,因此它永远不会自行终止
最好避免用于IO操作的守护程序线程,因为当程序刚终止并且资源未正确关闭时,它可能导致资源泄漏。
  1. Java线程本地

Java ThreadLocal用于创建线程局部变量。我们知道Object的所有线程都共享它的变量,因此该变量不是线程安全的。我们可以使用同步来实现线程安全,但如果我们想避免同步,我们可以使用ThreadLocal变量。
每个线程都有自己的ThreadLocal变量,并且可以使用它的get()和set()方法来获取默认值或将其值更改为Thread的本地值。
ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段。

jdk8以前
package com.threads;

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };
    
    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //formatter pattern is changed here by thread, but it won't reflect to other threads
        formatter.set(new SimpleDateFormat());
        
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}


jdk8+以后

private static final ThreadLocal<SimpleDateFormat> formatter = 
	ThreadLocal.<SimpleDateFormat>withInitial
	(() -> {return new SimpleDateFormat("yyyyMMdd HHmm");});

  1. Java线程转储
定义:Java线程转储是JVM中活动的所有线程的列表
作用:Java线程转储非常有助于分析应用程序和死锁情况中的瓶颈。

并发编程(三)初级1分钟看懂Java多线程与高并发
并发编程(三)初级1分钟看懂Java多线程与高并发

  1. 如何分析死锁并在Java中避免它

定义:
java中的死锁是一种编程情况,其中两个或多个线程永远被阻塞。Java死锁情况出现在至少两个线程和两个或更多资源上。

死锁举例

package com.threads;

public class ThreadDeadlock {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
    
        Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
        Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
        Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
        
        t1.start();
        Thread.sleep(5000);
        t2.start();
        Thread.sleep(5000);
        t3.start();
        
    }

}

class SyncThread implements Runnable{
    private Object obj1;
    private Object obj2;

    public SyncThread(Object o1, Object o2){
        this.obj1=o1;
        this.obj2=o2;
    }
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + " acquiring lock on "+obj1);
        synchronized (obj1) {
         System.out.println(name + " acquired lock on "+obj1);
         work();
         System.out.println(name + " acquiring lock on "+obj2);
         synchronized (obj2) {
            System.out.println(name + " acquired lock on "+obj2);
            work();
        }
         System.out.println(name + " released lock on "+obj2);
        }
        System.out.println(name + " released lock on "+obj1);
        System.out.println(name + " finished execution.");
    }
    private void work() {
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

dump举例:

2012-12-27 19:08:34
Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode):

"Attach Listener" daemon prio=5 tid=0x00007fb0a2814000 nid=0x4007 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t3" prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f658> (a java.lang.Object)
	- locked <0x000000013df2f678> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

"t2" prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f678> (a java.lang.Object)
	- locked <0x000000013df2f668> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

"t1" prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f668> (a java.lang.Object)
	- locked <0x000000013df2f658> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

"Service Thread" daemon prio=5 tid=0x00007fb0a1038000 nid=0x5303 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=5 tid=0x00007fb0a1037000 nid=0x5203 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=5 tid=0x00007fb0a1016000 nid=0x5103 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=5 tid=0x00007fb0a4003000 nid=0x5003 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object.wait() [0x000000015d0c0000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
	- locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)

"Reference Handler" daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object.wait() [0x000000015cfbd000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000013de75320> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:503)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
	- locked <0x000000013de75320> (a java.lang.ref.Reference$Lock)

"VM Thread" prio=5 tid=0x00007fb0a2049800 nid=0x3d03 runnable 

"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fb0a300d800 nid=0x3503 runnable 

"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fb0a2001800 nid=0x3603 runnable 

"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fb0a2003800 nid=0x3703 runnable 

"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fb0a2004000 nid=0x3803 runnable 

"GC task thread#4 (ParallelGC)" prio=5 tid=0x00007fb0a2005000 nid=0x3903 runnable 

"GC task thread#5 (ParallelGC)" prio=5 tid=0x00007fb0a2005800 nid=0x3a03 runnable 

"GC task thread#6 (ParallelGC)" prio=5 tid=0x00007fb0a2006000 nid=0x3b03 runnable 

"GC task thread#7 (ParallelGC)" prio=5 tid=0x00007fb0a2006800 nid=0x3c03 runnable 

"VM Periodic Task Thread" prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition 

JNI global references: 114


Found one Java-level deadlock:
=============================
"t3":
  waiting to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang.Object),
  which is held by "t2"
"t2":
  waiting to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang.Object),
  which is held by "t3"

Java stack information for the threads listed above:
===================================================
"t3":
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f658> (a java.lang.Object)
	- locked <0x000000013df2f678> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)
"t1":
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f668> (a java.lang.Object)
	- locked <0x000000013df2f658> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)
"t2":
	at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41)
	- waiting to lock <0x000000013df2f678> (a java.lang.Object)
	- locked <0x000000013df2f668> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:722)

Found 1 deadlock.

dump分析
dump分析:线程转储输出清楚地显示了死锁情况以及涉及的线程和资源导致死锁情况。 为了分析死锁,我们需要注意状态为BLOCKED的线程,然后是等待锁定的资源。每个资源都有一个唯一的ID,我们可以使用它找到哪个线程已经在对象上持有锁。例如,线程“t3”正在等待锁定0x000000013df2f658,但它已被线程“t1”锁定。

如何避免java中的死锁
1避免嵌套锁:这是死锁的最常见原因,如果已经存在死锁,请避免锁定另一个资源。如果您只使用一个对象锁,几乎不可能出现死锁情况。例如,这是run()方法的另一个实现,没有嵌套锁,程序运行成功,没有死锁情况


2仅限锁定所需内容:您应该仅对您必须处理的资源获取锁定,例如在上面的程序中我锁定了完整的Object资源,但如果我们只对其中一个字段感兴趣,那么我们应该仅锁定特定字段不完整对象。


3避免无限期等待:如果两个线程正在等待彼此无限期地使用线程连接,则可能会出现死锁。如果你的线程必须等待另一个线程完成,那么最好在你想要等待线程完成的最长时间内使用join

以编程的方式查找死锁:
一组很好的抽象和非常容易使用的多线程类
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); this.scheduler.scheduleAtFixedRate(deadlockCheck, period, period, unit);

一个方法来接收描述处于死锁状态的线程的对象列表
void handleDeadlock(final ThreadInfo[] deadlockedThreads);

public interface DeadlockHandler {
  void handleDeadlock(final ThreadInfo[] deadlockedThreads);
}

public class DeadlockDetector {

  private final DeadlockHandler deadlockHandler;
  private final long period;
  private final TimeUnit unit;
  private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
  private final ScheduledExecutorService scheduler = 
  Executors.newScheduledThreadPool(1);
  
  final Runnable deadlockCheck = new Runnable() {
    @Override
    public void run() {
      long[] deadlockedThreadIds = DeadlockDetector.this.mbean.findDeadlockedThreads();
    
      if (deadlockedThreadIds != null) {
        ThreadInfo[] threadInfos = 
        DeadlockDetector.this.mbean.getThreadInfo(deadlockedThreadIds);
      
        DeadlockDetector.this.deadlockHandler.handleDeadlock(threadInfos);
      }
    }
  };
  
  public DeadlockDetector(final DeadlockHandler deadlockHandler, 
    final long period, final TimeUnit unit) {
    this.deadlockHandler = deadlockHandler;
    this.period = period;
    this.unit = unit;
  }
  
  public void start() {
    this.scheduler.scheduleAtFixedRate(
    this.deadlockCheck, this.period, this.period, this.unit);
  }
}

创建一个处理程序,以将死锁线程信息输出到System.err。然后我们使用它发送电子邮件

public class DeadlockConsoleHandler implements DeadlockHandler {

  @Override
  public void handleDeadlock(final ThreadInfo[] deadlockedThreads) {
    if (deadlockedThreads != null) {
      System.err.println("Deadlock detected!");
      
      Map<Thread, StackTraceElement[]> stackTraceMap = Thread.getAllStackTraces();
      for (ThreadInfo threadInfo : deadlockedThreads) {
      
        if (threadInfo != null) {
      
          for (Thread thread : Thread.getAllStackTraces().keySet()) {
        
            if (thread.getId() == threadInfo.getThreadId()) {
              System.err.println(threadInfo.toString().trim());
                
              for (StackTraceElement ste : thread.getStackTrace()) {
                  System.err.println("\t" + ste.toString().trim());
              }
            }
          }
        }
      }
    }
  }
}

这将遍历所有堆栈跟踪并打印每个线程信息的堆栈跟踪。这样我们就可以准确地知道每个线程在哪一行上等待,以及哪个锁。这种方法有一个缺点 - 如果其中一个线程正在等待超时(实际上可以看作是临时死锁),它可能会发出错误警报。因此,当我们处理死锁时,原始线程不再存在,并且findDeadlockedThreads将为此类线程返回null。为了避免可能的NullPointerExceptions,我们需要防范这种情况。最后,让我们强制一个简单的死锁,看看我们的系统在运行

DeadlockDetector deadlockDetector = new DeadlockDetector(new DeadlockConsoleHandler(), 5, TimeUnit.SECONDS);
deadlockDetector.start();

final Object lock1 = new Object();
final Object lock2 = new Object();

Thread thread1 = new Thread(new Runnable() {
  @Override
  public void run() {
    synchronized (lock1) {
      System.out.println("Thread1 acquired lock1");
      try {
        TimeUnit.MILLISECONDS.sleep(500);
      } catch (InterruptedException ignore) {
      }
      synchronized (lock2) {
        System.out.println("Thread1 acquired lock2");
      }
    }
  }

});
thread1.start();

Thread thread2 = new Thread(new Runnable() {
  @Override
  public void run() {
    synchronized (lock2) {
      System.out.println("Thread2 acquired lock2");
      synchronized (lock1) {
        System.out.println("Thread2 acquired lock1");
      }
    }
  }
});
thread2.start();
输出: Thread1 
获取lock1 
Thread2获取lock2 
检测到死锁!
“Thread-1”Id = 11由“Thread-0”拥有的[email protected]上的BLOCKED Id = 10 
deadlock.DeadlockTester $ 2.run(DeadlockTester.java:42) 
  java.lang.Thread.run(Thread。 java:662)
“Thread-0”Id = 10 BLOCKED在[email protected]拥有“Thread-1”Id = 11 
  deadlock.DeadlockTester $ 1.run(DeadlockTester.java:28)
  java.lang.Thread。运行(Thread.java:662)

注意:死锁检测可能是一项昂贵的操作,建议间隔至少几分钟,因为检查死锁的频率并不比这更频繁,目前还没有恢复计划 - 我们只能调试并修复错误或重启应用程序并希望它不会再次发生。

  1. Java计时器线程

Java java.util.Timer是一个实用程序类,可用于调度将来某个时间执行的线程。Java Timer类可用于计划要一次运行的任务或定期运行的任务。java.util.TimerTask是一个实现Runnable接口的抽象类,我们需要继承这个类来创建我们自己的TimerTask,它可以使用java Timer类进行调度
Java Timer类是线程安全的,多个线程可以共享一个Timer对象,而无需外部同步。 Timer类使用java.util.TaskQueue以给定的定期间隔添加任务,并且在任何时候只能有一个线程运行TimerTask, 例如,如果要创建一个每10秒运行一次的Timer,但单线程执行需要20秒,然后Timer对象将继续向队列添加任务,一旦一个线程完成,它将通知队列,另一个线程将开始执行。 Java Timer类使用Object wait和notify方法来安排任务
Timer cancel()方法用于终止计时器并丢弃任何计划任务,但它不会干扰当前正在执行的任务并让它完成。如果计时器作为守护程序线程运行,无论我们是否取消它,它将在所有用户线程完成执行后立即终止。

注意:在使用Timer调度任务时,您应该确保时间间隔超过正常的线程执行,否则任务队列大小将继续增长,最终任务将始终执行
  1. Java生产者消费者问题

java.util.concurrent.BlockingQueue是一个java队列,它支持在检索和删除元素时等待队列变为非空的操作,并在添加元素时等待队列中的空间可用
Java BlockingQueue实现是线程安全的。所有排队方法本质上都是原子的,并使用内部锁或其他形式的并发控制
Java BlockingQueue接口是java集合框架的一部分,它主要用于实现生产者消费者问题。我们不需要担心在BlockingQueue中等待生产者或对象可供消费者使用的空间,因为它由BlockingQueue的实现类处理
Java提供了几种BlockingQueue实现,如ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronousQueue等
put(E e):此方法用于将元素插入队列。如果队列已满,则等待空间可用。
E take():此方法从队列头部检索并删除元素。如果queue为空,则等待该元素可用

  1. Java线程池

Java线程池管理工作线程池,它包含一个队列,可以使任务等待执行。我们可以用ThreadPoolExecutorjava创建线程池。

ExecutorService executor = Executors.newFixedThreadPool(5);
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), threadFactory, rejectionHandler);
请注意,在初始化ThreadPoolExecutor时,我们将初始池大小保持为2,最大池大小保持为4,工作队列大小保持为2.因此,如果有4个正在运行的任务并且提交了更多任务,则工作队列将只保留其中的2个其余的将由他们处理RejectedExecutionHandlerImpl。

如果要安排任务延迟运行或定期运行,则可以使用ScheduledThreadPoolExecutor类
Java通过ScheduledThreadPoolExecutor实现ScheduledExecutorService接口的类提供调度的线程池实现。ScheduledExecutorService定义用于使用不同选项计划任务的合同方法。
ScheduledExecutorService scheduleWithFixedDelay(Runnable命令,long initialDelay,long delay,TimeUnit unit)
ScheduledExecutorService scheduleWithFixedDelay方法可用于以初始延迟启动定期执行,然后以给定的延迟执行。延迟时间是从线程完成执行的时间开始

  1. Java Callable和Future

Java Callable和Future在多线程编程中经常使用
Java Callable接口使用Generic来定义Object的返回类型。Executors类提供了在线程池中执行Java Callable的有用方法。由于可调用任务并行运行,我们必须等待返回的Object。
Java Callable任务返回java.util.concurrent.Future对象。使用Java Future对象,我们可以找出Callable任务的状态并获取返回的Object。它提供了get()方法,可以等待Callable完成然后返回结果。
Java Future提供cancel()方法来取消关联的Callable任务。有一个get()方法的重载版本,我们可以指定等待结果的时间,避免当前线程被阻塞更长时间是有用的。有isDone()和isCancelled()方法来查找关联的Callable任务的当前状态。

package com.threads;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        //return the thread name executing this callable task
        return Thread.currentThread().getName();
    }
    
    public static void main(String args[]){
        //Get ExecutorService from Executors utility class, thread pool size is 10
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //create a list to hold the Future object associated with Callable
        List<Future<String>> list = new ArrayList<Future<String>>();
        //Create MyCallable instance
        Callable<String> callable = new MyCallable();
        for(int i=0; i< 100; i++){
            //submit Callable tasks to be executed by thread pool
            Future<String> future = executor.submit(callable);
            //add Future to the list, we can get return value using Future
            list.add(future);
        }
        for(Future<String> fut : list){
            try {
                //print the return value of Future, notice the output delay in console
                // because Future.get() waits for task to get completed
                System.out.println(new Date()+ "::"+fut.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //shut down the executor service now
        executor.shutdown();
    }

}

  1. Java FutureTask示例

FutureTask是Future接口的基本具体实现,并提供异步处理。
它包含启动和取消任务的方法,以及可以返回FutureTask状态的方法,无论它是完成还是取消。
我们需要一个可调用对象来创建未来任务,然后我们可以使用Java线程池执行器来异步处理这些任务


package com.threads;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {

	private long waitTime;
	
	public MyCallable(int timeInMillis){
		this.waitTime=timeInMillis;
	}
	@Override
	public String call() throws Exception {
		Thread.sleep(waitTime);
        //return the thread name executing this callable task
        return Thread.currentThread().getName();
	}

}


package com.threads;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FutureTaskExample {

	public static void main(String[] args) {
		MyCallable callable1 = new MyCallable(1000);
		MyCallable callable2 = new MyCallable(2000);

		FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
		FutureTask<String> futureTask2 = new FutureTask<String>(callable2);

		ExecutorService executor = Executors.newFixedThreadPool(2);
		executor.execute(futureTask1);
		executor.execute(futureTask2);
		
		while (true) {
			try {
				if(futureTask1.isDone() && futureTask2.isDone()){
					System.out.println("Done");
					//shut down executor service
					executor.shutdown();
					return;
				}
				
				if(!futureTask1.isDone()){
				//wait indefinitely for future task to complete
				System.out.println("FutureTask1 output="+futureTask1.get());
				}
				
				System.out.println("Waiting for FutureTask2 to complete");
				String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
				if(s !=null){
					System.out.println("FutureTask2 output="+s);
				}
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}catch(TimeoutException e){
				//do nothing
			}
		}
		
	}

}

三、归纳总结–知识点强化

并发编程(三)初级1分钟看懂Java多线程与高并发
操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源