文章目录
- 第四章 Java并发编程基础
- 框架图
- 线程简介
- 线程优先级
- 线程的状态
- 启动和终止线程
- 理解中断
- 线程间通信
- 等待/通知机制
- 管道输入/输出流
- Thread.join()的使用
- ThreadLocal的使用
- 线程应用实例
- 等待超时模型
- 数据库连接池示例
- 线程池技术及其示例(感觉有用,但是先跳过吧)
第四章 Java并发编程基础
框架图
线程简介
进程:现代操作系统在运行一个程序时,会为其创建一个进程。
线程:现代操作系统调度的最小的单元是线程,也叫轻量级进程,一个进程里面可以有多个线程,这些线程都有自己的计数器、栈堆、局部变量,还能访问共享变量。
(是否可以理解进程是包工头承包的一个项目,他需要找人干活,线程就是手底下的工人,具体来完成这个项目)
Java程序:
- Java天生就是多线程,一个main方法的运行时,会有main线程和多个其他线程同时运行。
- 查看线程代码:
public class MultiThread {
public static void main(String[] args) {
// 获取Java线程管理器
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 返回全部的线程,并且带有栈踪迹和同步信息
// 两个参数决定是否dump monitor和sync
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos){
// 打印线程ID和名字
System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
}
}
}
- 输出结果,比书上还多两个线程:
[6]Monitor Ctrl-Break
[5]Attach Listener
[4]Signal Dispatcher
[3]Finalizer
[2]Reference Handler
[1]main
使用多线程的原因:
- 更多的处理器核心:一个线程在一个时刻只能运行在一个处理器核心上,如果只有单线程无法充分利用多个处理器。(反过来就是说,一个处理器的时候,不一定适合用多线程?)。
- 更快的响应时间:可以将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列),例如描述,订单信息、库存信息同时处理。
- 更好的变成模型:Java自己提供了模型,有利于研究如何写好,而不是怎么写。
线程优先级
可以多线程的原理:现代操作系统基本用时分的形式调度线程,将操作系统分成很多小片,然后分配给线程,线程用完了就发生线程调度,等待下次分配。
线程优先级:
- 决定线程分配时间片的数量。
- 范围从1~10,默认为5。
- 可通过
setPriority(int)
方法修改优先级。 - 对于频繁阻塞的(休眠或者I/O操作)线程,设置较高优先级;对于偏重计算的,设置较低的优先级。
- 不同的JVM及操作系统中,线程规划会存在差异,有的甚至会忽略对线程优先级的设定。
优先级设置实例:
在win的环境下,设置是有效的。
public class Priority {
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws InterruptedException {
List<Job> jobs = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 前五个最小优先级,后五个最大优先级
int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
// 这个类里面的priority只是为了做标记
Job job = new Job(priority);
jobs.add(job);
// 第一个参数是要调用的对象,当线程start的时候,就调用他的run
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();
}
// 之前所有的都start,然后都陷在了while循环中
// 这里一改,全部就都动起来了
notStart = false;
// 为什么要等待呢?
// 答:其他线程已经启动了,这个等待并不会停止其他线程,应是只停止了main自己的
// 留出10s的时间,方便其他计算线程体现出差距
TimeUnit.SECONDS.sleep(10);
notEnd = false;
for (Job job : jobs){
System.out.println("Job Priority : " + job.priority + ", Count: " + job.jobCount);
}
}
// 以实现Runnable来控制线程
static class Job implements Runnable{
private int priority;
private long jobCount;
// 构造函数
public Job(int priority){
this.priority = priority;
}
@Override
public void run() {
while (notStart){
// 提示调度程序当前线程愿意放弃当前对处理器的使用。
// 调度器可以忽略这个提示。
Thread.yield();
}
while (notEnd){
// 可以让出cpu,受优先级影响?
// 让出完了再抢回来,看谁抢的厉害?
Thread.yield();
jobCount++;
}
}
}
}
Job Priority : 1, Count: 4305207
Job Priority : 1, Count: 4097408
Job Priority : 1, Count: 4047924
Job Priority : 1, Count: 4051995
Job Priority : 1, Count: 4278124
Job Priority : 10, Count: 6725872
Job Priority : 10, Count: 6589712
Job Priority : 10, Count: 7407455
Job Priority : 10, Count: 7430622
Job Priority : 10, Count: 6347184
线程的状态
六种状态表:
案例代码:
想表达的就是有多种状态?
public class ThreadState {
public static void main(String[] args) {
new Thread(new TimeWaiting(), "TimeWaitingThread").start();
new Thread(new Waiting(),"WaitingThread").start();
// 一个能获得锁,一个得不到锁
new Thread(new Blocked(),"BlockedThread-1").start();
new Thread(new Blocked(), "BlockedThread-2").start();
}
// 该线程不断地进行睡眠
static class TimeWaiting implements Runnable{
@Override
public void run() {
while (true){
SleepUtils.second(100);
}
}
}
// 该线程在Waiting.class实例上等待
static class Waiting implements Runnable{
@Override
public void run() {
while (true){
synchronized (Waiting.class){
try {
// 使线程进入等待状态,不过这里为什么使针对class的?
// 所有的wait class都等待
// 除非其他调用了notify或notifyAll
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 该线程在Blocked.class实例上加锁后,不会释放该锁
// 就自己一直不停,间断性sleep
static class Blocked implements Runnable{
@Override
public void run() {
synchronized (Blocked.class){
while (true){
SleepUtils.second(100);
}
}
}
}
}
jstack
查看的结果,可以看多种状态
"BlockedThread-2" #14 prio=5 os_prio=0 tid=0x00000000195bf800 nid=0x2a94 waiting for monitor entry [0x000000001a43f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at C4.ThreadState$Blocked.run(ThreadState.java:50)
- waiting to lock <0x00000000d64951c0> (a java.lang.Class for C4.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
"BlockedThread-1" #13 prio=5 os_prio=0 tid=0x00000000195bc800 nid=0x305c waiting on condition [0x000000001a33f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at C4.SleepUtils.second(SleepUtils.java:8)
at C4.ThreadState$Blocked.run(ThreadState.java:50)
- locked <0x00000000d64951c0> (a java.lang.Class for C4.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
"WaitingThread" #12 prio=5 os_prio=0 tid=0x00000000195b2000 nid=0x1a64 in Object.wait() [0x000000001a23e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6492c30> (a java.lang.Class for C4.ThreadState$Waiting)
at java.lang.Object.wait(Object.java:502)
at C4.ThreadState$Waiting.run(ThreadState.java:33)
- locked <0x00000000d6492c30> (a java.lang.Class for C4.ThreadState$Waiting)
at java.lang.Thread.run(Thread.java:748)
"TimeWaitingThread" #11 prio=5 os_prio=0 tid=0x00000000195ad000 nid=0x2f50 waiting on condition [0x000000001a13e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at C4.SleepUtils.second(SleepUtils.java:8)
at C4.ThreadState$TimeWaiting.run(ThreadState.java:18)
at java.lang.Thread.run(Thread.java:748)
Java线程切换图:
- 运行和就绪两个状态合称为运行状态。
- 等待和超时等待是同一级别的,都可以通过其他线程的通知结束等待,但超时等待还可以通过等待时间完成来结束等待。
- 调用同步方法时,如果得不到锁就会进入阻塞。(阻塞状态在
java.concurrent
包中的Lock
接口的线程状态是等待状态)。 - 线程运行完了
run
方法就会进入终止。
Daemon线程:
- 一种支持型线程,主要被用作程序中后台调度以及支持性工作。
- 当JVM不存在非Daemon线程的时候,JVM就会退出。
- 可以通过
Thread.setDaemon(true)
将线程设置为Daemon线程,但是这个设置要在start
之前。 - Daemon的
finally
块不一定会执行,所以不要依赖finally
块中的内容来确保执行关闭或清理资源的逻辑。
启动和终止线程
构造线程:
- 初始化源码:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 当前线程就是父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
- 为何当前线程就是父线程?
- 新构造的线程对象是由其parent线程来进行空间分配的,然后会继承很多parent的属性,最后分配一个唯一的ID来指定这个线程。
- 线程初始化完之后,存放在堆中。
启动线程:调用start()
方法即可,此时线程通知JVM,只要线程规划器空闲,就立即启动调用start()
方法的线程。
理解中断
什么是中断?:可以理解为一个线程的标志位属性,表示运行的线程被其他线程进行了中断操作。
如何进行中断?:A在运行,B通过调用A的interrupt()
方法对A进行中断。
如何知道被中断?:线程通过检查自身中断来进行响应,可以通过isInterrupted()
方法来判断是否存在中断标志位,还可以用静态方法Thread.interrupted()
对当前线程的中断标志位进行复位。注意:如果程序已经终止了,那么标志位就会消失,即使被中断过,isInterruped()
方法也会返回false
。
相关异常:InterruptedException
,抛出这个异常的之前,JVM会先清除中断标志位,然后再抛出异常。
案例:
看抛出异常的程序有没有中断位标志。
public class Interrupted {
public static void main(String[] args) throws InterruptedException {
// 不停休眠的进程
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
// 设置这个有啥用?
sleepThread.setDaemon(true);
// 一直运行
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠,让运行一阵
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("busyThread interrupted is " + busyThread.isInterrupted());
SleepUtils.second(2);
}
static class SleepRunner implements Runnable{
@Override
public void run() {
while (true){
SleepUtils.second(10);
}
}
}
static class BusyRunner implements Runnable{
@Override
public void run() {
while (true){
}
}
}
}
输出结果:
SleepThread interrupted is false
busyThread interrupted is true
java.lang.InterruptedException: sleep interrupted
过期的suspend()、resume()、stop():
- 分别对线程进行暂停、恢复、停止,但是这几个方法有问题。
- suspend():调用后线程不会释放资源,会带着锁进入睡眠状态,容易引发死锁问题。
- stop():终结一个线程是可能跟不会给线程释放资源的机会,也会导致不稳定。
安全地终止线程:自己通过标志控制是否停止,调用自定义的cancel()
方法,这种方法有机会清理资源。
public class Shutdown {
public static void main(String[] args) throws InterruptedException {
Runner one = new Runner();
Thread countThread = new Thread(one, "C one");
countThread.start();
// 等1s后就中断
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "C two");
countThread.start();
TimeUnit.SECONDS.sleep(1);
// 这次没有通过线程的方法,而是自定义的一个方法来停止
two.cancel();
}
private static class Runner implements Runnable{
private long i;
// 控制开关
private volatile boolean on = true;
@Override
public void run() {
// 没有停止或中断的情况下,执行
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("count: i = " + i);
}
public void cancel(){
on = false;
}
}
}
输出结果:
count: i = 769538591
count: i = 790801793
线程间通信
关键字:volatile修饰变量;sync修饰方法或同步块,保证多线程同一时刻只有一个线程处于方法或同步块中,保证了线程对变量访问的可见性和排他性。
Sync:实现方法本质上是对对象的监视器的获取,而且这个过程是排他的,一次只有一个线程能获得sync保护的监视器。没获得的就会进入阻塞队列。
等待/通知机制
问题描述:线程之间是要进行通信的,那么改变变量的可以称为生产者,对改变变量做出响应的可以称为消费者,但是消费者如何获取生产者的信息是个问题,一直查询的话太耗资源了,间断地sleep然后再查询地话,又控制不好sleep的时间。
解决方法:使用等待通知机制,我直接告诉你完事了,你可以动了。
等待/通知机制方法:通过一个O对象来进行交互,A可以通过O的wait()
方法让自身进入等待,B可以通过调用O的notify()
方法来让O对象管理的任意一个线程取消等待,或者用notifyAll()
方法让O对象管理的全部线程取消等待。
案例:
public class WaitNotify {
static boolean flag = true;
// 指定一个对象作为锁
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable{
@Override
public void run() {
System.out.println("11111");
// 加锁,拥有lock的监视器
synchronized (lock){
// 使用while,这样就可以在从wait返回后仍然判断条件
while (flag){
// 持续wait,同时释放了lock的锁
try {
System.out.println(Thread.currentThread() + " flag is true " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread() + " flag is false " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable{
@Override
public void run() {
// 加锁
synchronized (lock){
// 获取了锁,然后进行通知,通知不会释放lock锁
// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
System.out.println(Thread.currentThread() + " hold lock " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
// 再次加锁
// 一个方法两个同步代码块
synchronized (lock){
System.out.println(Thread.currentThread() + " hold lock again " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
输出结果:
11111
Thread[WaitThread,5,main] flag is true 12:50:04
Thread[NotifyThread,5,main] hold lock 12:50:05
Thread[NotifyThread,5,main] hold lock again 12:50:10
Thread[WaitThread,5,main] flag is false 12:50:15
分析:
- 先执行的
Wait
类的start()
,所以该类先获得lock
,然后执行lock.wait()
,让该线程进入了等待状态,此时会释放lock
的监视器。 - 一秒后,执行
Notify
,并且获得了lock
,此时第一个加锁代码块执行了lock.notifyAll()
,此时Wait
线程进入阻塞状态,进入到同步队列等待锁。 - 如果
notifyAll()
执行完之后释放锁的话,下面应执行的是Wait
线程,但是实际不是,所以说执行了通知之后并不会释放锁,锁还是在自己的线程(Notify
)内,因此第二个同步块又拿到了锁,最后线程的run
执行结束,释放锁。(从这里是不是能看出锁是归线程所有,而不是线程的同步代码块所有?) - 此时
lock
已经被释放,同步队列中的Wait
线程就可以获得锁了,开始从上次执行lock.wait()
的位置继续执行。
总结细节:
-
wait()
、notify()
、notifyAll()
的执行都需要先对调用对象加锁(就是在Sync
里用lock
变量吧)。 - 调用
notify()
、notifyAll()
的线程释放锁后,等待线程才有机会从wait()
返回。 -
notify()
函数是随机对一个当前锁设置的wait
线程进行通知。 - 从
wait()
方法放回的前提是获得了调用对象的锁。
程序示意图:
等待/通知的经典范式:
- 通知方(生产者):
- 获取对象的锁。
- 改变条件。
- 通知所有等待在对象上的线程。
- 等待方(消费者):
- 获取对象的锁。
- 如果条件不满足,就调用
wait()
方法,被通知后仍要检查条件。(条件写在while
里) - 条件满足则执行对应的逻辑。
管道输入/输出流
- 功能:主要用于线程之间的数据传输,传输媒介为内存。
- 四种具体实现:
PipedOutputStream
、PipedInputStream
、PipedReader
、PipedWriter
,前两种面向字节,后两种面向字符。 - 示例:注意Piped要进行输入和输出流的绑定。
public class Piped {
public static void main(String[] args) throws IOException {
// 定义面向字符的输入与输出流
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 进行绑定
// 正反都行?对
in.connect(out);
// 线程启动
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int send = 0;
try {
while ((send = System.in.read())!=-1){
out.write(send);
}
} finally {
out.close();
}
}
static class Print implements Runnable{
private PipedReader in;
public Print(PipedReader in){
this.in = in;
}
@Override
public void run() {
int receive = 0;
try {
// 标准流处理方法
while ((receive = in.read())!=-1){
// 因为是处理的字符,所以直接强转
System.out.print((char) receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Thread.join()的使用
功能:在A线程中执行B线程的join()
方法,即B.join()
,则A进入等待状态,直到B线程终止后才返回。这个函数开可以设置参数,join(long millis)
,即如果在给定时间里没有终止,那么就从超时方法里返回。
案例:
每一个线程调用前面线程的join()
方法。
public class Join {
public static void main(String[] args) throws InterruptedException {
// 获取main线程
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread = new Thread(new Domino(previous), String.valueOf(i));
thread.start();
previous = thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println("main 结束:" + Thread.currentThread().getName());
}
static class Domino implements Runnable{
private Thread thread;
public Domino(Thread thread){
this.thread = thread;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始");
try {
// 等传入的线程终止才从这继续
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
输出结果:
0开始
4开始
5开始
1开始
2开始
6开始
3开始
7开始
8开始
9开始
main 结束:main
0结束
1结束
2结束
3结束
4结束
5结束
6结束
7结束
8结束
9结束
Thread.join()方法部分源码:
这个看起来是在要等待的线程中执行的。
当B线程终止的时候,会调用线程自身的notifyAll()
方法,会通知所有等待在该线程对象上的线程。
ThreadLocal的使用
功能:是一个以ThreadLocal
对象为键(以自身为键咯),以任意对象为值(泛型,可以自己设置)的存储结构,这个结构被附带在线程上,可以根据一个ThreadLocal
对象查询绑定到这个线程上的一个值。
案例:
记录时间,注意可以给ThreadLocal
添加初始化函数。
public class Profiler {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){
// 如果调用get之前没有调用过set,get就会使用这个方法得到初始化的值
protected Long initialValue(){
return System.currentTimeMillis();
}
};
public static final void begin(){
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end(){
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws InterruptedException {
// Profiler.begin();
TimeUnit.SECONDS.sleep(1);
System.out.println("Cost: " + Profiler.end() + " mills");
}
}
线程应用实例
等待超时模型
场景:调用一个方法是等待一段时间,如果能在规定的时间内返回,就返回对应的结果;如果超时了,就返回默认的结果:
方法:就是用wait(int)
函数就行了。
我感觉这个例子不太对啊,这是万一在规定时间之内返回了,就继续等,一直等到超过规定时间,然后返回result
。
数据库连接池示例
目的:模拟从连接池中获取、使用和释放连接的过程。
案例代码:
同时注意看看里面的动态代理和倒计时的使用。
public class ConnectionPool {
// 连接列表
private LinkedList<Connection> pool = new LinkedList<>();
// 初始化,决定多少个
public ConnectionPool(int initialSize) {
if (initialSize > 0){
for (int i = 0; i < initialSize; i++) {
// ?为啥不行?
pool.addLast(ConnectionDriver.createConnection());
}
}
}
public void releaseConnection(Connection connection){
if (connection != null){
// 锁住池
synchronized (pool){
// 连接释放后需要进行通知,让其他消费者感知到连接池中已经归还了一个连接
// 所以把连接还回连接池就算释放了,不需要其他操作了吗?
pool.addLast(connection);
pool.notifyAll();
}
}
}
// 在mills内无法获取到连接,将会返回null
public Connection fetchConnection(long mills) throws InterruptedException {
synchronized (pool){
// 完全超时(mills也有可能直接设置为0或负数吧)
if (mills <= 0){
while (pool.isEmpty()){
pool.wait();
}
// 超时的话,只要有,也返回一个
return pool.removeFirst();
} else {
long future = System.currentTimeMillis() + mills;
long remaining = mills;
// 时间没到,且为空,就还要等
while (pool.isEmpty() && remaining>0){
pool.wait(remaining);
remaining = future - System.currentTimeMillis();
}
// 连接池不为空就返回连接,为空就返回null
Connection result = null;
if (!pool.isEmpty()){
result = pool.removeFirst();
}
return result;
}
}
}
}
public class ConnectionDriver {
// 这个有点忘了
static class ConnectionHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果执行的函数时commit,就sleep
if (method.getName().equals("commit")) {
TimeUnit.MILLISECONDS.sleep(100);
}
return null;
}
}
// 创建一个Connection的代理,在commit时休眠100ms
// 也没看到commit啊
public static final Connection createConnection() {
// 第一个参数是定义proxy的类加载器,这里也就是这个文件了
// 第二个参数是proxy类要执行的接口列表,这里指定了Connection,
// 也就是说Connection执行方法的时候,会通过代理
// 第三个参数是用到的InvocationHandler实现类
return (Connection) Proxy.newProxyInstance(
ConnectionDriver.class.getClassLoader(),
new Class<?>[]{Connection.class},
new ConnectionHandler());
}
}
public class ConnectionPoolTest {
static ConnectionPool pool = new ConnectionPool(10);
// 保证所有的ConnectionRunner能同时开始
// 倒计时用的,参数是时间
static CountDownLatch start = new CountDownLatch(1);
static CountDownLatch end;
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
end = new CountDownLatch(threadCount);
// 控制获取线程的次数,获取次数超出了线程数
int count = 20;
AtomicInteger got = new AtomicInteger();
AtomicInteger noGot = new AtomicInteger();
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new ConnectionRunner(
count, got, noGot),"ConnectionRunnerThread"
);
}
// 倒计时结束后,就释放所有等待的线程
// 目的是为了同一起跑线吧
start.countDown();
// 这个应是为了等所有线程都完成,在线程里会执行CountDown,
// main线程会等倒计时结束再继续
end.await();
System.out.println("total invoke: " + (threadCount * count));
System.out.println("got connection: " + got);
System.out.println("not got connection: " + noGot);
}
static class ConnectionRunner implements Runnable{
// 用来干嘛?
int count;
// 原子类
AtomicInteger got;
AtomicInteger notGot;
public ConnectionRunner(int count, AtomicInteger got, AtomicInteger noGot){
this.count = count;
this.got = got;
this.notGot = noGot;
}
@Override
public void run() {
try {
// 让当前线程等待,直到倒计时为0,除非被中断
// 后面又start.CountDown方法来让倒计时时间开始减小
start.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (count > 0){
try {
// 从线程池中获取连接,如果1000ms内无法获取到,将会返回null
// 分别统计连接获取的数量和未获取的数量
Connection connection = pool.fetchConnection(1000);
if (connection != null){
try {
connection.createStatement();
// 会被代理响应
connection.commit();
} finally {
// 释放
pool.releaseConnection(connection);
got.incrementAndGet();
}
} else {
notGot.incrementAndGet();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
count--;
}
}
end.countDown();
}
}
}
线程池技术及其示例(感觉有用,但是先跳过吧)
线程池预先创建若干线程,线程和客户端是分离的,可以用消息队列来理解。
客户端把工作请求传送到工作队列就返回,工作队列此时把任务分发给工作线程,两边互补接触。
如果工作队列中没有任务,那么工作线程就等待。