多线程基础
多线程基础部分参考多线程基础
1.Thread构造函数
Thread()
Thread(Runnable target)
Thread(ThreadGroup group,Runnable target)
Thread(Runnable target,String name)
Thread(String name)
Thread(ThreadGroup group,Runnable targe,String name)
Thread(ThreadGroup group,Runnable target,String name,long stackSize)
Thread(ThreadGroup group,String name)
其中Runnable target指一个任务类的对象、ThreadGroup group,为线程组(后边会学习),String name为线程名称。
stacksize是指虚拟机栈。stacksize 越大代表线程内方法调用递归的深度越深,stacksize越小代表创建的线程数量约多。
要理解这是为什么首先要知到JVM内存结构
每一个线程都会有如下几个部分1:1.程序计数器、2.Java虚拟机栈、3.本地方法栈、4.堆内存、5.方法区、6.Java 8元空间。
stacksize 越小则每一个线程占用内存越小,JVM能够创建的线程就会越多,但是每一个线程的递归调用深度就会越减。反之,同理。
在创建线程时,创建线程的这个线程是被创建线程的父线程。
子线程会被加入父线程所在的线程组。
守护线程
守护线程的特点:当主线程运行结束,则守护线程不管有没有运行完成都会退出。
使用setDaemon()方法将一个线程设为守护线程。
2.Thread API详解
一些其他方法
public long getId()得到线程ID
public static Thread currentThread() 得到当前线程
public ClassLoader getContextCLassLoader()获取线程上下文的类加载器
public void setContextClassLoader(ClassLoader cl)设置线程的类加载器
1.sleep()方法
public static void sleep(long millis) throws InterruptedExceptiom
调用sleep会使调用线程休眠指定时间 使用Thread.sleep()使当前线程休眠指定时间。时间都是毫秒值。
线程使用sleep()方法休眠时不会释放monitor锁
使用TimeUnit替代Thread.sleep
TimeUnit.HOURS.sleep(int);
TimeUnit.MINUTES.sleep(int);
TimeUnit.SECONDS.sleep(int);
TimeUnit.MILLISECONDS.sleep(int);
上述方法都可以使当前线程休眠指定单位时间。
2.yield()方法
调用yield方法会提示CPU当前线程自愿放弃CPU资源,会引起线程的切换。当然,如果在线程资源不紧张的情况下CPU会忽略这种提示。
yield与sleep区别
sleep是休息指定时间,yield具有不确定性。
调用sleep线程会进入阻塞态,调用yield线程会进入就绪态准备下一次的执行。
sleep能够捕获到调用interrupt的中断信号。
3.线程优先级
setPriority()设置线程优先级:0~10
getPriority()获得线程优先级
如果没有指定优先级则默认与父线程一致。
注意:
在写代码时,不能让某些业务严重的依赖线程的优先级别。
4.join()
在主线程调用join()方法是值,等到子线程运行完毕后主线程才能继续往下执行。
一般用于需要得到一些其他结果后才能继续执行的情况。
5.interrupt
调用以下方法线程会进入阻塞状态
wait();
sleep();
join();
调用某一线程的interrupt可以打断这个线程的阻塞状态。
每一个线程内部会有一个interrupted flag标志。
调用线程的interrupt分为两种情况
1.若该线程处于正常运行情况,调用该线程的interrupted会使flag标志为true
2.若该线程被阻塞方法(上边的)所阻塞,调用该线程的interrupt()会打断线程的阻塞状态。并且该线程会捕获一个InterruptedException异常。阻塞线程会继续执行,并且会擦出flag标志,即改为false(不影响线程中其他方法的执行)。
可以使用inInterrupted()方法来判断flag标志位。
interrupted也能判断flag标志位。不同的是interrupted判断到标志位为true后会自动的讲标志位改为false。
6.关闭一个线程
stop方法已经过期,那么我们应该如何去关闭一个线程。
1.线程运行结束会自动关闭
2.捕获中断信号。
1)无阻塞方法时
new Thread(){
@Override
public void run(){
while(!isInterrupted())
{
//working
}
}
}.start();
只要在外部调用interrupt()方法就可以关闭线程。
2)有阻塞方法时
new Thread(){
public void run(){
for( ; ;){
//working
try{
Threed.sleep(1);
}catch(InterruptedException e){
break;
}
}
}
}
3.线程安全与线程间通信
1.synchronized
synchronied对方法加锁相当于对this对象加锁、对静态方法加锁相当于对类对象加锁(类名.class)。
2.wait与notify
wait是让当前线程等待,notify是唤醒一个等待的线程。
wait和notify是Object类中的方法。都必须在synchronized锁中使用,并且锁对象必须与调用wait、notift的对象相同。
在线程中调用某一个对象的wait方法,该线程会进入该对象的wait set 集合中。并且该线程会释放monitor锁。若该线程的notify方法被调用则会从set集合中随机找一个线程唤醒。并且被唤醒的线程必须重新争夺monitor锁。
若调用notifyAll方法会唤醒set集合中的所有等待线程。
synchronized的缺点
synchronized无法控制阻塞时长,并且阻塞不可被中断。
3.使用Lock锁
Lock锁可使用的方法
lock() 获取一个锁,若锁被其他线程获取则等待
tryLock() 获取成功返回true 失败返回false 立即返回
tryLock(long time,TimeUnit unit) 在一定时间内等待获取该锁,得到返回true、时间到没有得到该锁返回false.
lockInterruptibly() 使用该方法获取锁时,在等待过程中能够响应中断。
unclock() 释放锁
ReentrantLock是唯一实现了Lock的类。
4.ThreadGroup
ThreadGroup并不能提供对线程的管理,ThreadGroup的主要功能是对线程进行组织。
public ThreadGroup(String name);//指定名称创建ThreadGroup
public ThreadGroup(ThreadGroup parent,String name) //指定父线程组合名称
默认情况下,子线程都会被加入父线程的ThreadGroup。
调用线程的getThreadGroup()可以得到线程所在的ThreadGroup。
ThreadGroup的方法
1.复制数组方法
public int enumerate(Thread[] list ) 将该线程组的所有活跃线程放在list数组中。
public int enumerate(Thread[] list,boolean recurse ) 若为true 将该线程组以及该线程组的所有子线程组中的所有活跃线程 放在list数组中。
返回值表示线程的个数。该方法获取的线程只是个预估值。因为线程的状态随时都可能会改变。
public int enumerate(ThreadGroup[] list )将该线程组的子线程组放在list数组中。
public int enumerate(ThreadGroup[] list,boolean recurse )若为true 将该线程组的所有子线程组放在list数组中。
2.基本操作
activeCount() 获取group中活跃的线程个数 估计值
activeGroupCount()获取group中活跃的子group
setMaxPriority()设置优先级
若线程的优先级大于group的优先级,则线程加入group后优先级会和group的优先级相等。执行该方法还会改变所有子group的优先级
getMaxPriority() 获取优先级
getName()获取group的名字
getParent()获取父group
list()将所有活跃线程输出到控制台
parentOf(ThreadGroup g) 判断父group是否是给定的
interrupt
调用group的interrupt则相当于该group的所有线程都调用了interrupt。
destroy
若Group中没有任何活跃的线程则可以调用destroy在父group中将自己移除。
5捕获线程执行异常
线程在执行单元中不允许抛出checked异常。而且线程运行在自己的上下文中,创造他的线程无法直接获得它运行中出现的异常信息。JAVA提供了一个UncaughtExceptionHandler接口,当线程在运行中出现异常时,会回调UncaugthExceptionHandler接口,从而得知是哪一个线程在运行时出错,以及出了什么样的错误。
关于处理运行时异常的API总共有四个:
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh):
为某个特定线程指定UncaughtExceptionHandler
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh):
设置全局的UncaughtExceptionHandler
public UncaughtExceptionHandler getUncaughtExceptionHandler( ):
获取特定线程的UncaughtExceptionHandler
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler():
获取全局的UncaughtExceptionHandler
看了这些可能还不知道这些方法是干什么的。简单的说就是使用上边的set方法可以在你创建的线程出现异常时,你能够捕获到。(是自己创建的线程,不是自己线程)
至于是怎么实现下边再来解释。
首先方法的参数UncaughtExceptionHandler 他是一个接口,并且接口中只有一个方法uncaughtExceotion()。
public interface UncaughtExceptionHandler{
void uncaughtexception(Thread t, Throwable e);
}
通过重写该方法能够处理创建的线程出现异常后的事情。其中t为出现异常的线程,e为出现异常的原因。
如果创建的线程出现了异常,JVM会首先调用一个dispathUncaughtException方法。而该方法就会调用我们重写过后的uncaughtexception方法。
举一个例子就会很清楚
public class CaptureThreadException{
public static void main(String[] args){
//该线程创建的线程出现异常都会被捕获到,并且执行重写的 uncaughtexception方法
Thread.setsetDefaultUncaughtExceptionHandler((t,e)->
{
System.out.println(t.getName()+"出现了异常");
e.printStackTrace();
});
final Thread thread = new Thread(()->
{
try {
Threed.sleep(2000);
}catch(InterruptedException e){
}
//线程出现异常
System.out.println(1/0);
},"Test-Thread");
thread.start();
}
}
6Hook(钩子)线程
Hook线程即为当JVM进程退出后执行的线程。
可以通过Runtime为JVM注入多个Hook线程。
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
//JVM退出后执行的操作
}
});
在开发中会经常遇到钩子线程。为了防止程序重复启动,可以在程序启动时创建lock文件,收到中断信号后(JVM退出后,使用钩子线程)删除lock文件。
线程池
使用工厂创建线程池
ExecutorService service =Executors.newFixedThreadPool(int numberOfThreads)//创建一个可以并行运行指定数目的线程池。一个线程在任务完成情况下可以重用,来执行另一个任务。
ExecutorService service =Executors.newCachedThreadPool()//创建一个线程池,他会在必要时创建新线程,但是如果之前创建的线程可用,则先重用之前创建的线程。
自己创建线程池
ExecutorService service = new ThreadPoolExecutor(corePoolSize ,maxPoolSize, keepAliveTime , timeUnit, TimeUnit.SECONDS,BlockingQueue);
参数说明:
corePoolSize 核心线程数
maxPoolSize 最大线程数 核心线程数+救急线程数<=最大线程数
keepAliveTime 保持时间 如果一个线程闲暇的时间超过了保持时间,那就把它回收,但不会少于核心线程数
timeUnit 时间单位
BlockingQueue 阻塞队列 当任务数超过核心线程数后,就把任务放入阻塞队列排队运行 (有界,无界)