概述


几乎所有的操作系统都支持同时运行多个任务,通常一个任务就是一个程序,而一个 程序就是一个进程。当一个程序运行时, 内部可能包含多个顺序执行流,每个顺序执行流就是一个线程。



进程与线程



进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。当程序进入内存运行时,即为进程。



进程拥有以三个特点:


独立性:进程是系统中独立存在的实体,它可以拥有独立的资源,每一个进程都拥有自己私有的地址空间。没有进程本身的允许,用户进程不可以直接访问其它进程的地址空间。


动态性:进程与程序的区别在于进程是动态的。进程中有时间的概念,进程具有自己的生命周期和各种不同的状态。


并发性:多个进程可以在单个处理器上并发运行,互不影响。



并发性(concurrency)和并行性(parallel)是不同的概念,并行是指同一时刻,多个命令在多个处理器上同时执行;并发是指同一时刻只有一条指令在执行,但多个进程指令被快速轮换执行,使得在宏观上具有了多个进程同时执行的效果。




对于一个CPU而言,它在某个时间点只能执行一个程序。




多进程的并发策略有:共用式的多任务操作策略(WIN3.1和Mac OS9),现在操作系统大多采用效率更高的抢占式多任务操作策略(Windows NT、Windows 2000以及UNIX/Linux)等操作系统。



多线程扩展了多进程了概念,使得同一个进程可以同时并发处理多个任务。


线程(Thread)被称为轻量级线程(LightWeight Process),线程是进程的执行单元。



线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不能拥有系统资源——它与父进程的其它线程共享该进程所拥有的全部资源。




线程可以完成一定任务,可以和其它线程共享父进程的共享变量和部分环境,相互协作来完成任务。


线程是独立运行的,其不知道进程中是否还有其他线程存在。


线程的执行是抢占式的,也就是说,当前执行的线程随时可能被挂起,以便运行另一个线程。


一个线程可以创建或撤销另一个线程,一个进程中的多个线程可以并发执行。




多线程的优势


进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率。


线程共享的环境包括:进程代码段、进程的公有数据等。利用这些共享的数据,线程很容易实现相互之间的通信。



简而言之,多线程编程具有以下优点:


可以解决进程之间不能共享内存的问题。


系统创建进程需要为该进程重新分配系统资源,而创建线程代价就要小得多,因此多线程效率较高。



而Java语言内置了多线程功能的支持,从而简化了Java的多线程编程。



线程的创建和启动



Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个进程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码)。Java使用线程执行体来表示这段程序流。




继承Thread类创建线程类


使用该方法来创建并启动多线程的步骤如下:



1.定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。



2.创建Thread子类的实例,即创建了线程对象



3.调用线程对象的start()方法来启动线程



以下程序使用了三个线程:



package 线程的创建和启动;


public class FirstThread extends Thread{
private int i;
@Override
public void run()
{
for(;i<10;i++)
{
System.out.println(getName()+"  "+i);
}
}
public static void main(String []args)
{
for(int i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+"  "+i);
if(i==5)
{
FirstThread t1=new FirstThread();
FirstThread t2=new FirstThread();
t1.start();
t2.start();
}
}
}


}






运行结果如下:(由于线程的随机性所有结果很可能并不相同)



java程序 后台进程 java中进程是什么意思_java程序 后台进程



Thread.currentThread():该方法总是返回正在执行的线程对象


getName():该方法是Thread类的实例方法,用于返回当前线程名



使用继承自Thread类的子类来创建线程类时,多个线程无法共享线程类的实例变量(就比如上面的i)






实现Runnable接口创建线程类


使用该方法创建并启动多线程的流程如下:


1.定义Runnable接口的实现类,并重写该类的run()方法,run()方法的方法体同样是该线程的线程执行体。



2.创建Runnable实现类的实例,并将此实例作为Thread的target创建一个Thread对象,该Thread对象才是真正的线程对象



3.调用其start()方法启动线程



Runnable对象仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程的对象依旧是Thread实例,只是线程实例负责执行其target的run()方法。



package 线程的创建和启动;


public class SecondThread implements Runnable{
private int i;
@Override
public void run()
{
for(;i<20;i++)
{
System.out.println(Thread.currentThread().getName()+"   "+i);
if(i==20)
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
public static void main(String []args)
{

for(int i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+"  "+i);
if(i==5)
{
SecondThread st=new SecondThread();
Thread t1=new Thread(st,"线程一");
Thread t2=new Thread(st,"线程二");
t1.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}

}
}
}






输出结果为(由于线程的不确定性,结果很可能不一致,比如可能同时出现线程一0和线程二0)




java程序 后台进程 java中进程是什么意思_java程序 后台进程_02





与继承Thread类不同,通过实现Runnable接口来获得当前线程对象不能使用this,只能使用Thread.currentThread()方法



 采用Runnable接口的方式创建的多个线程可以共享线程类(实现了Runnable接口的类)的实例变量。这是因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享一个target,所以多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例变量。






使用Callable和Future创建线程


通过Runnable实现多线程其实就是Thread类将run()包装成线程执行体,但目前Java并不可以把任意方法包装成线程执行体。



从Java5开始,Java提供 Callable接口,Callable接口提供了一个call()方法可以作为线程执行体,看起来和Runnable很像,但call()方法更强大——call()方法可以有返回值、call()方法可以抛出异常



Java5提供了Future接口来代表Callable接口的call()方法的返回值,并未Future接口提供了一个FutureTask实现类,该实现类实现类Future接口,也实现了Runnable接口——可以作为Thread的target。



Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。而且Callable接口是函数式接口,因此可以使用Lambda表达式创建Callable对象




使用该方法创建并启动有返回值线程的步骤如下:


1.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且call()方法应有返回值,在创建Callable实现类的实例。



2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。



3.使用FutureTask对象作为Thread对象的target创建并启动新线程。



4.使用FutureTask对象的get()方法来获得子线程执行结束后的返回值



package 线程的创建和启动;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;


class Target implements Callable<Integer>
{
int i=0;
public Integer call()
{
for(;i<20;i++)
{
System.out.println(Thread.currentThread().getName()+"   "+i);
}
return i;
}
}
public class ThirdThread {
public static void main(String []args)
{
Target t=new Target();
FutureTask<Integer> ft=new FutureTask<>(t);
Thread thread=new Thread(ft,"新线程");
thread.start();
try
{
System.out.println(ft.get());
}
catch(Exception e)
{
e.printStackTrace();
}
}
}





java程序 后台进程 java中进程是什么意思_Thread_03


创建线程的三种方式对比


实现Runnable接口和实现Callable接口可以看为一种方法,只是实现Callable可以有返回值,可以抛出异常。



采取Runnable、Callable的优势在于——线程类只是实现了Runnable或Callable接口,还可以继承其它类;在这种方法下,多个线程可以共享一个target对象,因此非常适合多个相同线程处理同一份资源的情况,从而将CPU、代码和数据分开,形参清晰的模型,体现了面对对象的编程思想。劣势在于编程复杂度略高。



采用继承Thread类的方式创建多线程的优点在于编程简单,劣势在于无法继承其它类。



一般推荐采取实现Runnable、Callable接口的方法来创建多线程。




PS:今天上线发现自己有了第一个粉丝