程序进入内存时,即变成一个进程,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位
进程三个特征:
独立性:进程是系统中独立存在的实体,拥有自己独立的资源,有自己私有的地址空间,没有经过进程本身允许的情况下,一个用户进程不能直接访问其他进程的地址空间
动态性:进程与程序的区别在于,程序是静态的指令集合,而进程是正在系统中活动的指令集合,进程中加入的时间概念,进程有自己的生命周期和各种不同的状态,程序不具备这些
并发性:多个进程可以在单个处理器上并发执行,多个进程间不会互相影响
并发和并行:
并行是同一时刻,多个指令在多个处理器上同时执行
并发指在同一时刻只有一条指令执行,但多个进程指令被快速轮换,宏观上被同时执行
并发方式:
共用式的多任务操作策略
抢占式多任务操作策略:效率更高更常用
多线程:
扩展了多进程,使得同一个进程可以同时并发处理多个任务。
线程thread也被称为轻量级进程,是进程的执行单元,类似进程在操作系统中的地位,线程在程序中是独立的,并发的执行流,进程初始化后,主线程就被创立。
线程是进程的组成部分,一个进程有多个线程,一个线程必须有一个父进程,线程可以有自己的堆栈,自己的程序计数器和局部变量,但不能拥有系统资源,与父进程的其他线程共享该进程的全部资源。
线程是独立运行的,不知道进程中其他线程的存在,执行时抢占式的
一个线程可以创建和撤销另一个线程,同一个进程中多个线程可以并发执行
多线程编程优点:
进程间不能共享内存,线程间非常容易
系统创建进程要为该进程重新分配系统资源,但创建线程代价很小,多线程来实现多任务并发比多进程效率高
Java内置了多线程功能支持,简化了多线程编程
线程的创建和启动:
1.继承Thread类创建线程类:
定义Thread类的子类,并重新run()方法,创建Thread子类的实例,调用线程对象的start()启动线程
Thread.currentThread:Thread类的静态方法,总是返回当前正在执行的线程对象
getName():Thread类的实例方法,返回调用该方法的线程名称
获得当前线程对象可以用this
不能共享线程类的实例变量
2.实现Runnable接口创建线程类:
定义Runnable接口的实现类,并重新run()方法,创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread才是真正的线程对象
SecondThread implements Runnable{}
SecondThread st = new SecondThread();
new Thread(st).start();
new Thread(st,"第二个线程名称").start();
获得当前线程对象必须使用Thread.currentThread()
可以共享线程类的实例变量,因为线程对象只是线程类的target
3.使用Callable和Future创建线程:
java5后开始支持,模仿C#的任何方法都可以做线程执行体
Runnable接口增强版,Callable接口提供一个call()方法,可以作为线程载体,功能比run()强大:
call()可以有返回值,
call()可以声明抛出异常
提供一个Callbale对象作为Thread的target,线程执行体就是该Callable对象的call()方法
Callable不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target,call()方法还有返回值,所以call()方法不是直接被调用,而是作为线程执行体被调用
Future接口代表Callable接口里call()方法的返回值,提供了FutureTask实现类,该实现类实现了Futrue接口,也实现了Runnable接口,即可以作为target:
Futrue接口里控制它关联的Callable任务的公有方法:
boolean cancel(boolean mayInterruptRunning):试图取消该Future关联的Callable任务
V get():返回Callable任务里的call()方法的返回值,调用该方法会导致程序阻塞,必须等子线程结束偶才会得到返回值
V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值,让程序最多阻塞timeout和unit指定的时间,如果指定时间后Callable任务依然没有返回值,将会抛出TimeoutException异常
boolean isCancelled():如果在Callable任务完成前取消,则返回true
boolean isDone():如果Callable任务已完成,则返回true
创建线程步骤:
创建Callable接口的实现类,并实现call()方法,该call()仿作将作为线程执行体且该call()方法有返回值。再创建Callable实现类的实例,可以使用Lambda表达式创建Callable对象
使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
使用FutureTask对象作为Thread对象的target创建并启动新线程
调用FutureTask对象的get()方法来获得子线程执行后的返回值
使用Lambda表达式来创建Callable对象,无需创建Callable实现类和对象:
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>()->{...});
三种创建线程方式对比:
通过继承Thread类后者实现Runnable、Callable接口都可以实现多线程,实现Runnable接口和实现Callable接口方式基本相同,只是Callable接口的方法有返回值,可以抛出异常。两者归为一种,实现接口方式。
采用实现接口方式创建多线程优缺点:
优点:
线程类只是实现了Runnable接口或者Callable接口,还可以继承其他类
多个线程可以共享一个target对象,非常适合多个相同线程来处理同一份资源,可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象思想
缺点:
编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法
采用继承Thread类创建多线程优缺点:
优点:
编写简单,访问当前线程直接使用this即可
缺点:
因为已经继承了Thread类,所以不能再继承其他父类
线程的生命周期:
有新建New,就绪Runnable,运行Running,阻塞Block,死亡Death五种状态
线程多次在运行和阻塞状态切换
线程被创建后,进入新建状态,和普通对象一样,仅仅被分配内存,初始化成员变量。
调用start()方法后,进入就绪状态,java虚拟机会为其创建方法调用栈和程序计数器,此时并没有开始运行,只表示可以运行,何时开始运行取决于JVM里的线程调度器
处于就绪态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,处于运行状态的线程数量取决于几个处理器,最高不超多处理器数量
抢占式的系统为每个线程分配时间片,用完则剥夺该线程资源,让其他线程获得机会。当发生如下情况时,线程将进入阻塞状态:
线程调用sleep()方法主动放弃所占用的处理器资源
线程调用了一个阻塞式I/O方法,在该方法返回之前,该线程被阻塞
线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有,
线程在等待某个通知notify
程序调用了线程的suspend()方法将该线程挂起,这方法容易导致死锁,要避免
阻塞线程在合适时候进入就绪状态,不是运行状态,必须等待线程调度器再次调用它
对的解除阻塞状态,进入就绪状态措施:
调用sleep()方法的线程经过的指定时间
线程调用I/O方法已经返回
线程成功获得了试图取得的同步监视器
线程正在等待通知时,其他线程发出了一个通知
处于挂起状态的线程被调用了resume()恢复方法
线程有阻塞只能进入就绪状态,无法直接进入运行态
就绪态和运行态之间的转换通常不受程序控制,而是有系统线程调度所决定,就绪态得到处理器资源,进入运行态,失去处理器资源或者调用yield()方法可以让运行态的线程转入就绪态
线程结束后,进入死亡态:
run()或者call()方法执行完成,线程正常结束
线程抛出一个未捕获的Exception或者Error
直接调用该线程的stop()方法来结束该线程,容易导致死锁,避免使用
线程对象的boolean isAlive()方法:线程处于就绪,运行,阻塞三种状态,返回true,处于新建,死亡两种状态,返回false
不能对死亡的线程调用start(),也不能对新建状态的线程调用两次start(),都会报报IllegalThreadStateException异常
控制线程:
join线程:
Thread提供了让一个线程等待另一个线程完成的方法,当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的线程执行完为止
join通常由使用线程的程序调用,以将大问题分为小问题,每个小问题分配一个线程,每个小问题都得到处理后,再调用主线程来进一步操作
三种重载方式:join(),join(long millis),join(long millis, int nanos)
daemon线程:
在后台运行,为其他线程提供服务,叫做后台线程,又被称为守护线程,精灵线程,JVM的垃圾回收线程就是典型的后台线程
特征:如果所有前台线程死亡,后台线程会自动死亡
调用Thread对象的setDaemon(true)方法可以将指定线程设置成后台线程,必须在start()前调用,否则IllegalThreadStateException
isDaemon():判断指定线程是否为后台线程
sleep线程:
让当前线程暂停一段时间,并进入阻塞状态
两种重载形式:static void sleep(long millis),static void sleep(long millis, int nanos)
Thread.sleep(1000)
yield让步线程:
让当前线程暂停,但不进入阻塞,只是将线程转入就绪状态
yield()只是让当前线程暂停一下,让线程调度器重新调度一次,完全有可能yield暂停后,又被调度出来重新执行
yield后,只有优先级大于等于当前线程的,处于就绪状态的线程,才会获得执行机会
Thread.yield()
sleep和yield区别:
sleep方法暂停线程后,会给其他线程执行机会,不会理会其他线程的优先级,但yield方法只会给优先级相同或者更高的线程执行机会
sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会进入就绪态,但yield方法强制将线程转入就绪态,因为完成可能yield暂停后,又立刻开始执行
sleep方法声明抛出InterruptedException异常,所以调用sleep方法要么捕捉该异常,要么显式抛出该异常,但yield方法没有声明抛出任何异常
sleep方法比yield方法具有更好的移植性,通常不建议使用yield方法来控制并发的线程执行
改变线程优先级:
Thread提供了setPriority(int pri),getPriority()方法来设置和返回指定线程的优先级,setPriority方法参数可以是1到10之间的整数,也可以使用以下三个静态常量:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORA_PRIORITY:5
改变主线程优先级:
Thread.currentThread().setPriority(6);
具体优先级层数和系统有关,所以避免使用具体数字,使用三个静态常量
线程同步:
同步代码块:
synchronized(obj){。。。同步代码块。。。},obj即为同步监视器,线程执行同步代码块之前,必须获得对同步监视器的锁定
java允许使用任何对象作为同步监视器,但一般使用可能被并发访问的共享资源充当同步监视器
同步方法:
使用synchronized关键字修饰的方法
对于synchronized修饰的实例方法,无需指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象
不要对线程安全类的所有方法都同步,只对会改变竞争资源的方法同步
对于可变类,如果有两种运行环境:单线程和多线程,应该提供两种版本,StringBuilder和StringBuffer就是为了照顾单线程和多线程所提供的类,单线程用StringBuilder保证性能,多线程用StringBuffer保证安全
释放同步监视器的锁定:
程序无法显式的释放同步监视器的锁定
以下情况会释放同步监视器的锁定:
当前线程的同步方法,同步代码块执行结束,当前线程释放同步监视器
当前线程的同步方法,同步代码块中遇到break,return终止了该代码块、方法的执行,当前线程释放同步监视器
当前线程的同步方法,同步代码块出现了未处理的Error或Exception,导致异常结束,当前线程释放同步监视器
当前线程的同步方法,同步代码块程序执行了同步监视器对象的wait()方法,当前线程暂停,当前线程释放同步监视器
以下情况线程不会释放同步监视器:
线程的同步方法,同步代码块时,程序调用了Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器
线程的同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,当前线程不会释放同步监视器
同步锁:
java5开始,java提供了一种更强大的同步机制——通过显式定义同步锁对象Lock来实现同步
Lock比起同步监视器更灵活,有Lock,ReadWriteLock两个根接口,并分别提供了ReentranceLock,ReentranceReadWriteLock两个实现类
使用:
class X{
private final ReentranceLock lock = new ReentranceLock();
public void m(){
lock.lock(); //加锁
try{...需要保证线程安全的代码...}
finally{lock.unlock();} //释放锁
}
}
通常建议使用try/finally来确保锁被释放
死锁:
有主线程和副线程,主线程有A对象锁,等待对B对象加锁,副线程有B对象锁,等待对A对象加锁,两个线程互相等待对方先释放,构成死锁
不推荐使用suspend,容易死锁
线程通信:
传统的线程通信(对于synchronized实现同步):
Object类提供的wait(),notify(),notifyAll()三个方法,注意不是Thread类的方法,是Object类
对于synchronized修饰的同步方法,因为该类的默认实例this就是同步监视器,所以可以直接调用三个方法
对于synchronized修饰的同步代码块,同步监视器是synchronized后面的对象,必须使用该对象来调用这三个方法
wait():导致当前线程等待,知道其他线程调用该同步监视器的nofity方法或者notifyAll方法来唤醒线程,三种重载,有参数的即指定时间。会释放锁定
notify():唤醒在此同步监视器上等待的单个线程,如果有多个线程在等待,则随机唤醒其中一个,只有在当前线程放弃锁定,即使用wait方法后,才可以执行被唤醒的线程
nofifyAll():唤醒在此同步监视器上等待的所有线程,同样只有当前线程放弃锁定,才能执行被唤醒的线程
使用Condition控制线程通信(对于Lock实现同步):
使用Lock实现同步的线程,不存在隐式的同步监视器,所以无法使用wait,notify,notifyAll。对于此情况,java提供了Condition类来保持协调
Condition可以让得到Lock对象但无法继续执行的线程释放Lock对象,也可以唤醒其他处于等待的线程
Condition实例被绑定在Lock对象上,获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可:
private final Condition con = lock.newCondition();
cond.await();
Condition类提供了三个方法:
await():类似于wait(),导致当前线程等待,直到其他线程调用该Condition的signal或者signalAll
signal():唤醒在此Lock对象上等待的单个线程,多个则随机唤醒一个,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程
signalAll():唤醒所有线程,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程
使用BlockingQueue阻塞队列控制线程通信:
特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞
BlockingQueue提供两个支持阻塞的方法:
put(E e):尝试把e放入BlockingQueue中,如满则阻塞
take():尝试从BlockingQueue的头部取出元素,如空则阻塞
BlockingQueue继承了Queue接口,可以使用Queue接口中的方法,分为三组:
在队列尾部插入元素:add(E e), offer(E e), put(E e),当队列已满,分别会抛出异常,返回false,阻塞队列
在队列头部删除并返回被删除的元素:remove(),poll(),take(),当队列为空,分别会抛出异常,返回false,阻塞队列
在队列头取出但不删除元素:element(),peek(),当队列为空,分别会抛出异常,返回false
BlockingQueue有五个实现类:ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronizedBlockingQueue,DelayBlockingQueue
使用管道pipe通信
通过volatile使用while轮询
线程组和线程的未处理异常:
java使用ThreadGroup表示线程组,可以对同一批线程进行管理。没显式指定线程属于哪个线程组,则属于默认线程组。在默认情况下,子线程和创建他的线程处于同一个线程组。一旦加入线程组后,线程一直属于该线程组,直到死亡,不能更改
ThreadGroup定义了非常有用的方法:void uncaughtException(Thread t, Throwable e),该方法可以处理该线程组内部任意线程所抛出的未处理异常
线程池:
与数据库连接池类似,线程池在系统启动时即创建大量的空闲线程,程序将一个Runnable或者Callable对象传给线程池,线程池就启动一个线程来执行他们的run()或者call()方法,执行结束后,线程不会死亡,而是返回线程池中成为空闲状态
线程池可以有效控制系统中并发线程的数量,大量并发线程会导致系统性能急剧下降甚至崩溃,而线程池的最大线程参数可以控制系统中并发线程数不超过此数
java5前,必须手动实现线程池,java5后,java支持内建线程池,新增了一个Executors工厂类生产线程池,该工厂类包含以下几个静态工厂方法创建线程池:
ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中
ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的,具有固定线程数的线程
ExecutorService newSingleThreadPool():创建一个只有单线程的线程池,相当于调用newFixedThreadPoll(1)
ScheduledExecutorService newScheduledThreadPool(int n):创建具有指定线程数的线程池,可以在指定延迟后执行线程任务
ScheduledExecutorService newSingleThreadScheduledExecutor():相当于newScheduledThreadPoll(1)
java8新增,利用多CPU并行能力,生成的是后台线程池:
ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法会使用多个队列来减少竞争
ExecutorService newWorkStealingPool():newWorkStealingPoll()的简化版本,按cpu个数来设置并行级别
ScheduledExecutorService是ExecutorService的子类,可以在指定延迟后执行线程任务
ExecutorService代表尽快执行线程,只要线程池中有空闲线程,就立即执行任务,程序只要将一个Runnable对象或者Callable对象(代表线程任务)提交给该线程池,线程池会尽快执行该任务
三个方法:
Futrue<?> submit(Runnable task):一个Runnable对象提交给线程池,线程池有空将会执行Runnable代表的任务。Future对象代表Runnable任务的返回值,但run()犯法没有返回值,所以Future对象在run方法执行结束后返回null
<T>Future<T> submit(Runnable task, T result):其他同上,但result显式指定线程执行结束后的返回值,Futrue对象在run方法执行后返回result
<T>Future<T> submit(Callable task):其他同上,Future代表Callable对象里call()方法的返回值
使用线程池执行线程任务步骤:
调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
创建Runnable实现类或者Callable实现类的实例,作为线程执行任务
调用ExecutorService对象的submit()方法来提交线程执行任务
当不想提交任务时,调用ExecutorService对象的shutdown()方法来关闭线程池
通过线程池来执行任务,不通过启动线程:
ExecutorService pool = Executors.newFixedThreadPoll(6);
Runnable target = ()->{...} //用Lambda表达式创建Runnable对象
pool.submit(target);
pool.submit(target);
pool.shutdown();
ForkJoinPool:将一个任务拆分成多个小任务,最终再合并
线程相关类:
ThreadLocal类:
java5后引入了泛型支持:ThreadLocal<T>,可以简化多线程编程时的并发访问
线程局部变量,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立改变自己的副本,而不会和其他线程副本冲突,就好像每一个线程都完全拥有该变量一样
三个public方法:
T get():返回此线程局部变量中当前线程的值
void remove():删除此线程局部变量中当前线程的值
void set():设置此线程局部变量中当前线程副本的值
private ThreadLocal<String> tl= new ThreadLocal<>(); tl.get();
解决多线程对同一变量的访问冲突,从另一个角度解决并发访问,不是通过锁,从而不用进行同步
不能代替同步机制,两者面向的问题领域不同。如果多个线程需要共享资源,以达到线程之间通信功能,使用同步机制,如果仅仅需要隔离多个线程之间的共享冲突,可以使用ThreadLocal
包装线程不安全的集合:Collections的类方法
线程安全的集合类:
以Concurrent开头的集合类
以CopyOnWrite开头的集合类
VSHE四个类
obj.wait()和Thread.sleep()都需要进行异常捕捉