一、进程与线程
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
线程:线程是位于进程中,负责某个独立运行资格的空间(进程是线a程分配的一块独立的空间)。
多线程:在一个进程中可以开启多个线程,让多个线程同时去完成某项任务。使用多线程的目的是为了提高程序的执行效率。
简单说就是一个程序运行以后至少得有一个进程,但是可以有n个线程
单线程程序有多个程序只能依次进行,只有当前一个程序执行结束以后才能执行下一程序。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread
对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main
方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
调用了 Runtime
类的 exit
方法,并且安全管理器允许退出操作发生。
非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run
方法之外的异常。
二、多线程
多线程程序可以允许多个程序同时进行,但在CPU在线程中做时间片的切换,CPU负责程序的执行,但是同一时间只能执行一个线程。CPU不停在多个程序之间高速切换。实现多线程的方法有三种,继承Thread类,实现Runnable接口,使用线程池。
2.1继承Thread类
package com.ahrtolia.ThreadTest;
import java.util.Random;
public class ThreadTest extends Thread{
String flag;
public ThreadTest(String flag) {
// TODO Auto-generated constructor stub
this.flag = flag;
}
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"开始工作");
Random random = new Random();
for(int i=0;i<5;i++) {
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(name+"====="+flag);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread T0 = new ThreadTest("0");
Thread T1 = new ThreadTest("1");
T0.start();
T1.start();
}
}
运行结果:
线程Thread-1开始工作
线程Thread-0开始工作
Thread-1=====1
Thread-0=====0
Thread-1=====1
调用线程需要使用start()方法而不是run方法,使用run方法只是调用方法,实际执行的还是main线程,调用start方法可以明显看到线程争抢。
new Thread的弊端如下:
1.每次new Thread新建对象性能差。
2.线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
3.缺乏更多功能,如定时执行、定期执行、线程中断。
2.2实现runnable接口
package com.ahrtolia.ThreadTest;
import java.util.Random;
public class RunnableTest implements Runnable{
int x;
public RunnableTest(int x) {
// TODO Auto-generated constructor stub
this.x = x;
}
@Override
public void run() {
// TODO Auto-generated method stub
String name = Thread.currentThread().getName();
System.out.println("线程"+x+"开始执行");
Random random = new Random();
for(int i=0;i<5;i++) {
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(name+"----------"+x);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread T0 = new Thread(new RunnableTest(1), "线程1");
Thread T1 = new Thread(new RunnableTest(2), "线程2");
T0.start();
T1.start();
}
}
执行结果如下:
线程2开始执行
线程1开始执行
线程1----------1
线程2----------2
线程1----------1
线程2----------2
线程1----------1
线程1----------1
线程1----------1
线程2----------2
线程2----------2
线程2----------2
也可以明显看线程争抢。
2.3实现Callable接口
需要实现Callable接口类MyThreadImplementCallable;
创建一个类的对象:MyThreadImplementCallable callable = new MyThreadImplementCallable(“测试”);
由Callable创建一个FutureTask对象:
FutureTask futureTask = new FutureTask(callable);
注意:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
有FutrueTask创建一个Thread对象:
Thread thread = new Thread/(Futrue Task);
启动线程:Thread.start();
获取任务线程执行结果:
FutrueTask.get();
注意:实现Callable接口的线程可以获得任务线程的执行结果;实现Runnable接口的线程无法获取任务线程执行的结果。
package com.ahrtolia.ThreadTest;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest implements Callable<String>{
String name;
public CallableTest(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"开始工作--------");
Random random = new Random();
Thread.sleep(random.nextInt(5)*100);
return name+"执行完成";
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
CallableTest callable = new CallableTest("测试");
FutureTask<String> futureTask = new FutureTask<String>(callable);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println("线程的执行结果"+result);
}
}
运行结果:
Thread-0开始工作--------
线程的执行结果测试执行完成
2.4线程池:
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
Java提供的四种线程池的好处在于:
1.重用存在的线程,减少对象创建、消亡的开销,性能佳。
2.可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
3.提供定时执行、定期执行、单线程、并发数控制等功能。
Executors创建线程池
Java中创建线程池很简单,只需要调用Executors
中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads)
,但是便捷不仅隐藏了复杂性,也为我们埋下了潜在的隐患(OOM,线程耗尽)。
线程池具体实现方法见下篇