进程 & 线程
进程:是系统进行资源分配的和调度的一个独立单位。举例:将公司的一个部门看做一个进程,公司分配项目奖金的时候就是以部门为基本单位分配1万块钱,部门下面的十几我的就分这1万块钱,这时就能够将部门中的每一个人看做是一个线程。这就是进程与线程之间的关系。java
线程:线程是进程的执行单元,一个进程能够有多个线程。编程
进程间能够并发,线程间也能够并发。数组
什么是并发,什么是并行?????缓存
并发(concurrency)和并行(parallellism)是: 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。 解释二:并行是在不一样实体上的多个事件,并发是在同一实体上的多个事件。 解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群 因此并发编程的目标是充分的利用处理器的每个核,以达到最高的处理性能。-- 并发编程网安全
所以,多线程编程解决的是并发问题,并行的问题能够经过分布式部署来解决。???多线程
建立线程的3种方式
继承Thread类
步骤:
一、 定义Thread类的子类,并重写run()方法。
二、 建立Thread子类的实现
三、 调用线程对象的start()方法启动线程并发
代码片断:dom
// 经过继承Thread类来建立线程类 public class FirstThread extends Thread { // 重写run方法,run方法的方法体就是线程执行体 public void run() { } public static void main(String[] args) { // 建立、并启动第一条线程 new FirstThread().start(); // 建立、并启动第二条线程 new FirstThread().start(); } }
实现Runnable接口
步骤:
一、定义Runnable接口的实现类,并重写该接口的run()方法
二、建立该实现类的实例,并以此实例做为Thread的target来建立Thread对象
三、调用该线程对象的start()方法来启动线程分布式
代码片断:ide
// 经过实现Runnable接口来建立线程类 public class SecondThread implements Runnable { // run方法一样是线程执行体 public void run() { } public static void main(String[] args) { SecondThread st = new SecondThread(); // ① // 经过new Thread(target , name)方法建立新线程 new Thread(st , "新线程1").start(); new Thread(st , "新线程2").start(); } }
Callable和Future
步骤:
一、建立Callable接口实现类,并实现call()方法
二、使用FutureTask类来包装Callable对象
三、使用FutureTask对象做为Thread对象的target建立并启动线程
四、经过FutureTask对象的get()方法获取线程的返回值
代码片断:
@Test public void test() throws Exception { FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() { public Integer call() throws Exception { return 1; } }); new Thread(task, "新的线程1").start(); Integer i = task.get(); System.out.println("i:" + i); }
线程生命周期
线程的生命周期有:新建、就绪、运行、阻塞、死亡。
线程对象调用start()方法后就处于就绪状态。
线程生命周期的状态转换图以下:
总结:
- wait(),进入阻塞状态,释放同步监视器
- sleep(),进入阻塞状态,给其它进程(无论线程优先级)执行的机会,不会释放同步监视器
- join(),调用线程进入阻塞状态,直到加入的线程执行完成为止
- yield(),不进入阻塞状态,给其它线程(只给优先级高)执行的机会,不会释放同步监视器
控制线程
join
join:让一个线程等待另外一个线程完成。
setDaemon(true)
设置为后台线程,调用线程实例自己的setDaemon()方法:
MyThread myThread = new MyThread(); myThread.setDaemon(true);
sleep
线程睡眠,并进入阻塞状态。
调用线程Thread类的静态sleep()方法,而不是某个线程的实例方法:
Thread.sleep(1000);
yield
线程暂停,但不进入阻塞状态。
调用线程Thread类的静态yield()方法,而不是某个线程的实例方法:
Thread.yield();
sleep()与yield()区别
- sleep()与yield()都会让当前线程暂停,sleep()会给其它线程执行的机会,不理会其它线程的优先级;但yield()只给优先级相同或更高的线程执行的机会。
- sleep()会让线程进入阻塞状态,yield()则不会进入阻塞状态。
线程优先级
priority,1-10,数值越大优先级越高:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
myThread.setPriority(Thread.MAX_PRIORITY);
注:servlet中的 load-on-startup 参数,值越大优先级越低
线程同步
传统线程同步
同步方法
同步一个易发生线程安全的方法,这里的锁就是this,即当前类对象:
// 提供一个线程安全draw()方法来完成取钱操做 public synchronized void draw(double drawAmount) { }
同步代码块
同步一个易发生线程安全的字段,这里的锁就是此可能发生线程安全的字段:
public class DrawThread extends Thread { // 模拟用户帐户 private Account account; // 当多条线程修改同一个共享数据时,将涉及数据安全问题。 public void run() { // 使用account做为同步监视器,任何线程进入下面同步代码块以前, // 必须先得到对account帐户的锁定——其余线程没法得到锁,也就没法修改它 // 这种作法符合:“加锁 → 修改 → 释放锁”的逻辑 synchronized (account) { } // 同步代码块结束,该线程释放同步锁 } }
什么时候释放同步监视器
- wait()方法会释放同步监视器
- Thread.sleep(),Thread.yield()不会释放同步监视器
同步锁(Lock)
java5开始提供了同步锁,经常使用的同步锁是ReentrantLock。
同步锁继承关系以下:
- Lock |-ReentrantLock - ReadWriteLock |-ReentrantReadWriteLock
同步锁使用代码片断:
import java.util.concurrent.locks.*; public class Account { // 定义锁对象 private final ReentrantLock lock = new ReentrantLock(); // 提供一个线程安全draw()方法来完成取钱操做 public void draw(double drawAmount) { // 加锁 lock.lock(); try { } finally { // 修改完成,释放锁 lock.unlock(); } } }
ThreadLocal
原理:为每个使用该变量的线程都提供一个变量值的副本,使每个线程均可以独立改变本身的副本,而不会和其余线程的副本冲突,就好像每个线程都彻底拥有该变量同样。代码内部使用ThreadLocal.ThreadLocalMap来存储变量的键和值,也就是内部使用一个相似Map的结构来存储name和name对应的值。
ThreadLocal只提供三个public方法:
- T get():返回此线程局部变量中当前线程副本中的值。
- void remove():删除此线程局部变量中当前线程副本中的值。
- void set(T value):设置此线程局部变量中当前线程副本中的值。
示例:
class Acount { private ThreadLocal<String> name = new ThreadLocal<>(); public Acount(String str){ this.name.set(str); } public String getName(){ return name.get(); } public void setName(String str){ this.name.set(str); } //...... }
线程间通讯
传统线程间通讯
适用条件:若是程序中使用的是同步代码块、同步方法来控制线程安全,则线程间的通讯是使用Object类的 wait(),notify(),notifyAll()控制线程间通讯。
public class Account { // 封装帐户编号、帐户余额的两个成员变量 private String accountNo; private double balance; // 标识帐户中是否已有存款的旗标 private boolean flag = false; public Account(){} // 构造器 public Account(String accountNo , double balance) { this.accountNo = accountNo; this.balance = balance; } // accountNo的setter和getter方法 public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public String getAccountNo() { return this.accountNo; } // 所以帐户余额不容许随便修改,因此只为balance提供getter方法, public double getBalance() { return this.balance; } public synchronized void draw(double drawAmount) { try { // 若是flag为假,代表帐户中尚未人存钱进去,取钱方法阻塞 if (!flag) { wait(); } else { // 执行取钱 System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount); balance -= drawAmount; System.out.println("帐户余额为:" + balance); // 将标识帐户是否已有存款的旗标设为false。 flag = false; // 唤醒其余线程 notifyAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } public synchronized void deposit(double depositAmount) { try { // 若是flag为真,代表帐户中已有人存钱进去,则存钱方法阻塞 if (flag) //① { wait(); } else { // 执行存款 System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount); balance += depositAmount; System.out.println("帐户余额为:" + balance); // 将表示帐户是否已有存款的旗标设为true flag = true; // 唤醒其余线程 notifyAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } }
使用Condition控制线程间通讯
适用条件:若是程序中使用的是同步锁(Lock)来控制线程安全,则线程间通讯是使用Condition来控制线程间通讯。
获取Condiion实例代码片断:经过锁去获取
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();
Condition类提供以下方法以实现线程间通讯:
- await()
- signal(),唤醒单个线程
- signalAll(),唤醒此Lock上的全部线程
示例:
import java.util.concurrent.*; import java.util.concurrent.locks.*; public class Account { // 显式定义Lock对象 private final Lock lock = new ReentrantLock(); // 得到指定Lock对象对应的Condition private final Condition cond = lock.newCondition(); public void draw(double drawAmount) { // 加锁 lock.lock(); try { if () { } else { // 唤醒其余线程 cond.signalAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } // 使用finally块来释放锁 finally { lock.unlock(); } } }
使用阻塞队列(BockingQueue)控制线程间通讯
todo...
线程组与异常处理
todo...
线程池
普通线程池
java 5 新增了一个 Executors 工厂类来产生线程池,经常使用如下几个静态方法:
- ExecutorService newCachedThreadPool():系统根据须要建立一个具备缓存功能的线程池。
- ExecutorService newFixedThreadPool(int nThreads):建立一个可重用的、固定线程数的线程池。
- ExecutorService newSingleThreadExector():建立一个只有单个线程的线程池。
- ScheduledExecutorService newScheduledThreadPool(int corePoolSize):建立指定线程数的线程池,它能够指定延迟后执行线程任务。
- ScheduledExecutorService newSingleThreadScheduledExecutor():建立一个只有一个线程和线程池,它能够在指定延迟后执行线程任务。
- ExecutorService newWorkStealingPool(int parallelism):建立持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减小竞争。(java 8)
- ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。若是当前机器有4个CPU,则目标并行级别被设置为4,也就是至关于前一个方法传入4做为参数。(java 8)
ExecutorService 表明尽快执行线程的线程池,程序只需把一个 Runnable 对象或 Callable 对象提交给指定的线程池便可。
ExecutorService 经常使用如下方法:
- Future<?> submit(Runnable task):将一个线程任务提交给线程池,Future表明返回值。
- <T> Future<T> submit(Runnable task,T result):同上,其中result显式指定线程执行执行结束后返回的值。
- <T> Future<T> submit(Callable<T> task):将一个Calable对象提交给指定的线程池,Future表明返回值。
线程池使用步骤:
一、调用Executors类的静态工厂方法建立一个ExecutorService对象。
二、建立Runnable实现类或Callable实现类的实例。
三、调用ExecutorService对象的submit()方法提交Runnable或Callable实例。
四、调用ExecutorService对象的shutdown()关闭线程池。
示例代码片断:
import java.util.concurrent.*; public class ThreadPoolTest { public static void main(String[] args) throws Exception { // 建立足够的线程来支持4个CPU并行的线程池 // 建立一个具备固定线程数(6)的线程池 ExecutorService pool = Executors.newFixedThreadPool(6); // 使用Lambda表达式建立Runnable对象 Runnable target = () -> { for (int i = 0; i < 100 ; i++ ) { System.out.println(Thread.currentThread().getName() + "的i值为:" + i); } }; // 向线程池中提交两个线程 pool.submit(target); pool.submit(target); // 关闭线程池 pool.shutdown(); } }
ForkJoinPool
jdk 7 提供了ForkJoinPool来支持将一个任务拆分红多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。
java.util.concurrent.Executor |-ExecutorService |-AbstractExecutorService |-ForkJoinPool
建立ForkJoinPool实例后,就能够调用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法来提交任务。
其中ForkJoinTask有两个抽象子类:
java.util.concurrent.Future |-ForkJoinTask 表示一个可执行、合并的任务 |-RecursiveAction 表示没有返回值的任务 |-RecursiveTask 表示有返回值的任务
完整示例:
import java.util.concurrent.*; // 继承RecursiveAction来实现"可分解"的任务 class PrintTask extends RecursiveAction { // 每一个“小任务”只最多只打印50个数 private static final int THRESHOLD = 50; private int start; private int end; // 打印从start到end的任务 public PrintTask(int start, int end) { this.start = start; this.end = end; } @Override protected void compute() { // 当end与start之间的差小于THRESHOLD时,开始打印 if(end - start < THRESHOLD) { for (int i = start ; i < end ; i++ ) { System.out.println(Thread.currentThread().getName() + "的i值:" + i); } } else { // 若是当end与start之间的差大于THRESHOLD时,即要打印的数超过50个 // 将大任务分解成两个小任务。 int middle = (start + end) / 2; PrintTask left = new PrintTask(start, middle); PrintTask right = new PrintTask(middle, end); // 并行执行两个“小任务” left.fork(); right.fork(); } } } public class ForkJoinPoolTest { public static void main(String[] args) throws Exception { ForkJoinPool pool = new ForkJoinPool(); // 提交可分解的PrintTask任务 pool.submit(new PrintTask(0 , 300)); pool.awaitTermination(2, TimeUnit.SECONDS); // 关闭线程池 pool.shutdown(); } }
import java.util.concurrent.*; import java.util.*; // 继承RecursiveTask来实现"可分解"的任务 class CalTask extends RecursiveTask<Integer> { // 每一个“小任务”只最多只累加20个数 private static final int THRESHOLD = 20; private int arr[]; private int start; private int end; // 累加从start到end的数组元素 public CalTask(int[] arr , int start, int end) { this.arr = arr; this.start = start; this.end = end; } @Override protected Integer compute() { int sum = 0; // 当end与start之间的差小于THRESHOLD时,开始进行实际累加 if(end - start < THRESHOLD) { for (int i = start ; i < end ; i++ ) { sum += arr[i]; } return sum; } else { // 若是当end与start之间的差大于THRESHOLD时,即要累加的数超过20个时 // 将大任务分解成两个小任务。 int middle = (start + end) / 2; CalTask left = new CalTask(arr , start, middle); CalTask right = new CalTask(arr , middle, end); // 并行执行两个“小任务” left.fork(); right.fork(); // 把两个“小任务”累加的结果合并起来 return left.join() + right.join(); // ① } } } public class Sum { public static void main(String[] args) throws Exception { int[] arr = new int[100]; Random rand = new Random(); int total = 0; // 初始化100个数字元素 for (int i = 0 , len = arr.length; i < len ; i++ ) { int tmp = rand.nextInt(20); // 对数组元素赋值,并将数组元素的值添加到sum总和中。 total += (arr[i] = tmp); } System.out.println(total); // 建立一个通用池 ForkJoinPool pool = ForkJoinPool.commonPool(); // 提交可分解的CalTask任务 Future<Integer> future = pool.submit(new CalTask(arr , 0 , arr.length)); System.out.println(future.get()); // 关闭线程池 pool.shutdown(); } }