线程与并发基础知识

来源:互联网 发布:miui网络短信 编辑:程序博客网 时间:2024/06/05 15:31

线程与并发

  • 线程与进程
  • 线程创建的方式
  • 线程池
  • 并发编程
  • 并发容器
  • 同步容器

1 线程与进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

2 线程创建的方式

线程创建有三种方式

继承Thread类创建线程类

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体;
(2)创建Thread子类的实例,即创建了线程对象;
(3)调用线程对象的start()方法来启动该线程。

实现Runnable接口创建线程类

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。

使用callable和Future接口创建线程

FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

比较

实现Runnable接口,Callable接口:
还可以继承其他类,扩展线程的功能;
多个线程共享一个target,适用于多个线程使用同一份资源的情况。
继承Thread类:
不能再继承其他的类,编写简单。

3 线程池

类结构

Executor接口>顶层接口,只包含一个excute(Runnable,Command)方法
ExecutorService接口
AbstactExecutorService
ThreadPoolExecutor>线程池类

参数

CorePoolSize:核心池大小,当线程池中的线程数目达到核心池大小就会把到达的任务放到缓存队列中。
MaximumPoolSize:线程池最大线程数
KeepAliveTime:表示线程没有任务执行时最多保持的时间
WorkQueue:阻塞队列,用来存储等待执行的任务

WorkQueue

ArrayBlockingQueue:基于数组
LinkedBlockingQueue:基于链表
SynchronousQueue:不保存提交的任务,直接创建线程

线程池工作原理

1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2、当调用execute()方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

线程池的排对策略

A.如果运行的线程少于corePoolSize,则Executor始终首选添加新的线程,而不进行排队。
B.如果运行的线程等于或多于corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。
C.如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。

4 并发编程

并发编程的三个概念

原子性
一个程序要么都执行,要么都不执行。
可见性
多线程访问同一个变量时,变量值的修改会被其他线程立即看到。
有序性
如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。

java内存模型

原子性
Sychronized和lock来实现,保证某时刻只有一个线程执行该代码块。
可见性
使用Volatile关键字:当一个共享变量被volatile修饰时,保证被修饰的值立即更新到主内存。
使用Sychronized和lock:保证某个时刻只有一个线程执行该代码块,锁释放之前会将变量修改刷新到主内存。
有序性
利用volatile,强制将修改的值立即写入内存,一个线程进行修改时,其他线程工作内存中的缓存行失效,必须等到修改完成刷新主内存之后,重新从主存取值。

Volatile关键字

确保指令重排时,内存屏障前的代码不会在内存屏障后执行,反之亦然。
强制立即将修改的值刷入主内存
如果是写操作,其他工作内存缓存行失效;要保证原子性才能在并发操作时使用volatile。

5 并发容器

CopyOnWriteArrayList

一开始大家都在共享同一个内容,当某人想要修改这个内容的时候,才会真正把内容copy出去,形成一个新的内容,然后再修改,这是一种延时懒惰策略。
原理
往容器中添加元素时,不直接向当前容器添加,而是先copy当前容器,复制出一个新的容器,然后向新的容器里添加元素添加完后再将原容器的引用指向新的容器。
当前容器不会添加任何元素,读写分离。添加操作需要加锁,否则多线程写的时候会copy出N个副本。
不足:如果新写入的对象占用内存比较大,很可能造成繁荣的GC;不能保证实时一致性。

ConcurrentHashMap

类中包含两个静态内部类:HashEntry,Segment
HashEntry用来封装映射表的键值对;
Segment用来充当锁的角色,每个Segment对象守护整个散列映射表的若干桶,每个桶是由若干个HashEntry对象链起来的链表。
一个ConcurrentHashMap实例中包含若干个Segment对象组成的数组。
只有在求SIZE等操作时才需要锁住整个表。get操作不需要锁。

6 同步容器

同步容器

同步容器将所有对容器的访问都串行化了,这样保证了线程的安全性,这种方法严重降低了并发性,当多个线程竞争容器时,吞吐量降低。
Hashtable,vector,stack都是采用synchronized同步,相当于所有线程进行读写时都去竞争一把锁,效率低下。

HashMap和Hashtable区别

1 Hashtable继承自Dictory;HashMap继承自AbstractMap。
2 HashMap是Hashtable的轻量级实现,都完成了Map接口。线程非安全,但一般来说效率高于Hashtable。
3 HashMap允许将null做为一个Entry的key或者value,而Hashtable不可以。
4 HashMap把Hashtable的contains()方法去掉增加containsValue()和containsKey()。

原创粉丝点击