线程
定义线程的方式
实现Runnable接口。
继承Thread类。
继承Callable接口(有返回值)。
public class FirstThread extends Thread{
public void run() {
System.out.println("线程体");
}
}
new FirstThread().start(); // 启动线程
public class Thread1 implements Runnable{
// 共享 Runnable可以继承其他类、同时多个线程共享同一个对象。
private int age = 0;
@Override
public void run() {
age++;
System.out.println("Runable ------------"+age);
}
}
Thread1 thread = new Thread1();
new Thread(thread).start();
new Thread(thread).start();
public class RtnThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println("循环变量I"+i);
}
return i;
}
}
public class CallableTest {
public static void main(String[] args){
// 创建Callable对象
RtnThread thread = new RtnThread();
//使用Future来包装Callable对象
FutureTask<Integer> task = new FutureTask<>(thread);
for (int i = 0; i<100 ;i++){
System.out.println(Thread.currentThread().getName()+"---------"+i);
if(i==20){
new Thread(task,"有返回值的线程").start();
}
}
try{
System.out.println("子线程返回值:"+task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
生命周期
-
新建-就绪-运行-阻塞-死亡
新建:new 关键字创建一个线程后。
就绪:当线程对象调用start()方法之后。
运行:处于就绪状态的线程获取到CPU,开始执行run方法中的线程体。
阻塞:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态(阻塞后-就绪)。
死亡:线程执行完成。(异常或stop)
常用方法
demoThread.isAlive()//新建、死亡 false 其余状态 true
// 死亡状态的线程 不能start()
demoThread.join() // 被join的demoThread线程执行完成、才会执行主线程。
// 后台线程 (jvm中的GC) 当整个后台只剩下后台线程时候,后台线程将死亡
demoThread.setDaemon(true) //设置为后台线程
demoThread.isDaemon() // 判断是否为后台进程
Thread.currentThread() //获取驱动该任务的Thread对象的引用
线程休眠和让步
- 休眠
//老版本
Thread.sleep(100);
//se5/6
TimeUnit.MILLISECONDS.sleep(100);
- 让步
Thread.yield(); // 只是让线程重新转入就绪状态。
优先级
优先级高的执行频率高
-
设置优先级setPriority(),获取优先级getPriority(),
- 范围1~10 建议用常量 不同操作系统支持不同。
优先级尽量在run方法的开头设定。
线程同步方式
-
volatile 修饰
保证了可见性性,当一个线程修改了某个变量时,其他所有线程都知道该变量被修改了。
并不是线程安全的。
这是因为当线程在获取到 race 变量的值,然后对其进行自增这中间,有可能其他线程对 race 变量做了自增操作,然后写回了主内存。而当前线程再将数据写回主内存时,就发生了数据覆盖。因此,就发生了数据不一致的问题。
synchronized 代码块和方法
// 代码块形式 对资源account进行加锁
public void run() {
synchronized (account){
if(account.getAccount()>(a-p*100)){
while ((p*100)<a){
account.setAccount(account.getAccount()-100);
p++;
}
System.out.println("总共取钱"+p*100+"元"+",剩余金额:"+account.getAccount());
}else {
System.out.println("金额不足 取钱失败!");
}
}
}
// 方法加锁 static
public synchronized static void pay(Account account,double p,double a){
if(account.getAccount()>(a-p*100)){
while ((p*100)<a){
account.setAccount(account.getAccount()-100);
p++;
}
System.out.println("总共取钱"+p*100+"元"+",剩余金额:"+account.getAccount());
}else {
System.out.println("金额不足 取钱失败!");
}
}
- ReentrantLock
public class Account {
...
//需要声明这个锁
private Lock lock = new ReentrantLock();
...
public void drawMoney(double money) {
// 1、拿到是谁来取钱
String name = Thread.currentThread().getName();
lock.lock();
try {
...
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
- synchronized和ReentrantLock区别
描述 synchronized ReentrantLock 用法 普通方法、静态方法、代码块 代码块 获取锁方式 自动加锁和释放锁 手动加锁和释放锁 类型 非公平锁 非公平锁(默认)、公平锁 响应中断 不能响应中断(存在死锁问题) 可以响应中断(避免死锁问题) 底层实现 jvm层面实现的 AQS程序级别的api实现的
锁
-
死锁
- 产生的条件:互斥、请求保持、不剥夺条件、循环等待条件。
乐观锁:认为访问共享资源不会出问题,无需加锁无需等待,只是在提交时候去验证对应的资源是否被其他线程修改,如果已被修改,则放弃本次操作。
-
悲观锁:认为访问访问共享资源一定会出问题,每次访问共享资源前都要加锁,这样其他线程访问共享资源就会阻塞直到锁被上一个持有者释放。
- 互斥锁:属于悲观锁,加锁失败后会释放cpu资源。
- 自旋锁:属于悲观锁,加锁失败后线程一直等待,直到拿到锁为止。
-
什么时候释放同步锁
同步代码块执行完成
同步代码块break、return、异常
程序执行了同步监视器的wait()方法。
-
不是释放同步锁
执行sleep和yield方法。
该线程的suspend()挂起。
线程组
对一批线程分类管理。
创建一个线程未显示指定线程组,默认:子线程和创建他的父线程一个组。
某线程加入线程组,该线程就一直属于该线程组,直至线程死亡。
// 构造方法 创建线程加入线程组
Thread(ThreadGroup group,Runnable target)
Thread(ThreadGroup group,Runnable target,String name)
Thread(ThreadGroup group,String name)
// 构造方法 创建线程组 创建完成不允许修改名称
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent,String name)
int activeCount() //返回活动中的线程数目
interrupt() //中断此线程中的所有线程。
isDaemon() // 判断线程组是否为后台线程
setDaemon(boolean daemon) // 设置该线程组为后台线程组、(线程并不为后台线程)
setMaxPriority(int pri) //设置线程组的优先级
线程池Executor
例:ExecutorService e=Executors.newCachedThreadPool();
方法 | 概述 |
---|---|
ExecutorService newCachedThreadPool(首选) | 创建和所需数量相同的线程。 |
ExecutorService newFixedThreadPool(I) | 创建固定数量线程的线程池。 |
ExecutorService newSingleThreadExecutor() | 就像线程数量为1的FixedThreadPool,提交多任务、任务会排队,上一个任务结束下一个任务才会开始。所有任务使用相同的线程。按提交顺序。 |
ScheduledExecutorService newScheduledThreadPool(int) | 创建具体指定线程数的线程池,它可以指定延迟后执行线程任务。 |
ScheduledExecutorService newSingleThreadScheduledExecutor() | 创建具有1个线程的线程池,它可以在指定延迟后执行线程任务 |
- ExecutorService 方法
方法 | 概述 |
---|---|
Future<?> submit(Runnable task) | 将一个Runnable对象指定给线程池。线程池空闲时执行Runnable对象代表的任务。Future代表返回值,如果没有则返回null。 |
<T> Future<T> submit(Runnable task,T result) | result显示指定线程之执行结束的返回值。 |
<T> Future<T> submit(Callable<t> task) | 将一个Callable对象提交给线程池 |
<T> void<T> execute(Runnable<t> command) | Executor方法、只接受Runnable |
- ScheduledExecutorService 方法
方法 | 概述 |
---|---|
ScheduledFuture<V> schedule(Callable<V> callable,long delay,TimeUnit unit) | 指定callable任务再delay延迟后执行。 |
ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit) | 指定command任务将delay延迟后执行。 |
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long init,long period,TimeUnit unit) | 指定command任务将再init延迟后执行,init后开始执行,依次再init+period*n处重复执行。 |
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,TimeUnit unit) | 创建并执行一个在给定初始延迟首次启动的定期操作,随后,每次一次执行终止和下一次执行开始之间都存在一定延迟。如果任一次执行出现异常,就会取消后续执行。 |
-
使用线程池执行任务的步骤
调用Executors类的静态方法创建一个ExecutorService对象。
创建Runnable实现类或者Callable实现类的实例,作为线程执行任务。
调用ExecutorService对象的submit方法来提交Runnable实例或者Callable实例。
当不想提交任何任务时,调用shutdown方法来关闭线程池。
ExecutorService pool= Executors.newFixedThreadPool(6);
pool.submit(new TestThread());
pool.submit(new TestThread());
pool.shutdownNow();
线程池5种状态
- RUNNING : 可以接受新任务和执行已添加的任务。
- SHUTDOWN : 不接收新任务,但处理已添加的任务。
- STOP : 不接收新任务,不处理已添加的任务,并且中断正在执行的任务。
- TIDYING : 所有任务已中止,记录的任务数量为0,执行terminated()。
-
TERMINARED:当terminated()执行完成,线程池彻底中止,变为TERMINARED状态。
为什么不建议使用Executors创建线程池
- Executors各个方法都存在弊端
- newFixedThreadPool和newSingleThreadExecutor 使用队列LinkedBlockingQueue(默认使用integer.MAX_VALUE,可以理解无界阻塞队列),导致其无限增大,撑爆内存(OOM)。
- newCachedThreadPool和newScheduledThreadPool 主要的问题是线程最大个数为integer.MAX_VALUE,可能会创建非常多数量的线程从而撑爆内存(OOM)。
使用ThreadPoolExecutor创建
new ThreadPoolExecutor(核心线程池大小,线程最大数量,线程活动保持时间,线程保持时间单位,任务队列,线程工厂,饱和策略)
- corePoolSize 线程池核心线程大小
- 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即corePoolSize。
- maximumPoolSize 线程池最大线程数量
- 一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
- keepAliveTime 空闲线程存活时间
- 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。
- unit keepAliveTime的计量单位
- workQueue 工作队列
- 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:ArrayBlockingQueue 有界队列、LinkedBlockingQuene 无界队列、SynchronousQuene 直接提交、PriorityBlockingQueue 优先级队列。
- threadFactory
- 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如 设定线程名、设置daemon和优先级等等。
- handler 拒绝策略
- 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
- CallerRunsPolicy 只用调用者所在线程来运行任务
- AbortPolicy 直接抛出异常
- DiscardPolicy 不处理,丢弃掉
- DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务.
- 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
线程相关类
- 包装线程不安全集合(Collections提供的静态方法)
方法 | 描述 |
---|---|
<T> Collection<T> synchronizedCollection(Collection<T> c) | 返回指定collection对应线程安全的collection |
static <T> List<T> synchronizedList(List<T> list) | 返回指定List对应线程安全的List对象 |
static <K,V>Map<K,V> synchronizedMap(Map<K,V> m) | 返回指定Map对象对应的线程安全的Map对象 |
static <T>Set<T> synchronizedSet(Set<T> s) | 返回指定Set对应的线程安全的Set |
static <K,V> SortedMap<K.V> synchronizedSortedMap(SortedMap<K,V> m) | 返回指定SortedMap对象所对应的线程安全的SortedMap对象。 |
static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) | 返回指定SortedSet对象对应的线程安全的SortedSet对象 |
- 线程安全集合类
ConcurrentHashMap、ConcurrentLinkedQueue。