Java多线程及并发

进程内存中的一段独立的空间java

线程位于进程中,负责当前进程中的某个具有独立运行资格的空间编程

进程是负责整个程序的运行,而线程是程序中具体的某个独立功能的运行一个进程至少应该有一个线程。数组

多线程一个进程中,咱们同时开启多个线程,让多个线程同时去完成某些任务功能(好比后台服务系统,就能够用多个线程同时响应多个客户的请求)数据结构

多线程的目的提升程序的运行效率多线程

多线程运行的原理CPU负责程序的运行,而CPU在运行程序的过程当中某个时刻点上,它其实只能运行一个程序。CPU它能够在多个程序之间进行高速的切换并发

每一个程序就是进程, 每一个进程中会有多个线程,而CPU是在这些线程之间进行切换。多线程能够提升程序的运行效率,但不能无限制的开线程。框架

 

实现多线程的两种方式:一、继承Thread;二、实现Runnable接口。异步

 

Java同步机制函数

一、synchronized工具

 

  synchronized( 须要一个任意的对象(锁) ){

 

    代码块中放操做共享数据的代码。

 

  }

  synchronized缺陷:

    一、多线程阻塞:须要有一种机制能够不让等待的线程一直无期限地等待下去(好比只等待必定的时间或者可以响应中断),经过Lock就能够办到

    二、多线程读写文件:须要一种机制来使得多个线程都只是进行读操做时,线程之间不会发生冲突,经过Lock就能够办到

  经过Lock能够知道线程有没有成功获取到锁。这个是synchronized没法办到的

 

二、Lock

  Lock和synchronized的区别:

    一、Lock不是Java语言内置的synchronizedJava语言的关键字,所以是内置特性Lock是一个类,经过这个类能够实现同步访问;

    二、Locksynchronized有一点很是大的不一样,采用synchronized不须要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完以后,系统会自动让线程释放对锁的占用;而Lock则必需要用户去手动释放锁,若是没有主动释放锁,就有可能致使出现死锁现象。

 

 

 

 

java.util.concurrent.locks包下经常使用的类

 

一、Lock

 

  public interface Lock {

 

      void lock();//获取锁

 

      void lockInterruptibly() throws InterruptedException;//获取锁

 

      boolean tryLock();//获取锁

 

      boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//获取锁

 

      void unlock();//释放锁

 

    }

  因为在前面讲到若是采用Lock,必须主动去释放锁,而且在发生异常时,不会自动释放锁。所以通常来讲,使用Lock必须在try{}catch{}块中进行,而且将释放锁的操做放在finally块中进行,以保证锁必定被被释放,防止死锁的发生。

 

二、ReentrantLock

 

三、ReadWriteLock

 

四、ReentrantReadWriteLock

注意:

  不过要注意的是,若是有一个线程已经占用了读锁,则此时其余线程若是要申请写锁,则申请写锁的线程会一直等待释放读锁。

  若是有一个线程已经占用了写锁,则此时其余线程若是申请写锁或者读锁,则申请的线程会一直等待释放写锁。

 

五、Locksynchronized的选择

 

 

  1Lock是一个接口,而synchronizedJava中的关键字,synchronized是内置的语言实现;

 

  2synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;

 

  3Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;

 

  4)经过Lock能够知道有没有成功获取锁,而synchronized却没法办到。

 

  5Lock能够提升多个线程进行读操做的效率。

 

  在性能上来讲,若是竞争资源不激烈,二者的性能是差很少的,而当竞争资源很是激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。因此说,在具体使用时要根据适当状况选择。

 

 

Java并发

 

线程池的5中建立方式

 

一、 Single Thread Executor : 只有一个线程的线程池,所以全部提交的任务是顺序执行,

 

代码: Executors.newSingleThreadExecutor()

 

二、 Cached Thread Pool : 线程池里有不少线程须要同时执行,老的可用线程将被新的任务触发从新执行,若是线程超过60秒内没执行,那么将被终止并从池中删除

 

代码:Executors.newCachedThreadPool()

 

三、 Fixed Thread Pool : 拥有固定线程数的线程池,若是没有任务执行,那么线程会一直等待,

 

代码: Executors.newFixedThreadPool(4)

 

在构造函数中的参数4是线程池的大小,你能够随意设置,也能够和cpu核数量保持一致,获取cpu核数量int cpuNums = Runtime.getRuntime().availableProcessors();

 

四、 Scheduled Thread Pool : 用来调度即将执行的任务的线程池,多是不是直接执行, 每隔多久执行一次... 策略型的

 

代码:Executors.newScheduledThreadPool()

 

五、 Single Thread Scheduled Pool : 只有一个线程,用来调度任务在指定时间执行,代码:Executors.newSingleThreadScheduledExecutor()

 

 

 

线程池的使用

 

提交 Runnable ,任务完成后 Future 对象返回 null

 

调用excute,提交任务, 匿名Runable重写run方法, run方法里是业务逻辑

 

 

 

提交 Callable,该方法返回一个 Future 实例表示任务的状态

 

调用submit提交任务, 匿名Callable,重写call方法, 有返回值, 获取返回值会阻塞,一直要等到线程任务返回结果

 

 

