并发
先说说Java代码的执行机制:java代码–》编译后成为java字节码–》字节码被类加载器加载到JVM–》JVM执行字节码–》最终转换成汇编指令在CPU执行
关于并发:
Java实现并发的方式也有多种。
1. 基本的线程机制
1.1 实现线程的基本方法:
(1) 实现Runnable接口来定义任务
通过实现Runnable接口并编写run()方法来是实现一个线程类,注意此线程没有返回值。
public class MyThread implements Runnable{
public void run(){
}
}
//使用:
MyThread myThread = new MyThread();
myThread.run();//开始执行线程
(2) 继承Thread类
将一个实现了Runnable接口的线程类作为参数传递给Thread构造器来实现线程:
Thread thread = new Thread(new MyThread());
thread.start();//开始执行线程,start()方法会自动调用run()方法。其实start()会很快的返回,因为这个start()方法是有由main这线程执行的
1.2 关键方法解析
(1) Thread.yield()方法:
这是一个静态方法,对该方法的调用表示对线程调度器的一种建议,声明:我已经执行完声明周期中最重要的部分了,此时可以把CPU切换给别的线程使用了。(简单点理解可以是降低当前线程的优先级,主动让出CPU)
1.3 使用Executor执行器(启动线程的优先选择)
Executor是在客户端和执行任务中间提供了一个间接层,帮我们管理线程对象从而简化并发编程。
我们一般使用Executor的子接口ExecutorService,ExecutorService知道如何构建恰当的上下文来执行Runnable对象。下面是一个典型的例子:
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++)
exec.execute(new LiftOff());
exec.shutdown();
}
}
这是很常见的情况,单个Executor来创建和管理所有的任务。
shutdown()方法的调用可以禁止新的任务提交给这个Executor,当前线程(示例中的main线程)将继续运行shutdown()调用之前所提交给Executor的所有任务,直到全部完成之后这个程序将尽快退出。
Executors创建的一些常用的线程池:
线程池名称 | 区别 |
---|---|
newCachedThreadPool() | 在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新的线程。 |
newFixedThreadPool(int) | 根据参数一次性预先执行代价昂贵的线程分配,但是也在初始时就限制了线程的数量。 |
newSingleThreadExecutor | 创建一个单线程执行程序,如果提交了多个任务,将会把这些任务排队,每个任务都需要等待上一个任务结束才能执行。 |
1.4 从任务中产生返回值
Runnable是执行工作的独立任务,但是它没有返回值。如果我们希望Task在完成后能够返回一个result,那么可以通过实现Callable接口实现。Callable是一种具有类型参数的泛型,它的类型是从call()(而不是run()方法)中返回的值的类型。并且使用ExecutorService.submit()方法调用它。下面是一个简单示例:
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 从任务中产生返回值;返回值类型为Callable泛型的类型
* @author LL
*/
class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id){
this.id = id;
}
//任务
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}
/**
* function: 从任务中产生返回值.并打印
* @author LL
*/
public class CallableDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
//Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结.
//这个list用来保存每个任务的返回结果
ArrayList< Future<String> > results = new ArrayList<Future<String>>();
for (int i=0; i<10; i++){
//submit()提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
//把返回的结果添加到List
Future<String> fs = exec.submit(new TaskWithResult(i));
results.add(fs);
}
for (Future<String> fs : results) {
try {
//阻塞等待结果
System.out.println(fs.get());
} catch (Exception e) {
e.printStackTrace();
} finally{
exec.shutdown();//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
}
}
}
}
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化。我们可以通过isDone()方法查看Future是否已经完成。当任务完成时我们可以通过调用get()方法来获取该结果。不过不用isDone()方法检查,get()方法将会阻塞直到获取到callable执行获取到结果。
1.5 休眠
影响任务行为的一种简单方法是调用sleep()方法,这将使任务中止执行给定的时间。现在一般用TimeUnit这个类显示指定的函数代替原来的函数:当前线程休眠
//Thread.sleep(100);
TimeUnit.MILLISECONDS.sleep(100);
1.6 优先级
我们一般在run()方法里面设置线程的优先级,如下所示
public void run(){
Thread.currentThread().setPriority(int)
......
}
不同的系统有不同等级的优先级,windows下一般只有7个优先级,而Solaris有2^31 个优先级,所以我们一把只设置三个优先级:Thread类下的静态常量
- static int MAX_PRIORITY 线程可以具有的最高优先级。
- static int MIN_PRIORITY 线程可以具有的最低优先级。
- static int NORM_PRIORITY 分配给线程的默认优先级。
1.7 后台线程
后台线程:指程序运行时在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。故当程序中所有的非后台线程结束时,程序也就终止了同时会杀死进程中所有的后台线程。
1)设置线程为后台线程的方法:
Thread t = new Thread(new LiftOff());
t.setDaemon(true);//设置为后台线程
1.8 加入一个线程 join()方法
一个线程A可以在另外的一个线程B之上(一般是run()方法里面)调用join方法,其结果是等待一段时间直到后一个线程结束才继续执行。比如:
- 若在线程t上(t的run()方法中)调用线程s的s.join()方法(s一般作为t的构造器参数传入),则线程t将别挂起,直到线程s结束为止,t才可恢复继续执行。
- 若在线程t上(t的run()方法中)调用t.join(),则当前线程会被挂起,直到线程t结束。