*线程:
*多线程的实现方式:
1.继承Thread
多用匿名内部类实现
2.实现Runnable
3.实现Callable
注意:创建执行线程的方式三:实现Callable接口。相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
二、执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
*多线程的一些方法:
1.start()
启动线程
2.sleep()
睡眠阻塞 参数类型为long 为毫秒值 必须处理异常
3.getId()
获取线程的id
4.getName()
获取线程的名字
5.getPriority()
获取线程的优先级
线程的优先级分为1-10个等级 10为最高 1为最低
默认值为5 优先级越高的线程,获取CPU时间片的几率越高。
6.isAlive()
线程是否处于活动状态
7.isDaemon()
是否为守护线程
8.isInterrupted()
是否被中断了
*中断睡眠阻塞:
thread.interrupt()
sleep方法要求必须处理中断异常:
当一个线程调用sleep方法进入阻塞状态时,若此时这个
线程的中断方法interrupt被调用,那么这时该线程的
阻塞状态会被打断,与此同时sleep方法就会抛出该异常
InterruptedException
想中断谁就用谁调用interrupt方法
*守护线程
void setDaemon(boolean tf)
当参数为true时,该线程被设置为守护线程。
守护线程在使用上与普通线程一致,但是在结束时机上不同
所有的前台线程结束 守护(后台)线程 才被终止
*阻塞插队:
void join()
这个方法来协调线程之间的同步运行
同步运行:运行有先后顺序
异步运行:运行没有先后顺序,各执行各的。
上一个运行的时候 就在要阻塞的里面用上一个调用join方法
*锁
synchronization
当多个线程并发操作同一临界资源时,由于线程切换时机
不确定,导致操作代码执行顺序未按照设计顺序执行,从
而出现操作混乱,严重时可能导致系统瘫痪。
对方法进行加锁
当方法调用完以后才能释放锁 其他线程才可以调用
*synchronized修饰同步代码块
synchronized(同步监视器){ //对代码块进行加锁
需要同步运行的代码片段
}
注意:这个对象可以是java任何类型的对象,
但是要保证需要同步运行该代码片段的线程看到的是同一个对象
*静态方法使用锁:
1.静态方法若使用synchronized修饰,那么该方法一定具有同步效果
2.静态方法的同步监视器对象为这个类的类对象
3.java中每个类都有且只有一个类对象(Class实例)与之对应
4.获取一个类的类对象比较简单的做法是调用这个类的静态属性class得到。例如 A.class
*互斥锁:
当使用synchroinzed控制多段代码,并且他们指定的同步监视器对象是同一个时,
这些代码片段就互为互斥的,多个线程不能同时在这些代码片段间一起执行。
*死锁:
在线程执行流程过程中,持有自己的锁同时需要对方线程释放其持有的锁,导致卡住。
*产生死锁的4大必要条件(!!):
①资源互斥/资源不共享
每个资源要么已经分配给了一个进程,要么是可用的,只有这两种状态,资源不可以被共享使用,所以所谓的互斥是指:资源不共享,如果被使用,只能被一个进程使用。
②占有和等待/请求并保持
已经得到资源的进程还能继续请求新的资源,所以个人觉得叫占有并请求也许更好理解。
③资源不可剥夺
当一个资源分配给了一个进程后,其它需要该资源的进程不能强制性获得该资源,除非该资源的当前占有者显示地释放该资源。
④环路等待
死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,环路上的每个进程都在等待下一个进程所占有的资源。
*防止死锁的方法:
①防止死锁的发生只需破坏死锁产生的四个必要条件之一即可。
②下面的方法开销非常之大,目前没有一个操作系统可以实现。
③因此,目前使用的方法是避免死锁,而不是防止死锁。
④这部分的内容大致浏览简单了解一遍即可,只要能在某些选择题中判断出选项对应的是下面四个方法中的哪个就可以了。
1、破坏互斥条件
方法:
如果允许系统资源都能共享使用,则系统不会进入死锁状态。
缺点:
有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。
2、破坏请求并保持条件
方法:
釆用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦投入运行后,这些资源就一直归它所有,也不再提出其他资源请求,这样就可以保证系统不会发生死锁。
缺点:
系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致“饥饿”现象,当由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。
3、破坏不可剥夺条件
方法:
当一个已保持了某些不可剥夺资源的进程,请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺了,或从而破坏了不可剥夺条件。
缺点:
该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。
4、破坏循环等待条件
方法:
为了破坏循环等待条件,可釆用顺序资源分配法。首先给系统中的资源编号,规定每个进程,必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中,只能申请编号大于Ri的资源。
缺点:
这种方法存在的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦
*死锁的检测:
1.画出资源分配图
2.简化资源分配图 (若能消去图中所有的边,则称该图是可完全简化的)
3.使用死锁定理判断
死锁定理:
①如果资源分配图中没有环路,则系统没有死锁;
②如果资源分配图中出现了环路,则系统可能有死锁。
或者说:
当且仅当S状态的资源分配图是不可完全简化的时候,系统状态则是死锁状态
*死锁的解除
1、资源剥夺法
挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。
2、撤销进程法
强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
3、进程回退法
让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。
*CountDownLatch:
jdk1.5开始concurrent包里提供的,并发编程工具类。
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行
*CountDownLatch工作原理:
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。
每当一个线程完成了自己的任务后,计数器的值就会减1。
当计数器值到达0时,它表示所有的线程已经完成了任务,
然后在闭锁上等待的线程就可以恢复执行任务。
*CountDownLatch的方法:
1.countDown()
减少锁存器的计数。如果计数到达零,释放所有等待的线程
2.await()
在倒计数为0之前会阻塞当前线程.
*CyclicBarrier 栅栏:
栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。
栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。
闭锁用于等待事件,而栅栏用于等待其他线程。
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。
当线程到达栅栏位置时将调用await方法,
这个方法将阻塞直到所有线程都到达栅栏位置。
如果所有线程都到达栅栏位置,那么栅栏将打开,
此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
方法:await()
*volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,
那么就具备了两层语义:
---保证了不同线程对这个变量进行操作时的可见性,
即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
---禁止进行指令重排序。
*线程池:
Executors.newCachedThreadPool();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
创建一个可缓存线程池程
是一种线程数量不定的线程池,并且其最大线程数为Integer.MAX_VALUE
超时时长是60秒
没有重用的就创建 有重用的就重用
*newFixedThreadPool
指定工作线程数量的线程池
每当提交一个任务就创建一个工作线程,当线程 处于空闲状态时,
它们并不会被回收,除非线程池被关闭了,
如果工作线程数量达到线程池初始的最大数,则将提交的新任务存入到池队列
*newScheduledThreadPool
核心线程数量是固定的,非核心线程数是没有限制的,
并且当非核心线程闲置时会被立即回收,
它可安排给延迟运行命令或者定期地执行。
这类线程池主要用于执行定时任务和具有固定周期的重复任务。
*使用线程池的优点
1.重用线程池的线程,避免因为线程的创建和销毁锁带来的性能开销
2.有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞
3.能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线程、并发数控制等功能
*shutdownNow ()和shutdown()的区别
1.shutDown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。
此时,则不能再往线程池中添加任何任务,
否则将会抛出RejectedExecutionException异常。
但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
执行该方法,线程池的状态立刻变成STOP状态,
并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,
当然,它会返回那些未执行的任务。
2.shutdownNow()
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,
但是大家知道,这种方法的作用有限,
如果线程中没有sleep 、wait、Condition、定时锁等应用,
interrupt()方法是无法中断当前的线程的。
所以,ShutdownNow()并不代表线程池就一定立即就能退出,
它可能必须要等待所有正在执行的任务都执行完成了才能退出。