最近的工作中遇到了融合数据的场景,采取了多线程的方案解决了问题,今天就回顾一下JAVA的多线程编程。
public class Application extends Thread{
public Application() {
super();
}
public Application(String name) {
super(name);
}
@Override
public void run() {
System.out.println("application线程:" + Thread.currentThread().getName());
}
public static void main(String[] args){
System.out.println("开始创建一个线程类");
Application application = new Application("MyApplication");
System.out.println("main线程:" + Thread.currentThread().getName());
System.out.println("开始运行线程:");
application.start();
}
}
Thread.currentThread()
返回当前执行中的线程的引用,Thread(String name)
构造方法给线程命名。
Runnable接口只定义了一个run()方法,Thread类也实现了Runnable接口
public class Application implements Runnable{
@Override
public void run() {
System.out.println("application线程:" + Thread.currentThread().getName());
}
public static void main(String[] args){
System.out.println("开始创建一个线程类");
Application application = new Application();
System.out.println("main线程:" + Thread.currentThread().getName());
System.out.println("---创建线程---");
Thread thread = new Thread(application);
System.out.println("---开始运行线程---");
thread.start();
}
}
public class Application{
public static void main(String[] args){
System.out.println("main线程:" + Thread.currentThread().getName());
System.out.println("---创建Runnable---");
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("runnable线程:" + Thread.currentThread().getName());
}
};
Thread thread = new Thread(runnable);
System.out.println("---开始运行线程---");
thread.start();
}
}
通过Lamda表达式来定义,Lamda表达式作为Java 1.8的特性,大大简化了代码,其核心在于其对方法的推导,即对函数式编程的支持。
public class Application{
public static void main(String[] args){
System.out.println("main线程:" + Thread.currentThread().getName());
System.out.println("---创建Runnable---");
Thread thread = new Thread(
() -> {
System.out.println("runnable线程:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep了2秒");
}
);
System.out.println("---开始运行线程---");
thread.start();
}
}
thread1.join()
调用时表示thread1
的线程加入到当前线程中,join()
后的代码需要等待thread1
线程执行完毕才能执行,即join()
阻塞当前线程。join()
方法也可以设置等待时间,即阻塞时间, join(1000)表示阻塞1秒。
public class Application {
public static void main(String[] args) throws InterruptedException {
// Create Thread 1
Thread thread1 = new Thread(() -> {
System.out.println("Entered Thread 1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Exiting Thread 1");
});
Thread thread2 = new Thread(() -> {
System.out.println("Entered Thread 2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Exiting Thread 2");
});
System.out.println("Starting Thread 1");
long start = System.currentTimeMillis();
thread1.start();
System.out.println("Waiting for Thread 1 to complete");
try {
thread1.join();
System.out.println(System.currentTimeMillis()-start);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
System.out.println("Starting Thread 2");
thread2.start();
thread2.join(1000);
System.out.println(System.currentTimeMillis()-start);
}
}
Java并发包中主要定义提供了以下三个用于创建管理线程的接口:
一个仅包含了一个execute()
方法的接口,用于启动Runnable
对象指定的任务。
Executor
的子接口,增加了管理任务生命周期的功能,提供了submit()
方法,其可以接受Runnable
和Callable
对象,Callable
对象和Runnable
对象非常相似,只不过Callable
的call()
可以返回一个值。
ExecutorService
的子接口,增加了执行计划任务的功能。
除了以上接口,Executors
类提供了创建不同Executor
的工厂方法。
创建一个单线程的ExecutorService并提交任务来看一看执行效果:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Application {
public static void main(String[] args) {
System.out.println("main Thread: " + Thread.currentThread().getName());
System.out.println("创建Executor...");
ExecutorService executorService = Executors.newSingleThreadExecutor();
long start = System.currentTimeMillis();
System.out.println("提交Runnable1:");
executorService.submit(
() -> {
System.out.println("Runnable1:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Runnable1执行完:" + (System.currentTimeMillis() - start));
}
);
System.out.println("提交Runnable2:");
executorService.submit(
() -> {
System.out.println("Runnable2:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Runnable2执行完:" + (System.currentTimeMillis() - start));
}
);
System.out.println("全部提交:" + (System.currentTimeMillis() - start));
}
}
上面的代码使用Executors.newSingleThreadExecutor()
创建了一个单线程的ExecutorService
来执行任务,当提交一个任务后,如果ExecutorService
中已有一个任务在执行,那么新提交的任务将会在队列中等待,直到前一个任务释放线程后,再去执行。可以看到上述的代码执行后并没有结束,那是因为ExecutorService
还在保持监听新的任务提交,直到我们主动关闭它,有下列两个方法:
shutdown()
当这个方法被调用时,ExecutorService
将会停止接受新的任务,等待之前被提交的任务都执行完毕后终止。shutdownNow()
当这个方法被调用时,将会立刻停止接受新的任务提交,终止正在执行中的任务,并返回还未执行的任务列表。import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Application {
public static void main(String[] args) {
System.out.println("main Thread: " + Thread.currentThread().getName());
System.out.println("创建Executor...");
ExecutorService executorService = Executors.newFixedThreadPool(2);
long start = System.currentTimeMillis();
System.out.println("提交Runnable1:");
executorService.submit(
() -> {
System.out.println("Runnable1:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Runnable1执行完:" + (System.currentTimeMillis() - start));
}
);
System.out.println("提交Runnable2:");
executorService.submit(
() -> {
System.out.println("Runnable2:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Runnable2执行完:" + (System.currentTimeMillis() - start));
}
);
System.out.println("提交Runnable3:");
executorService.submit(
() -> {
System.out.println("Runnable3:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Runnable3执行完:" + (System.currentTimeMillis() - start));
}
);
executorService.shutdown();
}
}
console:
可见利用了Executors.newFixedThreadPool(2)
定义了一个包含2个线程的固定线程池,其保证了在有任务提交时,始终都能拿到线程去执行任务。当两个线程都被占用时,任务则会在队伍中等待。
Runnable
或Callable
分开的Thread集合并且管理有管理Thread集合的能力,负责了Thread的创建和执行。Executors.newFixedThreadPool(2)
方法创建的实质是ThreadPoolExecutor
,其不光是维护ThreadPool,它还维护着一个任务队列。 // 创建线程池,可见任务队列用的是LinkedBlockingQueue()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// --------------------------------------------------------
// 进入构造方法
// 这里给到了创建线程的默认工厂Executors.defaultThreadFactory()
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// --------------------------------------------------------
// 进入全参构造方法
// 在这个构造方法中并没有看到线程被创建,显然是在submit()中创建了
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
// --------------------------------------------------------
// 进入submit,忽然发现ThreadPoolExecutor并没有重写父类AbstractExecutorService中的submit方法,于是直接去看父类
// 进入AbstractExecutorService,看这个代码,貌似奥妙就全在这个execute方法里了,这个抽象类继承了Executor接口,但是并没有实现execute方法,看来又要回到ThreadPoolExecutor了
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// --------------------------------------------------------
// 进入ThreadPoolExecutor中的execute
// 这里的注释其实已经写得很明白,主要是对线程池和队列做检查,我们不管那么多,先瞅瞅addWorker()方法。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
// --------------------------------------------------------
// 进入addwork()
// 从截取的代码片段可以看到,终于找到了Thread,点进Worker的构造方法
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// --------------------------------------------------------
// 进入Worker
// 是个内部类,终于看到了工厂方法new线程的过程了
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
最后找到了这个线程集合:
最后的最后提一下Executors.newCachedThreadPool()
,它创建的还是ThreadPoolExecutor
,区别在与其默认保证线程60秒存活,六十秒中内没有任务占用它,这个线程就会被释放,在之后需要再动态创建。所以在不用的状态下,CachedThreadPool不会占用任何资源。
ScheduledExecutorService
可以用来执行一些周期化的任务,或者在一个特定时间的延迟后执行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Application {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
long start = System.currentTimeMillis();
scheduledExecutorService.schedule(
() -> {
System.out.println("延迟执行:" + Thread.currentThread().getName());
System.out.println(System.currentTimeMillis() - start);
}, 1, TimeUnit.SECONDS
);
scheduledExecutorService.scheduleAtFixedRate(
() -> {
System.out.println("周期执行:" + Thread.currentThread().getName());
System.out.println(System.currentTimeMillis() - start);
}, 0, 2, TimeUnit.SECONDS
);
}
}
console:
关于源码原理,我去大概瞅了一眼,不是太能理解。又去百度了一些原理文章,有些机制还没能了解,之后学完再做一下源码分析吧,暂时就这样了。