并发基础总结
Runnable和Thread要点:
- 当从Runnable导出一个类时,他必须具有run方法,但是这个方法并无特殊之处-----他不会产生任何内在的线程能力.要实现线程行为,你必须现实的将一个任务附着到线程上.
- 将Runnable对象转变为工作任务的传统方式就是把它提交给一个Thread构造器.
- 当main创建Thread对象时,他并没有捕获任何的这些对象的引用,在使用普通对象时,这对于垃圾回收来说是一场公平的游戏,但是在使用Thread时,情况就不同了,每个Thread都注册了它自己,因此确实有一个对它的引用,而且在它的任务退出其run()并死亡之前,垃圾回收器无法清除它.
并行和并发
- 并发:从宏观方面来讲.并发就是同时进行多种事件,实际上,这几种事件,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情.
- 并行:则是真正意义上的同时进行多种事情,这种只可以在多核CPU的基础上完成.
线程池详解
线程池概念
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程.而创建并销毁线程的过程势必会消耗内存.而在Java中内存又是极其宝贵的,所以我们就提出了线程池的概念.,所以从应用场景看,线程池的好处就是可以方便的管理线程,也可以减少内存的消耗.
java.util.concurrent包中的执行器(Executor)将为你管理Thread对象.Executor在客户端和任务执行之间提供了一个间接层.Executor允许你管理异步任务的执行,而无需显式的管理线程的生命周期.
线程池底层原理
线程池的接口是Executor这个接口,具体实现为ThreadPoolExecutor类,对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这就是ThreadPoolExecutor的构造器
-
int corePoolSize:该线程池中核心线程数最大值,线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程,核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干。
-
int maximumPoolSize:该线程池中线程总数最大值。线程总数=核心线程数+非核心线程数。
-
long keepAliveTime:非核心线程闲置超时时常,如果一个非核心线程不干活的时间超过这个参数所规定的,就会被销毁掉。
-
TimeUnit unit:TimeUnit是一个枚举类型,其包括:NANOSECONDS; MICROSECONDS; MILLISECONDS; MINIUTES; HUOURS;DAYS
-
BlockingQueue workQueue :该线程池汇中的任务队列,维护着等待执行的Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待,如果队列满了,则新建非核心线程执行任务.
常用的workQueue类型:
-
SynchronousQueue:这是一种没有内部容量的阻塞队列,因此每个put()都必须等待一个take(),反之亦然.这个队列收到任务时,会直接提交给线程处理,而不保留它.就好比你在把一个对象交给某人,但是没有任何桌子放置这个对象,因此只有在这个伸出手,准备好接对象时,你才能工作.
-
LinkedBlockingQueue:这个队列没有最大值,即所有超过核心线程数的任务都将被添加到队列中.
-
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建核心线程执行任务,如果达到了,则入队等待,如果队列已满,则新建非核心线程执行任务,如果总线程数达到了maximumPoolSize,并且队列也满了,则发生错误.
-
DelayQueue
-
四种常见的线程池
CachedThreadPool()
可缓存线程池:
1.线程数无限制(没有核心线程,全部是非核心线程)
2.有空闲线程则复用空闲线程,若无空闲线程在新建线程
3.一定程度减少频繁创建/销毁线程,减少系统靠小
创建方法:
ExecutorService service = Executors.newCachedThreadPool();
源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
FixedThreadPool()
定续航线程池:
1.有核心线程,核心线程数就是线程的最大数量(没有非核心线程)
2.可控制线程最大并发数
3.超出的线程会在队列中等待
创建方法:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
SingleThreadExecutor()
单线程化的线程池:
1.有且仅有一个工作线程执行任务
2.所有任务按照指定顺序执行,即遵循队列的入队出队规则。
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
线程和线程池的比较
线程:
- 每次都新建,性能较差
- 线程缺乏统一管理,可能无限制的创建线程,互相竞争
- 可控性太差,比如定时定期执行
线程池:
- 降低资源消耗,不是每次都需要新建
- 统一管理,可有效控制最大并发量,提高资源利用率,避免过多资源竞争
- 可控性很好,可以定期定时执行、线程中断机制等。
从任务中产生返回值
Sleep()和Join()
Sleep()和Join()都有阻塞线程的作用.但是他们的用途却不同.
- sleep()用于让当前线程沉睡一段指定时间
- 而join()用在,很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束.但是这时如果主线程想等待子线程执行完成后再结束,比如子线程处理一个数据,主线程要取得这个数据的值,就要用到join()方法了.方法join()的作用是等待线程对象销毁.
- 同样的和sleep一样,join()在遇到interrupt()时也会发生异常.
join()方法示例:
class Sleeper extends Thread {
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch(InterruptedException e) {
print(getName() + " was interrupted. " +
"isInterrupted(): " + isInterrupted());
return;
}
print(getName() + " has awakened");
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join();
} catch(InterruptedException e) {
print("Interrupted");
}
print(getName() + " join completed");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumpy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
} /* Output:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*///:~
可以看到无论是否Sleeper是否中断或是正常结束,Joiner都必须等待Sleeper.isAlive()返回为假是才能执行.因为他们的run方法首行就调用了sleeper.join().
可以看到上面的is.Interrupted()是返回false的,这有点反常,这是因为:在Thread对象上调用isInterrupted()方法可以检查任何线程的中断状态,但是:线程一旦被中断,isInterrupted()方法便会返回true,而一旦sleep()方法抛出异常,它将恢复中断标志,isInterrupted()方法在此时就会返回false。
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join()具有释放锁的特点.
方法join(long)的示例代码:
public class ThreadB extends Thread {
@Override
public void run() {
try {
System.out.println("b run begin timer=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("b run end timer=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void bService() {
System.out.println("打印了bService timer=" + System.currentTimeMillis());
}
}
public class ThreadC extends Thread {
private ThreadB b;
public ThreadC(ThreadB b) {
super();
this.b = b;
}
@Override
public void run() {
b.bService();
}
}
public class Run15_join_sleep_2 {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA2 a = new ThreadA2(b);
a.start();
Thread.sleep(1000);
ThreadC c = new ThreadC(b);
c.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ThreadA2 extends Thread {
private ThreadB b;
public ThreadA2(ThreadB b) {
super();
this.b = b;
}
@Override
public void run() {
try {
synchronized (b) {
b.start();
b.join();
System.out.println("a run before for " + System.currentTimeMillis());
for (int i = 0; i < 100000000; i++) {
Math.random();
}
System.out.println("a run end " + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
输出:
b run begin timer=1460637677744
打印了bService timer=1460637678746
b run end timer=1460637682745
a run before for 1460637682745
a run end 1460637684923
*/
可以看到在a线程中调用b.join()后,会释放掉a线程获得的b对象的锁,所以c线程拿到了这个锁,执行了bService()方法.
而sleep(long)是不会释放锁的,下面是sleep(long)的示例:
public class ThreadA extends Thread {
private ThreadB b;
public ThreadA(ThreadB b) {
super();
this.b = b;
}
@Override
public void run() {
try {
synchronized (b) {
b.start();
Thread.sleep(6000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run15_join_sleep {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
Thread.sleep(1000);
ThreadC c = new ThreadC(b);
c.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
输出:
b run begin timer=1460637031625
b run end timer=1460637036626
打印了bService timer=1460637037626
由输出结果可以看到,c线程在a线程执行完毕后,才获得了b对象的锁.
线程的优先级
- 记住当线程的优先级没有指定时,所有线程都携带普通优先级。
- 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
- 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
- t.setPriority()用来设定线程的优先级。
- 记住在线程开始方法被调用之前,线程的优先级应该被设定。
- 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。
线程的让步Thread.yeild()
从原理上讲其实Thread.yeild方法其实只是给线程调度机制一个暗示:我的任务处理的差不多了,可以让给相同优先级的线程CPU资源了;不过确实只是一个暗示,没有任何机制保证它的建议将被采纳.
public class LiftOff implements Runnable {
protected int countDown = 5;
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() { }
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
// System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName() + "#" + id + "(" + (countDown > 0 ? countDown + ")" : "Liftoff!" + ")");
}
@Override
public void run() {
// TODO Auto-generated method stub
/*
* --countDown:先减去,再赋值
* countDown--先赋值,再减去
*/
while (countDown-- > 0) {
System.out.println(status());
Thread.yield();// 让步【表示我的任务处理的差不多了,可以让步给其他线程资源了】;
//CPU资源给其他线程使用;t.join当前线程挂起,直到t线程调用结束后切回
}
}
public class ExecuteMethodPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
pool-1-thread-2#1(4)
pool-1-thread-1#0(4)
pool-1-thread-3#2(4)
pool-1-thread-2#1(3)
pool-1-thread-3#2(3)
pool-1-thread-3#2(2)
pool-1-thread-1#0(3)
pool-1-thread-2#1(2)
pool-1-thread-3#2(1)
pool-1-thread-1#0(2)
pool-1-thread-2#1(1)
pool-1-thread-3#2(Liftoff!)
pool-1-thread-1#0(1)
pool-1-thread-2#1(Liftoff!)
pool-1-thread-1#0(Liftoff!)
以上标注的两个一样的线程先后执行 并没有遵守yeild给线程调度的建议.
后台线程
所谓后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中或不可缺的部分.因此当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中所有的后台线程.反过来说只要有任何非后台线程还在运行,程序就不会终止.比如,执行main()的就是一个非后台线程.
daemon线程的特点:
- A.守护线程创建的过程中需要先调用setDaemon方法进行设置,然后再启动线程.否则会报出IllegalThreadStateException异常.(问题:为什么不能动态更改线程为daemon线程?)
- B.由于daemon线程的终止条件是当前是否存在用户线程,所以我们不能指派daemon线程来进行一些业务操作,而只能服务用户线程.
- C.daemon线程创建的子线程任然是daemon线程.
//对应于A的特点
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Thread2());
thread1.start();
thread1.setDaemon(true);
Thread.sleep(10);
System.out.println("用户线程退出");
}
}
class Thread2 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("1+1="+(1+1));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1352)
at ThreadTest.main(ThreadTest.java:5)
1+1=2
通过源码可以发现,如果setDaemon()的线程是alive的,我们的程序就会抛出异常
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
//对应于B的特点
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Thread2());
thread1.setDaemon(true);
thread1.start();
Thread.sleep(10);
System.out.println("用户线程退出");
}
}
class Thread2 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("1+1="+(1+1));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果: 用户线程退出
上面并没有获取到任何信息,是因为为一个非后台线程也就是用户线程main已经早早结束,所以JVM直接杀死了非后台线程.