Java学习笔记: 线程

来源:互联网 发布:手机淘宝的分类在哪里 编辑:程序博客网 时间:2024/05/22 14:13

目录:

1. 线程的建立

2. 线程的生命周期与控制

3. 线程的同步与通信

4. 未处理的异常

5. 线程安全的集合

6. 同步器

7. 线程池


主要参考: Thinking in Java, Core Java, 疯狂的Java讲义, Java doc, 源码. 

Tips: Thinking in Java和Core Java都推荐了Java Concurrency in Practice, 相信是一本关于线程以及Concurrency的不错的进阶数目.


1. 线程的建立

        想让线程执行一段代码, 有两种方法将需要执行的代码绑定到线程中:

        (1) 继承Thread类, 重写run()方法;

        (2) 实现一个runnable对象, 调用Thread类的相关构造函数, 如Thread(Runnable).

        疯狂的Java讲义中提到, 这两种方式的区别在于: 第一种方法中, run()方法中所有的局部变量是独立的, 而第二种方法中Runnable r里面的局部变量是共享的. 但我觉得这个说法不够全面, 例如我用第二种方法创建线程的时候采用new Thread(new Runnable{...})的话, runnable里面的局部变量也是独立的. 共享局部变量的本质在于多个Thread实例的Runnbale target成员是否指向同一个对象. 采用第二种方法并不一定都会共享局部变量.

        另一点区别是, 在Thread.run()里面可以直接用this引用线程实例, 而在Runnable.run()里则需要使用Thread.currentThread()引用线程实例.

        Thread.run()方法也好, Runnable.run()也好, 并没有返回值. 如果需要线程有返回值, 可以采用FutureTask(Android里大量使用的AsyncTask类中就是使用这个方法创立线程), 创建的具体流程如下:

        首先创立一个Callable对象 -> 用Callable对象创立一个FutureTask对象 -> 因为FutureTask对象实现了Runnable接口, 所以可以用Thread(Runnable)方法建立线程. 线程启动后, 就可以调用FutureTask中Future接口里的方法获得线程的执行状态或者返回值.

        注: 已经结束的线程不能再次调用start(), 调用过run()方法的线程不能调用start()方法(其实包含了第一句话), 会抛出异常.


2. 线程的生命周期与控制

    2.1 线程的生命周期

        源码和Core Java中Thread类是一致的, 六种状态: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.

        疯狂的Java讲义里面将RUNNABLE状态细分为"就绪"和"运行"两种, 以CPU是否实际执行线程作为区分点. 但是没有对BLOCKED, WAITING和TIMED_WAITING做区分, 统一称为阻塞.

        简单流程如下:

        (1) new方法执行后, 线程进入NEW状态;

        (2) 调用start()后, 线程进入RUNNABLE状态;

        (3) 如果试图获得一个内部对象锁(synchronized关键字所用的锁), 而该锁却被别的对象占有, 线程进入BLOCKED状态;

        (4) 如果调用Object.wait, Thread.join或者等待java.util.concurrent中Lock或Condition时(Lock.tryLock或Condition.await), 线程进入WAITING状态;

        (5) (4)中方法如果带有超时参数, 以及Thread.sleep方法, 线程会进入TIMED_WAITING状态;

        (6) run方法执行结束正常退出, 或因为一个没有被捕获的异常(Runnable.run不能抛出异常, Callable.call可以), 或者调用stop方法(慎用), 线程进入TERMINATED状态.

        注: Thread.yield()只会让Thread让出CPU, 但不会改变线程RUNNABLE的状态.

    2.2 线程的控制

        join(), sleep(), yield(), setDaemon(), setPriority()等方法.

        注: setDaemon必须在start()前.

        注: 用lock等待获得内部锁的过程中, lock方法是不能响应中断的, 但是可以可以用lockInterruptibly()方法解决, 但是synchronized则不可以中断.


3. 线程的同步与通信

    3.1 线程的同步

        总的来说有两类:

        (1) 第一类使用synchronized关键字同步代码块或方法, 它带有一个隐式的condition, 可用wait(), signal()和signalAll()方法.

        (2) 第二类使用java.util.concurrent package中的Lock, 结合Condition实现.

    3.1.1 synchronized

        同步代码块: 

                synchronized(obj) { }; 

                相当于

                        obj.intrinsicLock.lock(); 

                        try {

                            ...

                        } finally {

                            obj.intrinsicLock.unlock()

                        }

                 Object内置wait, notify和notifyAll()方法. 三个方法均是native方法. 调用后将调用intrinsicCondition的await, signal和signalAll方法.

        同步方法: 

                public synchronized void method() { }; 

                相当于

                        public void method() {

                            this.intrinsicLock.lock();

                            try {

                                ...

                            } finally {

                                this.intrinsicLock.unlock();

                            }

                        }

                可以使用wait, notify和notifyAll.

                注: synchronized可以修饰static方法, 用其Class对象的内部锁. 但是不可以修饰构造器.

    3.1.2 Lock

        Lock和Condition(Lock.newCondition)可以起到河synchronized相同的作用.主要函数有:

                Lock.lock(), Lock.unlock()以及Condition.await, Condition.signal, Condition.signalAll方法.

        Lock中可以生成多个Condition, 而synchronized中只有一个, 所以Lock要更加灵活. 另外Lock还有tryLock方法和lockInterruptibly方法.

        注: 如果处于阻塞或者等待状态, 线程被interrupt, 那么会抛出异常. 除非是调用了Condition.awaitUninterruptibly方或是不带超时参数的lock().

    3.1.3 ReadWriteLock

        读锁: ReadWriteLock.readLock();

        写锁: ReadWriteLock.writeLock();

        可以有多个读同时发生, 但是写和读以及写和写不能同时发生.

    3.1.4 StampedLock

        StampedLock是ReadWrite的一种改进版本, 有Reading, Writing和Optimistic Reading三种模式.

        Reading和Writing模式对应ReadWriteLock的读锁和写锁, 使用语法稍有不同.

        StampedLock.readLock()和StampedLock.writeLock()返回的是一个Stamp(long类型), 在StampedLock的unlock, unlockRead和unlockWrite中使用.

        对于Optimistic Reading, 使用StampedLock.tryOptimisticRead. 判断读取后是否还有效(中间可能插入别的写操作)可以用StampedLock.validate(long)方法.

        此外, StampedLock读写锁还可以转换, 具体可以查阅Java doc.

     

    3.2 BlockingQueue

        除了使用synchronized和Lock进行线程的同步与通信外, 还可以使用BlockingQueue. 按照队尾插入, 队头获取, 取出后是否删除, 将方法分三类:

        队尾插入:

            (1) add: 向队尾添加一个元素, 如果队列满则抛出IllegalStateException异常. 这个方法是没有加锁的.

            (2) offer: 使用lock, 尝试向队尾添加一个元素, 返回Boolean标志成功和失败.

            (3) 有延时参数的offer: 使用Lock.lockInterruptibly和有延时参数的Condition.awaitNanos. 返回Boolean标志成功和失败.

            (4) put: 使用lockInterruptibly和不带延时参数的Condition.await. 

        队头获取并删除:

            (1) remove(Object): 使用lock(), 和add还是不太一样, 是否抛出异常也是optional的, 而且也不是从队头删除, 而是从队头开始找第一个equeals参数的, 返回Boolean.

            (2) poll(): 使用lock(), 若空返回null.

            (3) 有延时参数的poll(): lockInterruptibly加带参数的waitNanos, 若等待结束还为空则返回null.

            (4) take(): 相当于延时参数无限的poll, 无返回值.

        队头获取:

            (1) peek(): 使用lock().

            (2) element(): 已删除.

        BlockingQueue接口的实现类:

            (1) ArrayBlockingQueue

            (2) LinkedBlockingQueue

            (3) PriorityBlockingQueue: 不是标准的BlockingQueue, 取最小元素.

            (4) SynchronousQueue: 存取必须交替进行.

            (5) DelayQueue: 基于PriorityBlockingQueue, 使用Delay接口的getDelay方法进行比较.


    4. 未处理的异常

            略


    5. 线程安全的集合

            以Concurrent开头的集合类, 以及CopyOnWrite开头的集合类, 或者使用Collection的静态方法进行包装.


    6. 同步器

            6.1 CyclicBarrier

                    先建立一个CyclicBarrier, 然后调用Barrier.await方法实现多个线程的并行运行. CyclicBarrier的构造器中还可以传入一个Runnable, 它会在最后一个Thread到达时触发.

            6.2 DelayQueue

                    略

            6.3 CountDownLatch

                    调用await将阻塞, 直到调用CountDownLatch.countDown.

            6.4 Semaphore

                    控制访问数量的锁, 用Semaphore.acquire和Semaphore.release控制获取和释放锁.

            6.5 Exchanger

                    交换两个线程中一对Object(用模板参数传入), 使用Exchanger.exchange.


    7. 线程池

            (1) 提交任务:

                    用Executors类的静态方法生成ExecutorService或ScheduleExecutorService接口的具体类. 然后调用submit或sechdule等执行.

            (2) 控制任务组:

                    可以使用shutdown和shutdownNow停止任务.

                    可以使用invokeAny或invokeAll提交一个Collection的Callable<T>(invokeAny不是随机提交一个任务, 而是提交所有任务). invokeAny将随机返回一个任务的结果, 而invokeAll将返回一个List<Future<T>>. 可以用ExecutorCompletiomService包装一个ExecutorService, 这样可以将任务按是否完成排序. 可以调用take, poll等方法获取完成任务的返回值.

            (3) ForkJoinPool(RecursiveTask<T>)

                    RecursiveTask<T>可以把任务划分再把返回值合并起来, 使用其fork和join的方法. 使用ForkJoinPool.commonPool方法, 然后调用submit, execute, invoke等方法执行.

                    


    后记: 试着写了一篇当作复习, 发现越写越少. 一些已经懂的东西写起来没有兴致, 也没有从零开始全面讲解用法. 就当作一个纲提醒自己有哪些知识点吧, 以后遇到具体的需要细致分析的问题可以尝试详细写点.

        

                    



0 0
原创粉丝点击