java并发包消息队列及在开源软件中的应用

 

BlockingQueue也是java.util.concurrent下的主要用来控制线程同步的工具。

 

主要的方法是:puttake一对阻塞存取;addpoll一对非阻塞存取。

 

插入:

 

     1)add(anObject):anObject加到BlockingQueue,即若是BlockingQueue能够容纳,则返回true,不然抛出异常,很差

 

         2)offer(anObject):表示若是可能的话,anObject加到BlockingQueue,即若是BlockingQueue能够容纳,则返回true,不然返回false.

 

         3)put(anObject):anObject加到BlockingQueue,若是BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续, 有阻塞, 放不进去就等待

 

读取:

 

         4)poll(time):取走BlockingQueue里排在首位的对象,若不能当即取出,则能够等time参数规定的时间,取不到时返回null; 取不到返回null

 

         5)take():取走BlockingQueue里排在首位的对象,BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止; 阻塞, 取不到就一直等

 

其余

 

    int remainingCapacity();返回队列剩余的容量,在队列插入和获取的时候,不要瞎搞,数 据可能不许, 不能保证数据的准确性

 

    boolean remove(Object o); 从队列移除元素,若是存在,即移除一个或者更多,队列改 变了返回true

 

    public boolean contains(Object o); 查看队列是否存在这个元素,存在返回true

 

    int drainTo(Collection<? super E> c); //移除此队列中全部可用的元素,并将它们添加到给定 collection 中。取出放到集合中

 

    int drainTo(Collection<? super E> c, int maxElements); 和上面方法的区别在于,指定了移 动的数量; 取出指定个数放到集合

 

 

 

BlockingQueue有四个具体的实现类,经常使用的两种实现类为

 

1ArrayBlockingQueue:一个由数组支持的有界阻塞队列规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。

 

2LinkedBlockingQueue大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的。 

 

LinkedBlockingQueue 能够指定容量,也能够不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到puttake方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。

 

LinkedBlockingQueueArrayBlockingQueue区别:

 

LinkedBlockingQueueArrayBlockingQueue比较起来,它们背后所用的数据结构不同,致使LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue.

 

java并发编程的一些总结

 

.1. 不该用线程池的缺点

 

有些开发者图省事,遇到须要多线程处理的地方,直接new Thread(...).start(),对于通常场景是没问题的,但若是是在并发请求很高的状况下,就会有些隐患:

 

  • 新建线程的开销。线程虽然比进程要轻量许多,但对于JVM来讲,新建一个线程的代价仍是挺大的,决不一样于新建一个对象

 

  • 资源消耗量。没有一个池来限制线程的数量,会致使线程的数量直接取决于应用的并发量,这样有潜在的线程数据巨大的可能,那么资源消耗量将是巨大的

 

  • 稳定性。当线程数量超过系统资源所能承受的程度,稳定性就会成问题

 

.2. 制定执行策略

 

在每一个须要多线程处理的地方,无论并发量有多大,须要考虑线程的执行策略

 

  • 任务以什么顺序执行

 

  • 能够有多少个任务并发执行

 

  • 能够有多少个任务进入等待执行队列

 

  • 系统过载的时候,应该放弃哪些任务?如何通知到应用程序?

 

  • 一个任务的执行先后应该作什么处理

 

.3. 线程池的类型

 

不论是经过Executors建立线程池,仍是经过Spring来管理,都得清楚知道有哪几种线程池:

 

  • FixedThreadPool:定长线程池,提交任务时建立线程,直到池的最大容量,若是有线程非预期结束,会补充新线程

 

  • CachedThreadPool:可变线程池,它犹如一个弹簧,若是没有任务需求时,它回收空闲线程,若是需求增长,则按需增长线程,不对池的大小作限制

 

  • SingleThreadExecutor:单线程。处理不过来的任务会进入FIFO队列等待执行

 

  • SecheduledThreadPool:周期性线程池。支持执行周期性线程任务

 

其实,这些不一样类型的线程池都是经过构建一个ThreadPoolExecutor来完成的,所不一样的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory这么几个参数。具体能够参见JDK DOC

 

.4. 线程池饱和策略

 

由以上线程池类型可知,除了CachedThreadPool其余线程池都有饱和的可能,当饱和之后就须要相应的策略处理请求线程的任务,好比,达到上限时经过ThreadPoolExecutor.setRejectedExecutionHandler方法设置一个拒绝任务的策略,JDK提供了AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy几种策略,具体差别可见JDK DOC

 

.5. 线程无依赖性

 

多线程任务设计上尽可能使得各任务是独立无依赖的,所谓依赖性可两个方面:

 

  • 线程之间的依赖性。若是线程有依赖可能会形成死锁或饥饿

 

  • 调用者与线程的依赖性。调用者得监视线程的完成状况,影响可并发量

 

固然,在有些业务里确实须要必定的依赖性,好比调用者须要获得线程完成后结果,传统的Thread是不便完成的,由于run方法无返回值,只能经过一些共享的变量来传递结果,但在Executor框架里能够经过FutureCallable实现须要有返回值的任务,固然线程的异步性致使须要有相应机制来保证调用者能等待任务完成。

相关文章
相关标签/搜索