进程:它是内存中的一段独立的空间。java
线程:位于进程中,负责当前进程中的某个具有独立运行资格的空间。编程
进程是负责整个程序的运行,而线程是程序中具体的某个独立功能的运行。一个进程中至少应该有一个线程。数组
多线程:在一个进程中,咱们同时开启多个线程,让多个线程同时去完成某些任务(功能)。(好比后台服务系统,就能够用多个线程同时响应多个客户的请求)数据结构
多线程的目的:提升程序的运行效率。多线程
多线程运行的原理:CPU负责程序的运行,而CPU在运行程序的过程当中某个时刻点上,它其实只能运行一个程序。CPU它能够在多个程序之间进行高速的切换。并发
每一个程序就是进程, 而每一个进程中会有多个线程,而CPU是在这些线程之间进行切换。多线程能够提升程序的运行效率,但不能无限制的开线程。框架
实现多线程的两种方式:一、继承Thread;二、实现Runnable接口。异步
Java同步机制:函数
一、synchronized工具
synchronized( 须要一个任意的对象(锁) ){
代码块中放操做共享数据的代码。
}
synchronized缺陷:
一、多线程阻塞:须要有一种机制能够不让等待的线程一直无期限地等待下去(好比只等待必定的时间或者可以响应中断),经过Lock就能够办到。
二、多线程读写文件:须要一种机制来使得多个线程都只是进行读操做时,线程之间不会发生冲突,经过Lock就能够办到。
经过Lock能够知道线程有没有成功获取到锁。这个是synchronized没法办到的。
二、Lock
Lock和synchronized的区别:
一、Lock不是Java语言内置的,synchronized是Java语言的关键字,所以是内置特性。Lock是一个类,经过这个类能够实现同步访问;
二、Lock和synchronized有一点很是大的不一样,采用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
注意:
不过要注意的是,若是有一个线程已经占用了读锁,则此时其余线程若是要申请写锁,则申请写锁的线程会一直等待释放读锁。
若是有一个线程已经占用了写锁,则此时其余线程若是申请写锁或者读锁,则申请的线程会一直等待释放写锁。
五、Lock和synchronized的选择
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
3)Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;
4)经过Lock能够知道有没有成功获取锁,而synchronized却没法办到。
5)Lock能够提升多个线程进行读操做的效率。
在性能上来讲,若是竞争资源不激烈,二者的性能是差很少的,而当竞争资源很是激烈时(即有大量线程同时竞争),此时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下的主要用来控制线程同步的工具。
主要的方法是:put、take一对阻塞存取;add、poll一对非阻塞存取。
插入:
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有四个具体的实现类,经常使用的两种实现类为:
1、ArrayBlockingQueue:一个由数组支持的有界阻塞队列,规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。
2、LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的。
LinkedBlockingQueue 能够指定容量,也能够不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
LinkedBlockingQueue和ArrayBlockingQueue区别:
LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不同,致使LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue.
java并发编程的一些总结
.1. 不该用线程池的缺点
有些开发者图省事,遇到须要多线程处理的地方,直接new Thread(...).start(),对于通常场景是没问题的,但若是是在并发请求很高的状况下,就会有些隐患:
.2. 制定执行策略
在每一个须要多线程处理的地方,无论并发量有多大,须要考虑线程的执行策略
.3. 线程池的类型
不论是经过Executors建立线程池,仍是经过Spring来管理,都得清楚知道有哪几种线程池:
其实,这些不一样类型的线程池都是经过构建一个ThreadPoolExecutor来完成的,所不一样的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory这么几个参数。具体能够参见JDK DOC。
.4. 线程池饱和策略
由以上线程池类型可知,除了CachedThreadPool其余线程池都有饱和的可能,当饱和之后就须要相应的策略处理请求线程的任务,好比,达到上限时经过ThreadPoolExecutor.setRejectedExecutionHandler方法设置一个拒绝任务的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy几种策略,具体差别可见JDK DOC
.5. 线程无依赖性
多线程任务设计上尽可能使得各任务是独立无依赖的,所谓依赖性可两个方面:
固然,在有些业务里确实须要必定的依赖性,好比调用者须要获得线程完成后结果,传统的Thread是不便完成的,由于run方法无返回值,只能经过一些共享的变量来传递结果,但在Executor框架里能够经过Future和Callable实现须要有返回值的任务,固然线程的异步性致使须要有相应机制来保证调用者能等待任务完成。