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

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

  • 相关阅读:
    184. Department Highest Salary【leetcode】sql,join on
    181. Employees Earning More Than Their Managers【leetcode】,sql,inner join ,where
    178. Rank Scores【leetcode】,sql
    177. Nth Highest Salary【leetcode】,第n高数值,sql,limit,offset
    176. Second Highest Salary【取表中第二高的值】,sql,limit,offset
    118. Pascal's Triangle【LeetCode】,java,算法,杨辉三角
    204. Count Primes【leetcode】java,算法,质数
    202. Happy Number【leetcode】java,hashSet,算法
    41. First Missing Positive【leetcode】寻找第一个丢失的整数,java,算法
    删除
  • 原文地址:https://www.cnblogs.com/zrtqsk/p/3784049.html
Copyright © 2011-2022 走看看