------- android培训、java培训、期待与您交流! ----------
首先,我们先了解一下什么是线程池?又什么时候使用到线程池技术?
我们生活中很多事情的处理和线程池有很大的关系,比如:火车售票,银行系统等
这些行业内部的高性能服务就是使用到了线程池技术,并且得到安全和高效的保障!
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
那什么时候使用到线程池技术呢?
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建、销毁并管理线程池,将工作线程放入线程池中。
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务
3、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
4、 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,
它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。
它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,
这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
下面我们就逐一去了解有关java线程池使用到的类,及一些相关应用
例子1:
我们先了解一下线程中的定时器:
这种可以使用定时查收邮件的,或者发送邮件(实用): quartz(开源项目)
还有游戏制作的时候可以使用到定时技术。
下面使用线程的定时技术制作一个定时炸弹:代码如下
public class TraditionalTimer {
static int count = 0;
public static void main(String[] args) {
// method1();
// method2();
// method3();
method4();
}
public static void method4() {
Timer t = new Timer();
t.schedule(new MyTimerTask2(), 2000);
timecount();
}
/**
* 方法功能:每隔两秒就爆炸一次
*/
public static void method3() {
Timer t = new Timer();
t.schedule(new MyTimerTask(true), 3000);
timecount();
}
/**
* 方法功能:每隔两秒就爆炸一次
*/
public static void method2() {
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("爆炸");
}
}, 2000, 2000);
timecount();
}
/**
* 方法功能:定时炸弹2秒钟之后爆炸
*/
public static void method1() {
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("爆炸");
}
}, 2000);
timecount();
}
/**
* 方法功能:输出时间计时
*/
public static void timecount() {
new Thread() {
public void run() {
while (true) {
try {
System.out.println(new Date().getSeconds());
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
}
}
// 第一种实现方式:自定义一个定时的任务,间接性的经过2秒钟之后爆炸一次,有经过四秒钟之后爆炸一次,,一次循环
class MyTimerTask extends TimerTask {
private boolean flag = true;
MyTimerTask(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
System.out.println("爆炸");
new Timer().schedule(new MyTimerTask(flag ? false : true), flag ? 2000
: 4000);
}
}
// 第二种实现方式:
class MyTimerTask2 extends TimerTask {
private static int count = 0;
@Override
public void run() {
this.count = (this.count + 1) % 2;
System.out.println("爆炸");
new Timer().schedule(new MyTimerTask2(), 2000 + 2000 * this.count);
}
}
需求:用面试宝典中的子线程循环10次和主线程循环5次,两者交替运行50的例子
为了观察方便,我们可以将运行的结果通过重定向到一个文本文件中,在 run configuration中配置
例子2:
public class TraditionalThreadTest {
public static void main(String[] args) {
final MyThread t = new MyThread();
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 1; j <= 50; j++) {
t.sub(j);
}
}
}).start();
for (int j = 1; j <= 50; j++) {
t.main(j);
}
}
}
class MyThread {
private boolean flag = true;
public synchronized void sub(int j) {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 10; i++) {
System.out.println("sub-------------->" + i + " : " + j);
}
this.flag = true;
this.notify();
}
public synchronized void main(int j) {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 100; i++) {
System.out.println("main--->" + i + " : " + j);
}
this.notify();
flag = false;
}
}
需求:同一线程必须操作同一对象的元素,不然会出现安全问题
一个线程使用一个独立的对象数据,不能使用其他线程对象的数据
ThreadLocal该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联我们通过例子来测试:
例子3:
使用两个线程,并将两个数据分别放入到ThreadLocal类中,
这个类能保证线程之间的数据不会干扰
public class ThreadScopeTest {
// private static Map<Thread, Integer> map = new HashMap<Thread, Integer>();
private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Random r = new Random();
int data = r.nextInt();
System.out.println(Thread.currentThread().getName() + " creat: "
+ data);
// map.put(Thread.currentThread(), data);
x.set(data);
new A().print();
new B().print();
}
}).start();
}
}
static class A {
public void print() {
// int data = map.get(Thread.currentThread());
int data = x.get();
System.out.println("A:" + Thread.currentThread().getName() + ":"
+ data);
}
}
static class B {
public void print() {
// int data = map.get(Thread.currentThread());
int data = x.get();
System.out.println("B:" + Thread.currentThread().getName() + ":"
+ data);
}
}
}
输出结果:A:Thread-0:-704470354 A:Thread-1:-795936612 B:Thread-1:-795936612
B:Thread-0:-704470354
结论:同一线程使用的是自己的数据,不能使用其他线程的数据
需求:测试同一线程在执行过程中,自己使用的对象不会被其他线程使用,他们之间是互斥的
B:Thread-0:-704470354
结论:同一线程使用的是自己的数据,不能使用其他线程的数据
需求:测试同一线程在执行过程中,自己使用的对象不会被其他线程使用,他们之间是互斥的
public class ThreadLocalTest {
private static ThreadLocal<MyTreadScopeData> map = new ThreadLocal<MyTreadScopeData>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Random r = new Random();
int data = r.nextInt();
MyTreadScopeData my = MyTreadScopeData.getInstance();
my.setAge(data);
my.setName("name" + data);
new A().print();
new B().print();
}
}).start();
}
}
static class A {
public void print() {
MyTreadScopeData data = MyTreadScopeData.getInstance();
System.out.println("A:" + Thread.currentThread().getName() + ":"
+ data);
}
}
static class B {
public void print() {
MyTreadScopeData data = MyTreadScopeData.getInstance();
System.out.println("B:" + Thread.currentThread().getName() + ":"
+ data);
}
}
}
class MyTreadScopeData {
private static ThreadLocal<MyTreadScopeData> map = new ThreadLocal<MyTreadScopeData>();
private MyTreadScopeData() {
}
public static MyTreadScopeData getInstance() {
MyTreadScopeData instance = map.get();
if (instance == null) {
instance = new MyTreadScopeData();
map.set(instance);
}
return instance;
}
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return " [name=" + name + ", age=" + age + "]";
}
}
Executor类的应用:(用于创建线程池)
(在线程执行完任务之后,线程是不会消亡的)可以使用ExecutorService.shutdown()将线程池中的线程关闭
使用线程池技术,是为了减少线程在创建和销毁的过程中所浪费的资源和时间
线程池中的线程没有接收到任务的时候,线程将出入wait状态,当有任务进入时,将唤醒notify线程
tomcat服务器就是使用线程池的原理
我们可以使用这个方法将线程池中的线程进行消亡(默认是不会关闭线程的)
ThreadPool.shutdown();
ThreadPool.shutdown();
使用Executor的静态 newFixedThreadPool()方法创建线程池,并 指定线程数量
使用ExecutorService类接受线程池
ExecutorService是固定的线程池
ExecutorService ThreadPool = Executors.newFixedThreadPool(3);
使用线程池的缓冲技术,线程的数量会顺着任务的多少而改变,
当任务多的时候.可以动态的创建线程的数量来完成任务,
ExecutorService ThreadPool = Executors.newCachedThreadPool();
创建一个单一线程,当线程池中的单一线程死亡之后,会自动创建一个新的线程保证线程池中有唯一一个线程
ExecutorService ThreadPool = Executors.newSingleThreadExecutor();
例子:创建缓冲线程池,会动态创建线程,一般线程数量和任务数量相等
例子:创建缓冲线程池,会动态创建线程,一般线程数量和任务数量相等
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService ThreadPool = Executors.newCachedThreadPool();
// 往线程池中添加10个任务
for (int i = 0; i <= 10; i++) {
final int task = i;
ThreadPool.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j <= 10; j++) {
try {
Thread.sleep(20); // 让线程休眠一会
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " loop of " + j + "for task " + task);
}
}
private void extracted() throws InterruptedException {
Thread.sleep(20);
}
});
}
System.out.println("添加完成十个任务");
ThreadPool.shutdownNow();
}
}
Lock类比传统线程模型中的sychronized方式更加面向对象,
实现允许更灵活的结构,可以具有差别很大的属性
Lock
实现提供了比使用
synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的
Condition
对象。
与生活中的锁类似,锁本身也应该是一个对象,2个线程执行的代码片段要实现同步互斥的效果,
他们必须用同一个Lock对象,
读写锁:分为读锁和写锁,多个读硕不互斥,读锁与写锁互斥,写锁和写锁互斥,
这是有JVM自己控制的,你只要上好相应的锁即可,
如果你代码只读数据,可以很多人同时读,但是不能同时写,那就使用读锁;
如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁
总之,读的时候上读硕,写的时候上写锁
例子:
例子:
public class LockTest {
public static void main(String[] args) {
final Add add1 = new Add();
// 创建2个线程进行打印名字
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
add1.add("---------");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
add1.add("++++++++++");
}
}
}).start();
}
}
class Add {
Lock lock = new ReentrantLock();
public void add(String name) {
// 枷锁
lock.lock();
try {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
System.out.println();
} finally {
lock.unlock(); // 使用finally捕捉,使得代码更加安全
}
}
}
ReadWriteLock 维护了一对相关的
锁
,一个用于只读操作,另一个用于写入操作。
只要没有 writer,读取锁
可以由多个 reader 线程同时保持。写入锁
是独占的。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。
一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。
不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock
的读取锁。
例子: 需求:模拟Hibernate获取数据库信息
public class CacheTest {
private Map<String, Object> cache = new HashMap<String, Object>();
public static void main(String[] args) {
}
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key) {
// 上读锁
rwl.readLock().lock();
Object value = null;
try {
value = cache.get(key);
if (value == null) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (value == null) {
value = "aaaa"; // 模拟数据库获取数据,当内存中没有资源时,到数据库中获取
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
} finally {
rwl.readLock().unlock(); // 解锁
}
return value;
}
}
例子:需求:制作一个定时器,程序运行6秒后,爆炸一次,以后每隔2秒爆炸一次
public class ExecutorTest {
public static void main(String[] args) {
Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("爆炸");
}
}, 6, 2, TimeUnit.SECONDS);
}
}
Condition 类
将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
Condition 实例实质上被绑定到一个锁上。
要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法
注意,
Condition
实例只是一些普通的对象,它们自身可以用作
synchronized
语句中的目标,
并且可以调用自己的
wait
和
notification
监视器方法。获取
Condition
实例的监视器锁或者使用其监视器方法,
与获取和该
Condition
相关的
Lock
或使用其
waiting
和
signalling
方法没有什么特定的关系。
为了避免混淆,建议除了在其自身的实现中之外,切勿以这种方式使用
Condition
实例。
例子:
需求:有三个线程,主线程,二线程,三线程,它们依次排序执行循环十次(主线程----> 二 线程---->三线程)
public class ThreadConditionCommunication {
public static void main(String[] args) {
final ThreadQueue t = new ThreadQueue();
// 创建线程2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
t.subThread2(i);
}
}
}).start();
// 创建线程3
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
t.subThread3(i);
}
}
}).start();
//主线程执行的部分
for (int i = 0; i < 50; i++) {
t.main(i);
}
}
}
class ThreadQueue {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int queue = 1;
public void subThread2(int j) {
lock.lock();
try {
while (queue != 2)
condition2.await();
for (int i = 0; i < 10; i++) {
System.out.println("sub2 ---------->" + i + ":" + j);
}
queue = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void subThread3(int j) {
lock.lock();
try {
while (queue != 3)
condition3.await();
for (int i = 0; i < 10; i++) {
System.out.println("sub3---------->" + i + ":" + j);
}
queue = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void main(int j) {
lock.lock();
try {
while (queue != 1)
condition1.await();
for (int i = 0; i < 10; i++) {
System.out.println("main----------->" + i + ":" + j);
}
queue = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
例子:
需求:定义一个工厂 ,工厂容量为100;
使用三个线程生产 ,三个线程消费
public class InOutFactory {
public static void main(String[] args) throws Exception {
final Factory factory = new Factory();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Random r = new Random();
factory.put(r.nextInt());
}
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println(Thread.currentThread().getName()
+ "消费:" + factory.take());
} catch (InterruptedException e) {
System.out.println("消费失败");
}
}
}
}
).start();
}
}
}
class Factory {
Lock lock = new ReentrantLock();
final Condition noFull = lock.newCondition();
final Condition noEmpty = lock.newCondition();
private Object[] items = new Object[100];
int putptr, takeptr, count;
/**
* 生产方法
*/
public void put(Object x) {
lock.lock();
try {
while (count == items.length)
// 当生产的数量大于一百的时候停止生产
noFull.await();
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
count++;
System.out.println(Thread.currentThread().getName() + "生产:" + x);
Thread.sleep(1000);
noEmpty.signal();
} catch (Exception e) {
System.out.println("生产出现异常");
} finally {
lock.unlock();
}
}
/**
* 消费的方法
*
*/
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
noEmpty.await();
}
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0;
--count;
noFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
Semaphore类实现信号灯
Semaphore可以维护当前访问自身的线程个数,并提供同步线程机制。
例如:实现一个文件允许的并发访问数
Semaphore实现的功能类似厕所有5个坑,假如有10个人上厕所,那么同时只能有5个人能够占用,
当5个人中的任何一个人让开后,其中在等待的另外5个人中有一个可以占用了
单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁” 再由另一个线程放“锁”,
这可应用于死锁恢复的一些场合
一个计数信号量。从概念上讲,信号量维护了一个许可集。
如有必要,在许可可用前会阻塞每一个 acquire()
,然后再获取该许可。
每个release()
添加一个许可,从而可能释放一个正在阻塞的获取者。
但是,不使用实际的许可对象,Semaphore
只对可用许可的号码进行计数,并采取相应的行动。
例子:
需求:有资源数目为3,多个线程排队使用资源,只能有三个线程同时使用资源
public class SemaphoreTest {
public static void main(String[] args) {
//创建线程缓冲池,有多少任务将创建对应数量的线程个数
ExecutorService ThreadPool = Executors.newCachedThreadPool();
//定义资源的数量为3个
final Semaphore sp = new Semaphore(3);
for (int i = 0; i < 10; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
//从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
sp.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
//availablePermits()返回此信号量中当前可用的许可数
System.out.println("线程" + Thread.currentThread().getName()
+ "进入,当前已有" + (3 - sp.availablePermits()));
try {
Thread.sleep((long) (Math.random() * 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName()
+ "即将离开");
// 释放一个许可,将其返回给信号量。
sp.release();
System.out.println("线程" + Thread.currentThread().getName()
+ "已离开,当前已有" + (3 - sp.availablePermits()));
}
};
ThreadPool.execute(runnable);
}
}
}
Future类用于接受线程返回结果;
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
取消则由 cancel 方法来执行。
还提供了其他方法,以确定任务是正常完成还是被取消了。
一旦计算完成,就不能再取消计算。
如果为了可取消性而使用 Future 但又不提供可用的结果,
则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。
它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<String> futuer = threadPool.submit(new Callable<String>(){
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "hello";
}
});
System.out.println("等待线程返回的结果:");
try {
//等待线程返回的结果
// System.out.println("线程返回的结果:" + futuer.get());
//Futuer.get(1,TimeUnit.SECONDS)指定1秒内接收不到数据,就报出异常
System.out.println("线程返回的结果:" + futuer.get(2,TimeUnit.SECONDS));
} catch (Exception e) {
e.printStackTrace();
}
}
}
CompletionService类
将生产新的异步任务与使用已完成任务的结果分离开来的服务。
生产者 submit 执行的任务。 使用者 take
已完成的任务,并按照完成这些任务的顺序处理它们的结果。
例子:
例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。
通常, CompletionService 依赖于一个单独的Executor
来实际执行任务,在这种情况下,
CompletionService
只管理一个内部完成队列。
ExecutorCompletionService
类提供了此方法的一个实现。例子:
public class CompletionServiceTest {
public static void main(String[] args) {
//创建线程池,并往线程池中添加十个线程
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(
threadPool);
//向线程中添加十个任务
for (int i = 0; i < 10; i++) {
final int temp = i;
completionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//随机睡眠时间
Thread.sleep(new Random().nextInt(5000));
return temp;
}
});
}
for (int i = 0; i < 10; i++) {
try {
System.out.println(completionService.take().get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}