所有运行中的任务通常对应一个进程(Process),通常当一个程序进入内存运行时,就会变成一个进程。进程时处于运行过程中的程序,具有一定的独立功能。
进程的特性: 1、独立性:进程时系统中独立存在的实体,拥有自己的独立资源,每一个进程都拥有自己的私有地址空间。一个用户进程不可以直接访问其他进程的地址空间 2、动态性:程序时一个静态的指令集合,进程时一个正在系统中活动的指令集合。 3、多个进程可以再处理器上并发执行,多个进程之间不会互相影响 * expand > 并发性和并行性: 并行是指再同一时刻有多条指令在处理器上同时执行,并发指再同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行 线程同一个进程可以同时并发处理多个任务。线程也被称为轻量级的进程。线程在操作系统中是独立的、并发的执行流。对于绝大多数应用来说,一个应用程序通常仅要求有一个主线程。可以有多个线程的执行流。
线程的特性
1、线程可以与其他线程共享父进程中的共享变量及部分环境。 2、线程时独立运行的,线程的执行时抢占式的
线程的优势
进程之间不能共享内存,线程之间可以共享内存系统创建线程所需要重新分配的系统资源要小的多java语言内置流多线程的支持不是作为底层操作系统的调度方式,简化流编程从jdk 1.5开始 java提供了Callable接口,该接口提供了一个call()方法可以作为线程的执行体
call()方法比run()方法强大,call()可以有返回值call()方法可以抛出异常Callable接口有泛型的限制,Callable接口中的泛型形参类型与call()方法返回值类型相同
当一个线程被创建后,它既不是一启动就处于运行状态,也不是一直处于执行状态。在线程的生命周期中,线程要经过 新建(new)--->就绪(Runnable)--->运行(Running)--->阻塞(Blocked)--->死亡(dead) 一些状态。当线程启动后,线程并不能一直占有着cpu资源。cpu会在多条线程之间切换。线程也就会在多次执行、阻塞之间切换
处于就绪状态的线程获得了CPU资源,开始执行run()方法的线程执行体,该线程处于运行状态,在cpu调度中,当线程数大于处理器数时,依然会存在多个线程在同一个cpu上轮换的现象
一个线程开始运行后,就不可能一直处于运行状态,线程在运行过程中需要被中断,目的时让其他的线程获得执行的机会。线程的调度取决于底层的策略。
对于抢占式策略的系统来说,系统会给每一个可执行的线程一个时间段来处理任务。当时间段用完后,系统就会剥夺该线程所占用的资源。让其它线程获得执行的机会。对于协作式调度策略的cpu中只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源当正在执行的线程被阻塞之后,其他线程获得执行的机会,被阻塞的线程会在合适的时候重新进入就绪状态。被阻塞的线程阻塞状态解除后,需要重新等待线程调度器再次调度它。
线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态的转换不受程序的控制
线程的状态转换图
新建 就绪 运行 阻塞 死亡线程以如下的方式结束,结束后就会死亡
run()或call()方法执行完成,线程正常结束线程抛出一个未捕获的Exception或Error直接调用线程的stop()方法 为了测试某个线程是否已经死亡,可以调用线程对象的isAlive()方法。当线程处于就绪、运行、阻塞状态时会返回true,处于新建、死亡状态时会返回falseThread提供了一个线程等待另一个线程完成的方法——join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞。直到被join()方法加入的join线程执行完为止
join()重载形式
join() 等待被join的线程执行完成join(long millis)等待被join的线程的时间最长为millis毫秒,如果在millis毫秒内被join的线程还没有执行结束,则不在等待join(long millis,int nanos)等待被join的线程的时间最长为millis毫秒加nanos毫微秒 /** * Created by D9ing on 2016/11/14. * 线程控制join */ public class JoinThread { public static void main(String[] args) { //创建一个开始线程 new JoinDemo("新的线程").start(); TODO: 主线程执行 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "执行变量" + i); // TODO: join if (i == 10) { JoinDemo joinThread = new JoinDemo("被join的线程"); try { joinThread.start(); joinThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } /** * 线程 */ class JoinDemo extends Thread { public JoinDemo(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + "执行变量" + i); } } }使用方式
调用Thread对象的setDarmon(true)可将指定线程设置成后台线程。当所有的前台线程死亡时,后台线程也随之死亡。 /** * Created by D9ing on 2016/11/14. * 后台线程 */ public class DaemonThreadDemo { public static void main(String[] args) { // TODO: 创建后台线程 DaemonThread daemon = new DaemonThread(); // TODO: 设置后台线程 daemon.setDaemon(true); daemon.start(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "***" + i); } System.out.println("前台线程结束!!!"); } } class DaemonThread extends Thread { @Override public void run() { for (int i = 0; i < 10000; i++) { System.out.println(this.getName() + "---" + i); } System.out.println("后台线程结束!!!"); } }Thread提供 isDarmon()方法,用于判断指定的线程是否为后台线程
关于线程是否是前台线程和后台线程,前台线程创建的子线程为前台线程,后台线程创建的子线程为后台线程
由于所有的前台线程死亡后,JVM会通知后台线程死亡,但接收到死亡通知后需要一定的时间,后台线程设置必须在start()之前调用
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态。可以通过调用Thread类的静态sleep()来实现
sleep()重载
sleep(long millis):让当前正在执行的线程暂停millis秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响sleep(long millis,int nanos)当前执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间内,该线程不会获得执行的机会。即使系统中没有可执行的线程,也不会执行sleep线程
sleep yield的区别
sleep()暂停当前线程后,会给其他线程执行的机会,不会考虑线程的优先级。但yield()只会给优先级相同,或者优先级更高的线程执行的机会
sleep()会将线程转入阻塞状态。直到经过阻塞时间才会转入就绪状态。而yield不会将线程转入阻塞状态。它只是强制当前线程进入就绪状态。
sleep()方法声明抛出InterruptedException异常,所以调用sleep()要么捕获该异常要么声明抛出异常。
sleep()比yield()的可移植性更好
线程具有一定的优先级,优先级高的线程获得较多的执行机会,优先级低的获得较少的执行机会
Thread类提供了setPriority(int newPriority)来设置和返回指定线程的优先级。
MAX_PRIORITY 10 最高优先级MIN_PRIORITY 1 最低优先级NORM_PRIORITY 5 普通优先级当多线程并发,有多段代码同时执行时,我们希望在某一段代码执行时CPU不要切换到其他的线程 如果两段代码是同步的,那么同一时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码 线程的同步问题就是由于线程调度的不确定性引起的。
在方法的修饰符中添加Synchronized修饰符
锁对象 非静态的同步方法的锁对象是调用者的对象this静态的同步方法的锁对象是该类的字节码对象多线程使用锁嵌套,互相等待释放锁对象就会出现死锁问题 关于线程安全的类
Vector 线程安全 ArrayList 线程不安全的StringBuffer 线程安全 StringBuilder 线程不安全Hashtable 线程安全 HashMap 线程不安全集合类的不安全线程转化 Collections.synchronized(xxx)java5 java提供了——显示定义同步锁对象来实现同步
Lock Lock支持多个相关的条件对象每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象 *Lock实现类ReentrantLock(可重入锁) *java8提供了新的StampedLock 使用ReentrantLock代码格式java提供Object方法来实现线程间通信
wait() 、notify()、notifyAll() wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒线程。该wait()有三种重载形式——无时间参数的wait()[一直等待,直到其他线程通知]、带毫秒参数的wait()、带毫微秒参数的wait()[等待指定时间后自动苏醒]调用wait()方法的当前线程会释放对该同步监视器的锁定notify() 唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意的,只有当前放弃对该同步监视器的锁定后(wait()),才可以执行被唤醒的线程notifyAll() 唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程Condition对象将同步监视器对象的方法(wait()、notity()、notityAll())分解成截然不同的对象,通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set) 也就是说,在这种情况下 Lock替代了synchronized同步代码块。Condition替代了同步监视器的功能
步骤创建Lock锁对象通过Lock锁对象获取绑定的Condition对象加锁---> 操作数据 --->操作线程--->释放锁(一般在finally中释放)API: Condition实例被绑定在一个Lock对象上,要获得特定Lock对象的Condition对象,调用Lock对象的new Condition()方法
await() 类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。signal() 唤醒在此Lock对象上等待的单个线程,类似notify(),如果所有的线程都等待,会随机选择一个唤醒只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程signalAll() 唤醒在此Lock对象上等待的所有线程API:
put(E e) 尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞线程take() 尝试从BlockingQueue的头部取出元素,该队列的元素已空,则阻塞线程集成Queue()接口的方法 add(E e)offer(E e)put(E e) 需要学习一下队列线程组定义了 void uncaughtException(Thread t,Throwable e) 该方法可以处理线程组内的任意线程所抛出的未处理异常
java5开始 线程在执行过程中如果抛出了一个未处理的异常,JVM在结束该线程之前会自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理器对象,则会调用该对象的uncaughtException(Thread t,Throwable e)来处理异常
Thread.UncaughtExceptionHandler是Thread类的一个静态内部接口。接口内只有一个方法 void uncaughtException(Thread t,Throwable e)t 代表出现异常的线程e 代表该线程抛出的异常异常处理
static setDefaultUncaughtExcptionHandler(Thread.UncaughtExceptionHandler eh):为该线程类的所有线程实例设置默认的异常处理器setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):为指定的线程实例设置异常处理器由于每个ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。当一个线程抛出未处理的异常时,JVM会首先查找该异常对应的异常处理器也就是(setUncaughtExceotionHandler()中指定的异常处理器) 如果找到该异常处理器,则将调用该异常处理器处理异常,否则,JVM将会调用该线程所属的线程组对象的uncaughtException()来处理异常 线程组处理异常流程
如果该线程组有父线程组,则调用父线程组的uncaughtException()来处理异常如果该线程实例所属的线程类有默认的异常处理器则调用该异常处理器来处理异常如果该异常对象是ThreadDeath对象,则不做任何处理,否则将异常跟踪栈的信息打印到System.err错误输出流,并结束线程tips:与try...catch区别
当使用catch捕获异常时,异常不会向上传播给上一级调用者使用异常处理器对异常进行处理后,异常依然会传播给上一级调用者 /** * Created by D9ing * 2016/11/17 * Describe:线程的异常处理 * Todo: * Good Luck */ public class ExHandlerThread { public static void main(String[] args) { // TODO: 设置主线程异常处理器 Thread.currentThread().setUncaughtExceptionHandler(new mExhandler()); int a = 5 / 0;//除0异常 System.out.println("线程正常结束"); } } class mExhandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t + "线程出现了异常" + e); } }soource
系统启动一个新线程的成本比较高,这时候应当使用线程池来更好的提高性能。线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象穿给线程池。线程池会启动一个线程来执行他们的run()或者call(),当执行体方法结束后,线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法在java5,java内建线程池,java5新增了一个Executors工厂类来产生线程池
newCachedThreadPool() 创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将被缓存在线程池中newFixedThreadPool(int nThreads)创建一个可重用的具有固定线程数的线程池newSingleThreadExceutor() 创建一个只有单线程的线程池==newFixedThreadPool(1)newScheduledThreadPool(int corePoolSize) 创建具有指定线程数的线程池,可以在指定延迟后执行线程任务,corePoolSize指池中锁保存的线程数newSingleThreadScheduledExecutor()创建只有一个线程的线程池,可以在指定延迟后执行线程任务java8新增
ExecutorService new WorkStralingPool(int parallelism) 创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争
ExecutorService new WorkStealingPool() 创建一个依赖CPU核数的并行数量的线程池
ExecutorService 代表一个线程池对象
ScheduleExecutorService 是ExecutorService是子类 可以在指定延迟后执行线程任务
API 方法 Future<?> submit(Runnable task) 将一个Runnable对象提交给指定的线程池,Future代表Runnable任务的返回值 <T>Future<T>submot(Runnable task,T result)将一个Runnable对象提交给指定的线程池,result显式指定线程执行结束后的返回值,所以Future对象将在run()方法执行结束后返回result <T>Future<T>submit(Callable<T> task) 将一个Callable对象提交给指定的线程池,Future代表Callable中的call()方法的返回值
延迟加载线程池 ScheduleFuture<T>schedule(Callable<V> callable,long delay,TimeUnit unit)指定callable任务将在delay延迟后执行 ScheduleFuture<?>schedule(Runnable command,long delay,TimeUnit unit)指定command任务将在delay延迟后执行 ScheduleFuture<?> scheduleAtFixedRate(Runnable command,long delay,long period,TimeUnit unit)指定command任务将在delay延迟后执行,而且以设定的频率重复执行 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,随后在每一次执行终止和下一次执行开始之间都存在给定的延迟,除非遇到异常,否则只能通过程序显式取消或终止任务
用完一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列,调用shutdown()方法后的线程池不再接受新任务,以前已经提交的任务会执行完成,当线程池中的所有任务都执行完成后,池中所有的线程都会死亡。shutdownNow()该方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表java7提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果
ForkJoinPool是ExecuttorService的实现类,是一种特殊的线程池。
ForkJoinPool(int p)创建一个包含p个并行线程的ForkJoinPoolForkJoinPool()以Runtime.availableProcessors()方法的返回值作为线程数来创建线程池java8拓展了ForkJoinPool功能,ForkJoinPool增加了通用池的功能
ForkJoinPool commonPool() 该方法返回一个通用池,通用池的运行状态不会受shutdown()或shutdownNow()方法的影响。除非程序杀死了进程int getCommonPoolParallelism() 返回通用池的并行级别再创建了ForkJoinPool实例之后,就可调用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)来执行指定任务了。ForkJoinTask代表一个可以并行、合并的任务。
ForkJoinTask 是一个抽象类, 有两个抽象子类 RecursiveAction 没有返回值 RecursiveTask<T> 有返回值,返回值为泛型T
[java 在多核cpu的强大]
java还位线程安全提供了一些工具类。
ThreadLocal 代表一个线程局部变量,把数据放在ThreadLocal中就可以让每个线程创建一个该变量的副本
支持泛型的ThreadLocal<T> 其作用就是每一个线程的局部变量,每个线程都可以保留一个自己的副本,不会和其他线程的副本冲突。ThreadLocal API *T get() 返回此线程局部变量中当前副本中的值 *void remove() 删除此线程局部变量中当前线程副本中的值 *void set(T value) 设置此线程局部变量中当前副本的值使用建议:如果多个线程之间需要共享资源,以达到线程之间的通信功能,就要使用同步机制。如果仅仅需要隔离多个线程之间的共享冲突,则使用ThreadLocal
Concurrent开头的集合类 ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue和ConcurrentLinkedDeque
以CopyOnWrite开头 CopyOnWriteArrayList、CopyOnWriteArraySet
以Concurrent开头的集合类代表了支持并发访问的集合。可以支持多个线程并发写入访问。ConcurrentLinkedQueue 实现了多线程的高效。多个线程访问无须等待Exercise misson 1: 两个线程一个打印1-27 另一个打印A-Z
package com.probuing.learnjavaagain.threadlearn.threadcommexercise; /** * Created by D9ing * 2016/11/18 * Describe:需求 两个线程一个打印1~52 另一个打印A-z * Todo:普通线程通信实现 * Good Luck */ public class PrintCommThread { public static void main(String[] args) { PrintBean bean = new PrintBean("打印"); // PrintBean bean2 = new PrintBean("打印字母"); new PrintNumThread(bean).start(); new PrintLetterThread(bean).start(); } } class PrintBean { // TODO: flag private boolean flag = true; private String name; private char[] letter = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; public PrintBean(String name) { this.name = name; } public synchronized void printNum() { for (int i = 1; i < 53; i++) { if (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name + "线程名:" + Thread.currentThread().getName() + "----" + i); flag = false; notifyAll(); } } public synchronized void printLetter() { for (int i = 0; i < letter.length; i++) { if (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name + "线程名:" + Thread.currentThread().getName() + "----" + letter[i]); flag = true; notifyAll(); } } } /** * @User:D9ing * @Data:上午11:46 * @describe:打印1~52线程 * @todo: */ class PrintNumThread extends Thread { // TODO: 索引bean private PrintBean bean; public PrintNumThread(PrintBean bean) { this.bean = bean; } @Override public void run() { bean.printNum(); } } /** * @User:D9ing * @Data:上午11:56 * @describe:打印字母线程 * @todo: */ class PrintLetterThread extends Thread { private PrintBean bean; public PrintLetterThread(PrintBean bean) { this.bean = bean; } @Override public void run() { bean.printLetter(); } } Lock锁实现 package com.probuing.learnjavaagain.threadlearn.threadcommexercise; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by D9ing * 2016/11/18 * Describe:两个线程 一个打印1-27 另一个打印A-z * Todo:java8 Lock实现 * Good Luck */ public class PrintCommLock { public static void main(String[] args) { LockBean bean = new LockBean("打印实体"); // TODO: 普通线程构造 // new Thread(new PrintNumThreadLock(bean)).start(); // new Thread(new PrintLetterThreadLock(bean)).start(); // TODO: 线程池 ExecutorService executorService = Executors.newFixedThreadPool(4); executorService.submit(new PrintNumThreadLock(bean)); executorService.submit(new PrintLetterThreadLock(bean)); // executorService.submit(new ) } } /** * @User:D9ing * @Data:下午12:34 * @describe: Lock实体 * @todo: */ class LockBean { private final Lock lock = new ReentrantLock(); private final Condition cond = lock.newCondition(); private char[] letter = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; private String name; private boolean flag = true; public LockBean(String name) { this.name = name; } /* 打印数字方法 */ public void printNum() { // TODO: 加锁 lock.lock(); try { for (int i = 0; i < letter.length; i++) { if (!flag) { cond.await(); } System.out.println(Thread.currentThread().getName() + "---" + i); flag = false; // TODO: 唤醒线程 cond.signalAll(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // TODO: 释放锁 lock.unlock(); } } public void printLetter() { // TODO: 加锁 lock.lock(); try { for (int i = 0; i < letter.length; i++) { if (flag) { // TODO: 线程等待 cond.await(); } System.out.println(Thread.currentThread().getName() + "---" + letter[i]); flag = true; // TODO: 唤醒线程 cond.signalAll(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } class PrintNumThreadLock implements Runnable { private LockBean bean; public PrintNumThreadLock(LockBean bean) { this.bean = bean; } @Override public void run() { bean.printNum(); } } class PrintLetterThreadLock implements Runnable { private LockBean bean; public PrintLetterThreadLock(LockBean bean) { this.bean = bean; } @Override public void run() { bean.printLetter(); } }