《Java并发编程的艺术》第四章 Java并发编程基础


第四章 Java并发编程基础

框架图

高清图片地址
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里)
    • 条件满足则执行对应的逻辑。

管道输入/输出流

  • 功能:主要用于线程之间的数据传输,传输媒介为内存。
  • 四种具体实现PipedOutputStreamPipedInputStreamPipedReaderPipedWriter,前两种面向字节,后两种面向字符。
  • 示例:注意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();
        }
    }
}

线程池技术及其示例(感觉有用,但是先跳过吧)

线程池预先创建若干线程,线程和客户端是分离的,可以用消息队列来理解。
客户端把工作请求传送到工作队列就返回,工作队列此时把任务分发给工作线程,两边互补接触。
如果工作队列中没有任务,那么工作线程就等待。

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
【4层】3100平米综合办公楼毕业设计(含计算书、建筑结构图) 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值