一、相关概念
1.1程序、进程和线程的概念:
程序:是一系列指令的集合,即指的是一段静态的代码,静态对象。
进程:是运行的程序,是CPU分配资源的最小单位。
线程:是进程的实体,是CPU调度和分派的基本单位。
1.2进程和线程的主要区别:
1.从属关系不同:进程是正在运行程序的实例,进程中包含了线程,而线程中不能包含进程(即一个进程可以拥有多个线程,而一个线程同时只能被一个进程所有)。
2.描述侧重点不同:进程是操作系统分配资源的基本单位,而线程是操作系统调度的基本单位。
3.共享资源不同:多个进程间不能共享资源,每个进程有自己的堆、栈、虚存空间(页表)、文件描述等信息,而线程可以共享进程资源文件(堆,方法区),并且有自己私有的堆、栈。
4.上下文切换速度不同:线程切换速度较快(指的是从一个线程切换到另一个线程),而进程切换较慢。
5.操纵者不同:一般情况下,进程的操纵者是操作系统,线程的操纵者是编程人员。
1.3引入线程的原因:
1.便于调度。
2.线程可以共享简称的数据和代码,启动和切换速度更快。
3.具有高并发性,可以启动多个线程执行同一个程序的进程。
4.充分利用处理器的功能。
并发:指两个或两个以上的事件在同一时间隔发生,微观上,是把事件分成若干段,使多个进程快速交替的执行。
并行:指两个或两个以上的事件在同一时刻发生,无论是微观还是宏观上,二者都是一起执行的。
1.4多进程和多线程:
多进程:每个进程互相独立,不影响主程序的稳定性,通过增加CPU可以扩充软件的性能,可以减少线程加锁/解锁的影响,极大提高性能。缺点是多进程逻辑控制复杂,需要和主程序交互,需要跨进程边界,进程之间上下文切换比线程之间上下文切换代价大。
多线程:无需跨进程,程序逻辑和控制方式简单,所有线程共享该进程的内存和变量等。缺点是每个线程与主程序共用地址空间,线程之间的同步和加锁控制比较麻烦,一个线程的崩溃会影响到整个进程或者程序的稳定性。
二、创建和启动线程。
2.1线程的创建方式
*****继承Thread类
①创建一个继承于Thread类的子类
②重写Thread类的run()【此线程要执行的操作,声明在此方法体中】
③创建当前Thread的子类的对象
④通过对象调用start():启动当前线程,调用当前线程的run()
class MyThread extends Thread{ //重写run方法,run 表示这个线程需要执行的任务 @Override public void run() { System.out.println("这个线程需要执行的任务"); } } public class ThreadDemo { public static void main(String[] args) { //创建一个 MyThread 实例 MyThread myThread = new MyThread(); //start 方法表示这个线程开始执行,注意,这里不是调用 run()方法 myThread.start(); } }
*****实现Runnable接口
①创建一个实现Runnable接口的类
②实现接口中的run()【此线程要执行的操作,声明在此方法体中】
③创建当前实现类的对象
④将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
⑤Thread类的实例调用start():
class MyRunnable implements Runnable{ //需要重写run方法 @Override public void run() { System.out.println("这个线程需要执行的任务"); } } public class ThreadDemo { public static void main(String[] args) { //创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为参数。 Thread thread = new Thread(new MyRunnable()); //start 方法表示这个线程开始执行,注意,这里不是调用 run()方法 myThread.start(); } }
*****实现Callable(jdk5.0增)
实现者定义了一个不带参数的方法,称为call。 Callable接口与Runnable接口类似,两者都是为其实例可能由另一个线程执行的类设计的。
与Runnable方式对比的好处是:
---call()可以有返回值,更加灵活。
---call()可以使用throws的方式处理异常。
---callable使用了泛型参数,可以指明具体的call()返回值类型。
缺点:如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。
public class MyCallable implements Callable<Integer> { public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; } public static void main(String[] args) throws InterruptedException和 ExecutionException { MyCallable myCallable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable); Thread thread = new Thread(futureTask); thread.start(); try { int result = futureTask.get(); System.out.println("The result is " + result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
*****使用线程池
线程池解决两个不同的问题:它们通常在执行大量异步任务时提高性能,因为减少了每个任务的调用开销,并且它们提供了一种方法来限制和管理执行任务集合时消耗的资源(包括线程)。每个ThreadPoolExecutor(阿里推荐)还维护一些基本的统计信息,比如完成任务的数量。
使用线程池的好处:
---提高了程序执行的效率。(线程早已提前创建好了)
---提高了资源的复用率。(执行完的线程并未销毁,而是可以继续执行其他的任务)
---可以设置相关的参数,对线程池中的线程的使用进行管理。
java.util.concurrent.ThreadPoolExecutor类的构造器(其一): public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ?null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }