zoukankan      html  css  js  c++  java
  • Java多线程总结(二)锁、线程池

      掌握Java中的多线程,必须掌握Java中的各种锁,以及了解Java中线程池的运用。关于Java多线程基础总结可以参考我的这篇博文Java多线程总结(一)多线程基础

      转载请注明出处——http://www.cnblogs.com/zrtqsk/p/3784049.html,谢谢。

      

    一、Java中锁

    什么是锁。锁就是为了保护资源,防止多个线程同时操作资源时出错的机制。

    我们先来看一下锁的类图:

      如图,Java中的锁有两个主要的根接口——Lock和ReadWriteLock,分别表示锁和读写锁。其中Lock的主要实现类是ReetrantLock。ReadWriteLock的主要实现类是ReetrantReadWriteLock。而ReetrantReadWriteLock读写锁是通过两个内部类——ReadLock和WriteLock实现的,其中ReadLock是共享锁,WriteLock是独占锁。这两个内部类都实现了Lock接口。

    (1)、Java中的锁主要有以下几种概念:

    1、同步锁  

      同一时刻,一个同步锁只能被一个线程访问。以对象为依据,通过synchronized关键字来进行同步,实现对竞争资源的互斥访问。

    2、独占锁(可重入的互斥锁) 

      互斥,即在同一时间点,只能被一个线程持有;可重入,即可以被单个线程多次获取。什么意思呢?根据锁的获取机制,它分为“公平锁”和“非公平锁”Java中通过ReentrantLock实现独占锁,默认为非公平锁。

    3、公平锁 

      是按照通过CLH等待线程按照先来先得的规则,线程依次排队,公平的获取锁,是独占锁的一种。Java中,ReetrantLock中有一个Sync类型的成员变量sync,它的实例为FairSync类型的时候,ReetrantLock为公平锁。设置sync为FairSync类型,只需——Lock lock = new ReetrantLock(true)

    4、非公平锁 

      是当线程要获取锁时,它会无视CLH等待队列而直接获取锁。ReetrantLock默认为非公平锁,或——Lock lock = new ReetrantLock(false)。

    5、共享锁    

      能被多个线程同时获取、共享的锁。即多个线程都可以获取该锁,对该锁对象进行处理。典型的就是读锁——ReentrantReadWriteLock.ReadLock。即多个线程都可以读它,而且不影响其他线程对它的读,但是大家都不能修改它。CyclicBarrier, CountDownLatch和Semaphore也都是共享锁

    6、读写锁  

      维护了一对相关的锁,“读取锁”用于只读操作,它是“共享锁”,能同时被多个线程获取。“写入锁”用于写入操作,它是“独占锁”,只能被一个线程锁获取。Java中,读写锁为ReadWriteLock 接口定义,其实现类是ReentrantReadWriteLock包括内部类ReadLock和WriteLock。方法readLock()、writeLock()分别返回度操作的锁和写操作的锁。

    (至于“死锁”,并不是一种锁,而是一种状态,即两个线程互相等待对方释放同步监视器的时候,双方都无法继续进行,造成死锁。)

     锁的用法主要就是下面的流程:

            //先得到lock
            
            lock.lock();//然后获取锁
            try {
                //各种控制操作
            }catch(Exception e){
                
            }finally {
                lock.unlock();//解锁
            }    

    可以看到,这样的用法比synchronized关键字依据对象同步,要方便简单的多。

    (2)LockSupport和Condition

    1、LockSupport

      是用来创建锁和其他同步类的基本线程阻塞原语。 LockSupport中的静态方法park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而不会导致死锁。演示如下:

    package lock;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class LockSupportTest {
        static Thread mainThread = null;
        public static void main(String[] args) {
            //获取主线程
            mainThread = Thread.currentThread();
            //新建线程并启动
            MyThread thread1 = new MyThread("thread1");
            thread1.start();
            //模拟线程工作开始
            System.out.println(Thread.currentThread().getName() + "-----》 runs now!");
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "-----》 running step " + i);
                //当前线程睡眠1秒
                sleepOneSecond();
                if(i == 2){
                    System.out.println(Thread.currentThread().getName() + "-----》 now pack main thread——————————");
                    //让主线程阻塞
                    LockSupport.park();
                }
            }
            System.out.println(Thread.currentThread().getName() + "-----》 run over!");
            
        }
        
        /**当前线程暂停一秒钟 */
        public static void sleepOneSecond(){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        static class MyThread extends Thread {
    
            public MyThread(String name){
                super(name);
            }
            
            @Override
            public void run() {
                synchronized (this) {
                    //模拟工作开始
                    System.out.println(Thread.currentThread().getName() + "-----》 runs now!");
                    for (int i = 0; i < 5; i++) {
                        System.out.println(Thread.currentThread().getName() + "-----》 running step " + i);
                        //当前线程睡眠1秒
                        sleepOneSecond();
                    }
                    //模拟工作结束
                    System.out.println(Thread.currentThread().getName() + "-----》 run over!");
                    
                }
                System.out.println(Thread.currentThread().getName() + "-----》 now unpack main thread———————— ");
                //解除主线程的阻塞
                LockSupport.unpark(mainThread);
            }       
        }
    }

    结果如下:

    thread1-----》 runs now!
    thread1-----》 running step 0
    main-----》 runs now!
    main-----》 running step 0
    thread1-----》 running step 1
    main-----》 running step 1
    main-----》 running step 2
    thread1-----》 running step 2
    main-----》 now pack main thread——————————
    thread1-----》 running step 3
    thread1-----》 running step 4
    thread1-----》 run over!
    thread1-----》 now unpack main thread———————— 
    main-----》 running step 3
    main-----》 running step 4
    main-----》 run over!

    2、Condition

      对锁进行精确的控制,可用来代替Object中的wait、notify、notifyAll方法,需要和Lock联合使用。可以通过await(),signal()来休眠、唤醒线程。创建方式:Condition condition = lock.newCondition();

    演示懒得自己写了,参照http://www.cnblogs.com/skywang12345/p/3496716.html,如下:

    package LockSupportTest;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionTest {
        private static Lock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition();
    
        public static void main(String[] args) {
    
            ThreadA ta = new ThreadA("ta");
    
            lock.lock(); // 获取锁
            try {
                System.out.println(Thread.currentThread().getName()+" start ta");
                ta.start();
    
                System.out.println(Thread.currentThread().getName()+" block");
                condition.await();    // 等待
    
                System.out.println(Thread.currentThread().getName()+" continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();    // 释放锁
            }
        }
    
        static class ThreadA extends Thread{
    
            public ThreadA(String name) {
                super(name);
            }
    
            public void run() {
                lock.lock();    // 获取锁
                try {
                    System.out.println(Thread.currentThread().getName()+" wakup others");
                    condition.signal();    // 唤醒“condition所在锁上的其它线程”
                } finally {
                    lock.unlock();    // 释放锁
                }
            }
        }
    }

    结果如下:

    main start ta
    main block
    ta wakup others
    main continue

    如上,用起来挺简单的。

    二、线程池

    我们先来看一下线程池的类图:

    1、介绍

      可见,线程池的主要是由一个Executor接口统筹的。这个接口代表一个执行者,是一个典型的命令模式的运用。这个接口只有一个方法void execute(Runnable command),提交并执行任务。

      ExecuteService顾名思义,指的是Executor的服务类,继承了Executor接口,提供了更详细的控制线程的方法。

      AbstractExecutorService是一个抽象类,实现了ExecutorService大部分的方法。

      而我们最常用的ThreadPoolExecutor则继承了ExecutorService

      ForkJoinPool是JDK7新增的线程池,也是继承了这个线程类。

      ScheduledExecutorService这个接口继承了ExecutorService,比ExecutorService新增了“延时”和“周期执行”的功能。

      ScheduledThreadPoolExecutor这个类则实现了ScheduledExecutorService接口,且继承了ThreadPoolExecutor新增了“延时”和“周期执行”的功能

      Executors是一个线程池的工厂类,提供一系列静态方法,用于创建各种不同功能的线程池或线程相关的对象。

      而线程池的使用,最基本的就是如下:  

             // 创建各种线程 
             Thread thread1 = new MyThread();
             Thread thread2 = new MyThread();
             Thread thread3 = new MyThread();
    
             // 创建线程池pool
    
             // 将线程放入池中进行执行
             pool.execute(thread1 );
             pool.execute(thread2 );
             pool.execute(thread3 );
    
             // 关闭线程池
             pool.shutdown();     

    2、ForkJoinPool

      可见,整个线程池系列,说白了,也就3个类ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。一个是普通线程池,一个是新增了“延时”和“周期执行”的功能的线程池。那么ForkJoinPool是什么呢?

      ForkJoinPool为了是解决现在、未来计算机多核的问题。ExecuteService其他实现类基本都是基于单核下执行的,解决的是并发问题,而ForkJoinPool解决的是并行问题。ExcuteService中处于后面的任务需要等待前面任务执行后才有机会执行,而ForkJoinPool会采用work-stealing模式帮助其他线程执行任务。work-stealing模式——所有在池中的线程尝试去执行其他线程创建的子任务,这样就很少有线程处于空闲状态,非常高效。

      ForkJoinPool除了可以执行Runnable任务外,还可以执行ForkJoinTask任务,即ForkJoinPool的execute方法可以传入一个ForkJoinTask对象,这个任务对象跟Runnable的不同是,ForkJoinTask被放到线程内部的队列里面,而普通的Runnable任务被放到线程池的队列里面了。

      需要详细了解ForkJoinPool,可以参考http://blog.csdn.net/aesop_wubo/article/details/10300273。

      

    3、Executors

      Executors是一个线程池的工厂类,提供一系列静态方法,用于创建各种不同功能的线程池或线程相关的对象。

      主要有如下的几个静态方法:

      newCachedThreadPool()  :  创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程被缓存在线程池中。

      newFixedThreadPool(int nThreads)  :  创建一个可重用的,具有固定线程数的线程池。

      newSingleThreadExecutor()  :  创建一个只有一个单线程的线程池。

      newScheduledThreadPool(int corePoolSize)  :  创建具有指定数目的线程池,可以指定延时后执行任务,即使线程空闲,也被保持在线程池内。

      newSingleThreadScheduledExecutor()  :  创建一个只有一个单线程的线程池,可以指定延时后执行任务

    4、线程池的状态

      线程池的状态有五种——RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED

      (图片出处:http://www.cnblogs.com/skywang12345/p/3509960.html)

      RUNNING  :  线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。

      SHUTDOWN  :  线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务

      STOP  :  线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务

      TIDYING  :  当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。 当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空 的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。

      TERMINATED  :  线程池彻底终止,就变成TERMINATED状态。

    参考:http://www.cnblogs.com/skywang12345/p/java_threads_category.html

      http://blog.csdn.net/aesop_wubo/article/details/10300273

     如果觉得本文还不错的话,麻烦点击推荐哦!谢谢啦!

  • 相关阅读:
    【译】NodeJS and Good Practices
    【译】单一责任原则
    CSS 属性 z-index
    Node 连接 MySql
    CentOS 7 后台克隆远程库
    H5log打印
    利用Promise实现Promise.all
    Set、Map、WeakSet、WeakMap 理解与使用
    vue如何禁止弹窗后面的滚动条滚动?
    vue面试题总结
  • 原文地址:https://www.cnblogs.com/zrtqsk/p/3784049.html
Copyright © 2011-2022 走看看