简述线程,程序、进程的基本概念
线程:与进程相似,但线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序:是含有指令和数据文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态代码。
进程:是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,
因为同一进程中的线程极有可能会相互影响。
线程有哪些基本状态
Java 线程在运行的生命周期中可能处于下面 6 种不同状态
新建(NEW)可运行(RUNNABLE)阻塞(BLOCKED)等待(WAITING)超时等待(TIMED_WAITING)终止(TERMINATED)
可运行状态是个复合状态,又可以分为READY:就绪状态。RUNNING:表示该线程正在执行。
如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决
内存泄漏:对于应用程序来讲,当对象不再被使用的时候,但是java的垃圾回收不能回收他们,就产生了内存泄漏。
上图中包含了未引用对象和引用对象。未引用对象将会被垃圾回收器回收,而引用对象却不会。未引用 对象很显然是无用的对象。然而,无用的对象并不都是未引用对象,有一些无用对象也有可能是引用对象,这部分对象正是内存泄露的来源。
比如说:对象A引用了对象B,A的生命周期比B的生命周期长,当对象B执行完,垃圾回收器是不会回收对象B的,这就可能造成内存不足的问题。因为A还引用着B,还可能引用其他比A生命周期短的对象。
怎样阻止内存泄露
1.使用List、Map等集合时,在使用完成后赋值为null
2.使用大对象时,在用完后赋值为null
3.目前已知的jdk1.6的substring()方法会导致内存泄露
4.避免一些死循环等重复创建或对集合添加元素,撑爆内存
5.简洁数据结构、少用静态集合等
6.及时的关闭打开的文件,socket句柄等
7.多关注事件监听(listeners)和回调(callbacks),比如注册了一个listener,当它不再被使用的时候,忘了注销该listener,可能就会产生内存泄露。
线程池的原理,为什么要创建线程池?创建线程池的方式;
线程池的优点:
1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。
2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
线程池的实现原理:
线程池的创建:
/**
* corePoolSize:线程池核心线程数量
* maximumPoolSize:线程池最大线程数量
* keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
* unit:存活时间的单位
* workQueue:存放任务的队列
* handler:超出线程范围和队列容量的任务的处理程序
* @return
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
线程池的源码解读
1、ThreadPoolExecutor的execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//如果线程数大于等于基本线程数或者线程创建失败,将任务加入队列
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)){
//线程池处于运行状态并且加入队列成功
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//线程池不处于运行状态或者加入队列失败,则创建线程(创建的是非核心线程)
else if (!addIfUnderMaximumPoolSize(command)){
//创建线程失败,则采取阻塞处理的方式
reject(command); // is shutdown or saturated
}
}
}
2、创建线程的方法:addIfUnderCorePoolSize(command)
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask);
}finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
我们重点来看第7行,这里将线程封装成工作线程worker,并放入工作线程组里:
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w);
if (t != null) {
w.thread = t;
workers.add(w);
int nt = ++poolSize;
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
worker类的方法run方法,worker在执行完任务后,还会通过getTask方法循环获取工作队里里的任务来执行:
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
由于线程池的饱和策略内容过多,我将重新记录一篇来讲Java提供的4中策略:
1、AbortPolicy:直接抛出异常
2、CallerRunsPolicy:只用调用所在的线程运行任务
3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。