Java并发编程:(1)进程和线程的由来、进程的创建、线程的创建

 

1 进程和线程的由来

      1 起初,为了提高一个时间段内CPU的利用率,允许多个任务程序进行切换,人们发明了进程,用进程来对应一个程序,每个进程对应一定的内存地址空间,并且只能使用它自己的内存空间,各个进程间互不干扰。并且进程保存了程序每个时刻的运行状态,这样就为进程切换提供了可能。当进程暂停时,它会保存当前进程的状态(比如进程标识、进程的使用的资源等),在下一次重新切换回来时,便根据之前保存的状态进行恢复,然后继续执行。

这就是并发:能够让操作系统从宏观上看起来同一个时间段有多个任务在执行;但在微观上,任一个具体的时刻,只有一个任务在占用CPU资源(针对单核CPU)。

    换句话说,进程让操作系统的并发成为了可能

     2 后来,当一个进程有多个任务的时候,人们为了将一个进程下的多个子任务分开执行。发明了线程,让一个线程去执行一个子任务,这样一个进程就包括了多个线程,每个线程负责一个独立的子任务

     但是要注意,一个进程虽然包括多个线程,但是这些线程是共同享有进程占有的资源和地址空间的。进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。

     换句话说,进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能。

2 创建线程的四种方法

     了解JVM的都知道,一个应用程序对应一个JVM实例或JVM进程(一般来说名字默认为java.exe或者javaw.exe)。Java采用单线程编程模型,如果不自己创建线程,一个程序默认创建一个线程,也是主线程;但是执行的时候,或许有其他线程一起并发执行,比如垃圾收集器线程。

创建一个线程,一般有四种创建方法:(源码https://github.com/TerenceJing/training

      1)继承Thread类;

      2)实现Runnable接口;

      3)使用Callable和Future创建线程

      4)使用线程池框架创建线程来执行任务

2.1 方式一:继承Thread类

此时必须重写run方法,在run方法中定义需要执行的任务;

class MyThread extends Thread{
    private static int num = 0;
     
    public MyThread(){
        num++;
    }
     
    @Override
    public void run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}

利用start()方法启动线程(同时创建了一个新的线程):

public class Test {
    public static void main(String[]args)  {
        MyThread thread = new MyThread();
        thread.start();
    }
}

注意:

1)启动创建一个线程是通过start()方法,而不是run()方法;

调用run方法只是相当于在主线程中执行run方法中定义的任务,类似于调用普通方法,不会创建一个新的线程来执行定义的任务。

2)新线程创建的时候,不会阻塞主线程的后续执行。例如创建线程A的时候,线程B可能正在执行,两者执行结果顺序和表面的线程启动顺序无关。

2.2 方式二:实现Runnable接口

实现Runnable接口也必须重写其run方法。

class MyRunnable implements Runnable{     
    public MyRunnable() {}     
    @Override
    public void run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}
public class Test {
    public static void main(String[]args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable = new MyRunnable();//定义一个子任务
        Thread thread = new Thread(runnable);//将子任务作为参数交给Thread去执行。
        thread.start();//创建一个新的线程来执行子任务。
    }
}

MyRunnable实现了Runnable接口,也就是定义了一个子任务,然后将子任务MyRunnable作为参数交由Thread去执行,通过Thread.Start()方法来创建一个新线程执行子任务

同样,调用Runnable的run方法不会创建新线程,类似于普通的方法调用。 

综合两种方式:都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

2.3 方式三:使用Callable和Future创建线程

类似于Runnable接口一样,Callable接口(也只有一个方法)也提供了一个任务执行方法call()

@FunctionalInterface
public interface Callable<V> {
    T call() throws Exception;
}

         由上面声明的接口可以知道,使用Callable方式创建线程可以返回一个指定类型的结果,但需要FutureTask类的支持,用于接收运算结果,可以使用泛型指定返回值的类型。

//匿名类方式 
FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String res = "通过实现Callable接口的call方法";
                System.out.println(res);
                return res;
            }
        });
new Thread(task, "有返回值的线程name").start();
String result = task.get();
System.out.println(result);

   注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

2.4 方式四:使用线程池框架创建线程

      第四种方式,就是可以通过线程池启动线程,当有一个Runnable任务或Callable任务时,可以使用线程池提交任务,线程池会启动空闲线程来执行任务,此处可以使用Excutor框架作为线程池,也可以自定义线程池

     2.4.1 Excutor框架作为线程池创建线程

