多线程应用编程
基础概念
- 程序是为了完成某个特定任务,使用某种计算机语言编写的一组指令的有序集合
- 进程是具有一定独立功能的程序的运行过程,是系统进行资源分配和任务调度的一个独立单位
- 线程是进程中的一个独立执行线索,是CPU调度和分配的基本单位,自己基本上不拥有系统资源
进程
每个进程都有独立的代码和数据空间,进程切换成本较高,一个进程中可以包含1-n个线程,进程是资源 分配的最小单位
同一个进程中的线程可能会有共享代码,可以共享进程的数据空间,每个线程由独立的运行栈和程序计 数器,线程切换的开销很小,线程是CPU调用的最小单位
启动进程
进程和线程一样分为5个阶段:创建、就绪、运行、阻塞和终止
cmd /c 启动运行名称后会关闭关闭窗口
启动方法1
ProcessBuilder builder=new ProcessBuilder("cmd","/c","ipconfig/all");//构建进程的对象
Process process=builder.start(); //启动进程
//获取ipconfig/all命令的执行结果
BufferedReader reader=new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));//用于获取ipconfig/all进程的执行结果
String tmp=null;
while((tmp=reader.readLine())!=null)
System.out.println(tmp);
启动方法2
String cmdStr="cmd /c ipconfig/all";
Process process=Runtime.getRuntime().exec(cmdStr);
BufferedReader reader=new BufferedReader(new
InputStreamReader(process.getInputStream(),"GBK"));//用于获取ipconfig/all进程的执行结果
String tmp=null;
while((tmp=reader.readLine())!=null)
System.out.println(tmp);
应用
考试系统中生成java代码文件并编译运行
//生成T1.java文件
File f=new File("T1.java"); //这个类名称也可以通过用户输入查找到
if(f.exists())
f.delete(); //如果T1.java已经存在则删除旧有文件
PrintWriter out=new PrintWriter(new FileWriter(f));
out.println("public class T1 {");
out.println("public static void main(String[] args)throws Exception {");
out.println("System.out.println(\"Hello java!\");");
out.println("}}");
out.close();
//编译T1.java,对应的命令为javac T1.java
Process process=Runtime.getRuntime().exec("cmd /c javac T1.java");
boolean runnable=true;
//process.getInputStream()获取获取所执行进程的输出信息,注意不是报错信息。如果需要获取报错信息则应该使用process.getErrorStream()。注意javac编译器编译通过实际上是没有响应信
息的
BufferedReader br=new BufferedReader(new
InputStreamReader(process.getErrorStream()));
while(true){
String tmp=br.readLine(); //整行读取javac编译的报错信息
if(tmp==null)
break; //如果没有报错信息则直接退出循环
System.out.println(tmp);
runnable=false; //告知系统编译失败
}
//如果编译通过则调用解释器java执行T1.class
if(runnable){
process=Runtime.getRuntime().exec("cmd /c java T1");
br=new BufferedReader(new InputStreamReader(process.getInputStream()));
//输出执行结果
while(true) {
String tmp=br.readLine();
if(tmp==null)
break;
System.out.println(tmp);
}
}
进程三大特征
- 独立性:进程是一个能够独立运行的基本单位,是系统资源分配和调度的独立单位
- 动态性:进程的实质就是程序的一次执行过程,动态的产生、动态的消亡
- 并发性:任何进程都可以同其它进程一起并发执行
- 异步性
- 结构特征:进程由程序、数据和进程控制块三部分构成
僵尸进程和孤儿进程
僵尸进程就是当子进程比父进程先结束,但是父进程没有回收子进程,并没有释放子进程所占用的资 源。
- 父进程先退出,子进程会给init进程接管,子进程退出后init会回收子进程所占用的相关资源
- 是对系统资源的浪费,测试人员有业务发现,需要处理解决
孤儿进程就是当父进程退出,而多个子进程还在运行
- 孤儿进程会被init进程管理,并由init进程对其进行状态收集工作
- 没有什么危害
并行与并发
并行是指多个CPU或者多台机器同时执行一段处理逻辑,是真正的同时执行
并发是指通过CPU调度算法,使用户看上去似乎是在同时执行,实际从CPU操作层面上不是真正的同时 执行
线程
线程是比进程更小的执行单位
- 线程:轻量级进程LWP,系统负担小,主要是CPU分配
- 线程不能独立存在,必须存在于进程中
- 每个线程也都有它产生、存在和消亡的过程,也是一个动态的概念
- 一个线程有它自己的入口和出口以及一个顺序执行的代码序列
为什么使用线程
- 减轻编写交互频繁、涉及面多的程序的困难
- 改善应用的吞吐量
- 有多个处理器的同时可以并发/并行运行不同的线程
主线程
线程是进程中的一个实体,用来描述进程的执行,负责执行包括在进程内部地址空间中的代码 创建一个进程时,它的第一个线程称为主线程,是由系统自动生成的
- 主线程是产生其它子线程的线程
- 通常都是最后执行完成的,因为还需要它来执行各种关闭动作。
public class Test1{
public static void main(String[] args)throws Exception{ //当java程序运行时,首先创建一个运行main方法的线程---主线程
System.out.println(Thread.currentThread().getName());//获取运行main方
法的线程名称
//在主线程内容可以启动子线程
Thread t=new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
t.start();//启动线程t的运行
System.in.read(); //阻塞当前main线程
}
}
进程中线程之间的关系
一个进程中的线程之间没有父子关系之分,都是平级关系,所有线程都是一样的,一个退出不会影响另 外一个。但是在主线程中执行完毕可以使用System.exit(int)强制结束当前进程,exit方法会使整个进程 停止,那么所有的线程都会自动退出
进程 vs 线程
- 一个线程只能属于一个进程,而一个进程可以包含多个线程,至少有一个线程(主线程)
- 资源调度和分配给进程,同一进程中的所有线程共享该进程的所有资源
- 线程在执行过程中,共享数据一般都需要进行协作同步
- CPU分给线程,真正在处理机上运行的是线程
- 线程是指在进程内部的一个执行单元,也是进程内的可调度的实体,两者都是动态的概念
进程和线程最大的区别在于:进程是由操作系统来控制的,而线程是由进程来控制
线程本身的数据通常只有寄存器数据以及当前程序在运行时使用的堆栈,所以线程的切换比进程切换的 成本要小的多。
多个进程的内部数据和状态是完全独立的,而同一个进程内的多线程共享一块内存空间和一组系统资 源,有可能互相影响。
多线程
多线程是指同时存在几个执行体,按几个不同的执行线索共同工作的情况
- 多线程实现单个进程中的并发计算,JDK1.8开始提供了针对并行支持
- 各个线程间共享进程空间中的数据,并可以利用共享单元实现数据交换、实时通信与必要的同步操 作 多线程的程序可以更好的表述和解决现实世界中的具体问题,是计算机应用开发和程序设计的一个必然 趋势
优点:解决了多部分同时运行的问题
缺点:线程太多会有效率反而降低的问题,一般计算密集型应用不适合多线程
java与多线程
Java语言内置了对多线程的支持,可以很方便地开发出具备多线程功能,同时处理多个任务的应用。
每个Java应用程序都有一个隐藏的主线程
- Application main方法
- Applet小应用程序,主线程指挥浏览器加载并执行java小程序
线程的4种实现方法
继承Thread类
Thread类本质上就是一个实现了Runnable接口的实例,代表一个线程对象
public class Thread implements Runnable{
public void run() {
if (target != null) {
target.run();
}
}
}
//定义线程最简单的方法就是继承Thread类,需要覆盖定义run方法即可
启动线程的唯一方法就是通过Thread类对象的start()实例方法,不能直接调用run()方法。start方法本身 是一个native方法,用于启动一个新线程,并指定run()方法。
public class LeftThread extends Thread{
public void run(){
for(int i=0;i<50;i++){
System.out.println("画条龙...."+i);
//停止运行,阻塞300ms
try{
this.sleep(300);
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
//可以在主线程种启动这个线程
Thread t1=new LeftThread();
t1.start();//可以启动线程,可以通过Thread.currentThread().getName()获取线程名称匿名内部类的定义方法
new Thread(){
public void run(){....}
}.start();
t1.run();//没有语法错误,但是会发现输出的线程名称为main,不是其它线程,而是当前的main主线程。不是多线程
问题: 最大的限制实际上就是java的单根继承体系,这种方式基本不用
实现Runnable接口
@FunctionalInterface //注解用于声明当前接口是一个函数式接口,可以使用lambda表达式替代
匿名内部类的写法
public interface Runnable {
public abstract void run();
}
独立类实现接口
public class RightThread implements Runnable{
public void run(){
for(int i=0;i<50;i++){
System.out.println("画彩虹...."+i);
}
}
}
//main方法调用
Thread t1=new Thread(new RightThread());
t1.start();
匿名内部类
Thread t1=new Thread(new Runnable(){
public void run(){
for(int i=0;i<50;i++){
System.out.println("画彩虹...."+i);
}
}
});
t1.start();
lambda表达式,要求接口必须是函数式接口
Thread t1=new Thread(()->{
for(int i=0;i<50;i++){
System.out.println("画彩虹...."+i);
}
});
t1.start();
使用Callable和Future接口创建线程
创建Callable接口的实现类,并实现call()方法
使用FutureTask类来包装Callable接口实现类的对象,并且以FutureTask对象作为Thread对象的target 来创建线程
接口定义
Callable接口用于定义线程的执行逻辑。java.util.concurrent.Callable【juc】
@FunctionalInterface //属于函数式接口,所以可以直接使用lambda表达式进行定义
public interface Callable<V> { //<>写法叫做泛型,用于定义返回的数据类型
V call() throws Exception; //call方法就是线程的具体执行逻辑,有返回值
}
Future接口用于接收Callable接口中call方法的执行结果,并提供测试Callable执行情况 java.util.concurrent.Future
public interface Future<V> { //用于获取Callable接口中call方法的执行结果
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException,ExecutionException, TimeoutException;
}
FutureTask实现类
系统提供了一个Future接口的实现类FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Callable<V> callable) {
if (callable == null) throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null,Thread.currentThread()))
return;
try {
Callable<V> c = callable; //获取属性上的callable对象
if (c != null && state == NEW) { //判定是否为new状态
V result;
boolean ran;
try {
result = c.call(); //调用call方法,并获取返回值
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
public interface RunnableFuture<V> extends Runnable, Future<V> {... ...}
具体使用
1、定义Callable接口的实现类
public class MyCallable implements Callable<Integer> {
int begin=0;
int end=0;
public MyCallable(int begin, int end){
this.begin=begin;
this.end=end;
}
public Integer call() throws Exception{
int res=0;
for(int i=begin; i<=end; i++){
res+=i;
}
System.out.println(Thread.currentThread()+":"+res);
return res;
}
}
2、调用
FutureTask[] arr=new FutureTask[10];
for(int i=0;i<10;i++){
int begin=i*100+1;
int end=(i+1)*100;
arr[i]=new FutureTask<>(new MyCallable(begin,end));
Thread t=new Thread(arr[i]);
t.start();
}
//获取最终计算结果
int res=0;
for(int i=0;i<arr.length;i++)
res+=((Integer)arr[i].get()); //通过FutureTask提供的get方法获取Callable实现类中call方法的执行结果
System.out.println("1+2+3+...+1000="+res);
使用线程池创建线程
java提供了ExecutorService、Callable和Future实现等工具以简化线程池的使用,线程池的具体实现实 际上是依赖于ThreadPoolExecutor
//计算1+2+...+1000
public class Test1{
public static void main(String[] args) throws Exception {
//创建一个固定大小的线程池,经常用于实现使用少量的线程以应对短时间的波峰请求
ExecutorService es=Executors.newFixedThreadPool(3);
//为了获取线程的执行结果所以使用Callable接口定义线程的执行逻辑
Future[] fs=new Future[10];
for(int i=0;i<fs.length;i++){
int begin=i*100+1;
int end=(i+1)*100;
Callable<Integer> caller=new MyCallable(begin,end);
//提交工作任务,使用线程池执行任务并获取对应的Future对象
fs[i]=es.submit(caller);
}
//获取最终计算结果
int res=0;
for(int i=0;i<fs.length;i++)
res+=((Integer)fs[i].get()); //通过FutureTask提供的get方法获取Callable实现类中call方法的执行结果
es.shutdown();//关闭线程池
System.out.println("1+2+3+...+1000="+res);
}
}
Callable接口的实现类
public class MyCallable implements Callable<Integer> {
int begin = 0;
int end = 0;
public MyCallable(int begin, int end) {
this.begin = begin;
this.end = end;
}
public Integer call() throws Exception {
int res = 0;
for (int i = begin; i <= end; i++) {
res += i;
}
System.out.println(Thread.currentThread() + ":" + res);
return res;
}
}