浅谈Java并发编程

来源:互联网 发布:阿里云学生认证条件 编辑:程序博客网 时间:2024/05/15 12:40

Java并发编程以并发性和线程安全性出发,构建并发模块来避免并发危险、构造线程安全的类。

并发编程的基本概念

涉及到的基本概念有原子性与可见性、Volatile与Synchronized、进程与线程等等。

原子性与可见性

原子性是指不可再分的操作,一次只有一个线程能够去执行它,也就是说代码执行是互斥的。比较常用的是用Synchronized为方法加锁保证原子性。

可见性是指一个变量在一个线程中的修改能够被所有使用该变量的其它线程看到。

在并发编程中,需要保证原子性和可见性的操作都应该同步。

Volatile与Synchronized

Volatile能够使变量在值发生改变时能尽快地让其他线程知道,它的读写操作都发生在内存中,而不在寄存器中,从而实现同步。但有一点值得注意的是,声明为volatile的简单变量,其当前值如题与该变量以前的值相关(如volatile n; n++;这样的操作),那么volatile关键字不起作用

Synchronized则锁定了变量只能被当前线程操作,其它线程无法同时访问这些变量,可能会造成线程阻塞,开销也比Volatile大

在原子性和可见性方面,volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性。
valatile需要加锁保护的原因
由于及时更新,很可能导致另一线程访问最新变量值,无法跳出循环的情况多线程下计数器必须使用锁保护。

ConcurrentHashMap

我们都知道HashMap是线程不安全的,在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%。而HashTable使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。此时,HashTable应运而生了,它使用分离锁技术,将数据分离成一段一段存储,给每段进行加锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。使用分段锁,减小了请求 同一个锁的频率。

ConcurrentHashMap 数据结构为一个 Segment 数组(充当锁的角色),Segment 的数据结构为 HashEntry 的数组,而 HashEntry 存的是我们的键值对,可以构成链表。

这里写图片描述

ConcurrentHashMap 是一个并发散列映射表的实现,它允许完全并发的读取,并且支持给定数量的并发更新。相比于 HashTable 和用同步包装器包装的 HashMap(Collections.synchronizedMap(new HashMap())),ConcurrentHashMap 拥有更高的并发性。在 HashTable 和由同步包装器包装的 HashMap 中,使用一个全局的锁来同步不同线程间的并发访问。同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器。这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成串行化的了。

ConcurrentHashMap 的高并发性主要来自于三个方面:

  1. 用分离锁实现多个线程间的更深层次的共享访问。
  2. 用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
  3. 通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。

更加具体的东西需要到JDK去读源码才能理解

线程与进程

进程是系统进行资源分配和调度的一个独立单位,通常用时间片轮转高度来实现进程的切换,开销比较大。它有自己的独立空间,进程一旦崩溃,不会引起其它的进程的崩溃。

线程是进程中代码片段,是一个比进程更小的单位,一个进程中可以允许有多个线程,并能共享信息。多线程指的是将进程任务分配到不同的线程中同时执行。

线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

线程池的基本思想还是一种对象池的思想(数据库连接池也是用的这种思想),开辟一块内存空间,里面存放了众多活动线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

线程池的好处体现在资源、响应速度、可控性上:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Java的线程池由Executors提供,有四种类型(缓存性线程池、单线程池、固定线程池、周期线程池):

1)newCachedThreadPool 是一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。

2)newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

3)newFixedThreadPool 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

4)newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

Thread与Runnable

Android实现多线程的方式有Thread和Runnable,其中Thread是用于继承,Runnable是接口,两者的区别主要在于Java是单继承的,只能继承一个Thread,Runnable是接口,Java中可以实现多个接口。、

守护进程daemon进程

守护线程在非守护线程结束后,会自动结束

Concurrent包

java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,创建 java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。
A、Semaphore:类,控制某个资源可被同时访问的个数;
B、 CountDownLatch: 类,可以用来在一个线程中等待多个线程完成任务的类。
C、ReentrantLock:类,具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大;
D、 Future:接口,表示异步计算的结果;

java threadlocal

ThreadLocal的类声明:

public class ThreadLocal<T>

可以看出ThreadLocal并没有继承自Thread,也没有实现Runnable接口。

ThreadLocal类为每一个线程都维护了自己独有的变量拷贝。每个线程都拥有了自己独立的一个变量。
所以ThreadLocal重要作用并不在于多线程间的数据共享,而是数据的独立

  1. ThreadLocal存放的值是线程封闭,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递
  2. 线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收
  3. 在Thread类中有一个哈希表,用于为每一个线程提供一份变量的副本。
  4. 对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式
0 0
原创粉丝点击