引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。

        //第四种方式:使用线程池框架创建线程
        //4.1
        ExecutorService executorService=Executors.newCachedThreadPool();
           //当提交任务速度高于线程池中任务处理速度时,缓存线程池会不断的创建线程。
           // 适用于提交短期的异步小程序,以及负载较轻的服务器
        ExecutorService executorService1=Executors.newFixedThreadPool(5);
           //固定大小的线程池,为了满足资源管理需求而需要限制当前线程数量
        ExecutorService executorService2=Executors.newSingleThreadExecutor();
           // 单线程池,需要保证顺序执行各个任务的场景

        List<Future<String>> resultList = new ArrayList<Future<String>>();
        for (int i = 0; i < 6; i++){
            System.out.println("************* a" + i + " *************");
            //execute()执行Runnable接口任务
            executorService.execute(()-> {
                System.out.println("线程池Runnable任务:"+Thread.currentThread().getName());
            });
            executorService.execute(()-> {
                System.out.println("线程池Runnable任务:"+Thread.currentThread().getName());
            });
            //执行Callable接口任务
            Future<String> ft=executorService.submit(()-> {
                System.out.println("线程池Callable任务:"+Thread.currentThread().getName());
                return "hello";
            });
            resultList.add(ft);
        }

        //遍历任务的结果
        for (Future<String> fs : resultList){
            try{
                while(!fs.isDone());            //Future返回如果没有完成,则一直循环等待,直到Future返回完成
                System.out.println(fs.get());   //打印各个线程(任务)执行的结果
            }catch(InterruptedException e){
                e.printStackTrace();
            }catch(ExecutionException e){
                e.printStackTrace();
            }finally{
                //启动一次顺序关闭,执行以前提交的任务,但不接受新任务
                executorService.shutdown();
            }
        }

    ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

    2.4.2  自定义线程池创建线程

      自定义线程池,可以用ThreadPoolExecutor类创建(线程池详细解析参考:Java并发编程:线程池解析),它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池:

@Test
    public void threadPoolTest() {        
        //自定义一个线程池
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,5,5000,    
                         TimeUnit.MICROSECONDS,new ArrayBlockingQueue<>(10) );

        for(int i=0;i<10;i++){
            poolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("自定义线程池执行线程:"+Thread.currentThread().getName());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
        }

        poolExecutor.shutdown();
        poolExecutor.shutdown();
        System.out.println(poolExecutor.getPoolSize());
        System.out.println(poolExecutor.getQueue().size());
    }

 

向线程池提交了10个任务,其中三个在线程池中创建了线程执行,7个进入阻塞队列等待。

四种线程创建方法对比

     实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,前者执行run()方法无返回值,因此可以把这两种方式归为一种,这种实现接口的方式继承Thread类的方法之间的差别如下:

   1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

   2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

  3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

  4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

   综合比较:前三种的线程如果创建关闭频繁会消耗系统资源影响性能(前三种推荐实现接口的方式),而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池

 

创建进程

两种方式创建进程:ProcessBuilder的start方法和Runtime.exec()方法

总共涉及到5个主要的类:

首先说一个前提进程类:java.lang.Process

public abstract class Process
{    
    abstract public OutputStream getOutputStream();   //获取进程的输出流      
    abstract public InputStream getInputStream();    //获取进程的输入流 
    abstract public InputStream getErrorStream();   //获取进程的错误流 
    abstract public int waitFor() throws InterruptedException;   //让进程等待  
    abstract public int exitValue();   //获取进程的退出标志 
    abstract public void destroy();   //摧毁进程
}

方式一:ProcessBuilder的start方法

Final类:ProcessBuilder

public final class ProcessBuilder
{
    private List<String> command;
    private File directory;
    private Map<String,String> environment;
    private boolean redirectErrorStream;
 
//第一种构造器,参数作为List传入
    public ProcessBuilder(List<String> command) {
    if (command == null)
        throw new NullPointerException();
    this.command= command;
    }
 //第二种构造器,不定长参数,以字符串形式传入
    public ProcessBuilder(String... command) {
    this.command= new ArrayList<String>(command.length);
    for (String arg : command)
        this.command.add(arg);
    }
....
}

start方法中具体做了哪些事情:

上述根据命令参数以及设置的工作目录进行一些参数设定,并返回一个ProcessImpl类型的Process对象。

Final类java.lang.ProcessImpl:

final class ProcessImpl extends Process {
    static Process start(String cmdarray[],
             java.util.Map<String,String>environment,
             Stringdir,
             boolean redirectErrorStream)
    throws IOException
    {
String envblock =ProcessEnvironment.toEnvironmentBlock(environment);
 
return new ProcessImpl(cmdarray, envblock, dir,redirectErrorStream);// 创建一个ProcessImpl对象
 
    }
 ....
}

那么,如何使用ProcessBuilder创建进程:打开cmd,获取ip地址信息:

public class Test {
public static void main(String[]args) throws IOException  {
//关键步骤:将命令字符串传给ProcessBuilder的构造器
        ProcessBuilderpb = new ProcessBuilder("cmd","/c","ipconfig/all");
        Processprocess = pb.start();
        Scannerscanner = new Scanner(process.getInputStream());
         
        while(scanner.hasNextLine()){
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
}

方式二:Runtime的exec方法

Runtime,顾名思义,即运行时,表示运行时采用单利模式(进程只会运行于一个虚拟机实例当中)产生了当前进程所在的虚拟机实例。

Exec方法实现:事实上通过Runtime类的exec创建进程的话,最终还是通过ProcessBuilder类的start方法来创建的

public Process exec(String[] cmdarray, String[] envp,File dir)
   throws IOException {
   return new ProcessBuilder(cmdarray)
       .environment(envp)
       .directory(dir)
       .start();
   }
 

创建进程:

因为exec方法不支持不定长参数,必须先把命令参数拼接好再传进去

public class Test {   
 public static void main(String[]args) throws IOException  {
        Stringcmd = "cmd "+"/c "+"ipconfig/all";
        Processprocess = Runtime.getRuntime().exec(cmd);
        Scannerscanner = new Scanner(process.getInputStream());
         
        while(scanner.hasNextLine()){
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
}

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值