并发编程(上)

目录

一、复习回顾

        进程

        线程

        创建线程

        常用方法

        线程状态及生命周期

二、多线程

        优点:

        缺点:

三、并行执行与并发执行

并发执行:

        并行执行:

四、并发编程核心问题(缺点)

一、不可见性

二、乱序性

三、非原子性

三、Volatile关键字

四、如何保证原子性

        一、锁

        二、原子变量

五、原子类

六、CAS

        CAS 机制

        会产生ABA问题:

         如何解决ABA问题:

另:锁分类:

乐观锁/悲观锁

乐观锁:

悲观锁:

可重入锁

读写锁(ReentrantReadWriteLock)

分段锁

自旋锁

共享锁/独占锁

共享锁:

独占锁:

公平锁/非公平锁

公平锁:

非公平锁:

偏向锁/轻量级锁/重量级锁

无锁:

偏向锁:

轻量级锁:

重量级锁:


一、复习回顾

        进程

        进程就是运行时程序,是计算分配内存的资源的最小单位。

        线程

         线程是进程中的一个执行单元,是cpu执行的最小单位。

        创建线程

         1.类 继承 Thread  重写run()

         2.实现Runnable接口 重写run(),  再创建Thread对象,去执行任务

         3.实现Callable接口重写call(),再创建Thread对象,去执行任务call()可以有返回值,可以抛出异常

        4.线程池

        常用方法

        run(),start(),join(),yield(),sleep(),wait(),notify()

        线程状态及生命周期

        并发编程(上)_第1张图片

二、多线程

        优点:

提过程序响应速度,可以多线程完成自己工作,提高了硬件设备利用率。

        缺点:

多线程同时访问共享数据,会出现资源抢夺。

三、并行执行与并发执行

并发执行:

是在一个时间段内,多个线程依次执行。

        并行执行:

两个或多个在同一时间点上一起执行。

四、并发编程核心问题(缺点)

一、不可见性

        一个线程对共享数据修改,另一个线程立即看见,称为可见性。

         由于想让程序响应处理速度更快java内存模型设计有主内存和工作内存(线程使用的内存).

线程中不能直接对主内存中的数据进行操作,必须将主内存数据加载到工作内存(本地内存), 这样在

多核cpu下就会产生不可见性.我们的目标就是要做到可见性。

并发编程(上)_第2张图片

二、乱序性

        为了优化性能,有时候会改变程序中语句的先后顺序。

         volatile 可以解决不可见性, volatile修饰的变量在一个线程修改后,对其他线程立即可见还可以

解决乱序性, volatile修饰的变量在执行时禁止指令重排序,但是不能解决非原子性问题,volatile的

底层实现。

三、非原子性

        一个或多个操作在 CPU 执行的过程中整体执行不被中断,称为原子性。

如何解决非原子性问题?加锁,互斥的,A线程执行时加锁,此时其他线程就不能执行了。

三、Volatile关键字

        一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后:

        1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

         2. 禁止进行指令重排序。

        3. volatile 不能保证对变量操作的原子性。

四、如何保证原子性

        一、锁

        锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的一种实现。

加锁是一种阻塞式方式实现。

        synchronized 是独占锁,一定能保证原子性,因为被synchronized 修饰某段代码 后,无论是单核 CPU 还是多核 CPU,只有一个线程能够执行该代码,所以一定能保证原子操作。synchronized 也能够保证可见性和有序性。

        二、原子变量

        JUC(java.util.concurrent 包)中有 locks 包和 atomic 包,它们可以解决原子性问题。

        原子变量是非阻塞式方式实现。

五、原子类

        在java.util.concurrent 包下面提供一些类,可以在不加锁的情况下,实现++操作的原子性,这

这些类称为原子类 AtomicInteger。

        原子类的原子性是通过 volatile + CAS 实现原子操作的。适合在低并发的情况下使用。

六、CAS

        CAS 机制

        是一种不加锁的实现(乐观锁),采用自旋的思想,当一个线程要进行++操作时,可以

先从内存中取出共享变量,记录一个预估值,然后在工作内存中改变共享变量,当将修改后的变量

写会主内存前,要判断预估值和主内存中的值是否一致,如果一致说明,没有其他线程修改过,如

果不一致,说明其他线程修改过,需要重新获取主内存的共享变量,重复之前的操作。

        原子类内部实现使用了不加锁的CAS机制。线程不会被阻塞,所有的线程都会不不断的重试进

行操作,在访问量大的情况下,会导致cpu消耗过高。

        会产生ABA问题:

        一个线程获取了内存值为A,当线程修改后,要写回内存时,已经有线程改变了内存值B,又

有线程将内存值改回到与当前线程预估值相同的值 A

         如何解决ABA问题:

        使用有版本号的原子类。

另:锁分类:

乐观锁/悲观锁
乐观锁:

        乐观锁其实就是不加锁, 乐观的认为不加锁的并发操作是没有问题的,并发修改时,进行比较,

然后满足条件进行更新,否则再次比较。适合读多写少的情况。

悲观锁:

        悲观锁认为不加锁的是肯定会出问题的,使用java中提供的各种锁实现加锁。适合写操作比较

多的情况。

可重入锁

        当一个线程进入到一个同步方法中,然后在此方法中要调用,另一个同步方法,而且两个方法共

用同一把锁,此时线程是可以进入到另一个同步方法中的。

读写锁(ReentrantReadWriteLock)

         可以实现写锁和读锁 ,读写可以用一个锁实现,都是读的时候,多个线程可以共享这把锁(可以同

时进入),一旦有写的操作,那么就要一个一个操作。读读共享,读写互斥,写写互斥。

分段锁

        jdk8之后,去除了真正的分段锁,现在的分段锁不是锁,是一种实现的思想。将锁的粒度更小化。

 例如:ConcurrentHashMap 没有给方法加锁,用hash表中的第一个节点当做锁的,这样就可以

有多把锁,提高了并发操作效率。

自旋锁

        自旋锁也不是锁,是获取锁的方式。

例如: 原子类,需要改变内存中的变量,需要不断尝试;

          synchronized加锁,其他线程不断尝试获取锁 。

共享锁/独占锁
共享锁:

        多个线程共享一把锁, 读写锁中的读锁,都是读操作时,多个线程可以共用。

独占锁:

        互斥锁。该锁一次只能被一个线程所持有。

        例如:synchronized  ReentrantLock   读写锁的写锁。

公平锁/非公平锁
公平锁:

        按照请求锁的顺序分配,谁先来,先获得锁。

非公平锁:

        不按照请求锁的顺序分配,谁抢到 谁获得 。例如:synchronized。

ReentrantLock底层可以设置为公平锁,也可以设置为非公平锁。 默认非公平锁。

偏向锁/轻量级锁/重量级锁

        偏向锁/轻量级锁/重量级锁是针对synchronized锁的状态。

无锁:

        没有任何线程使用锁对象。

偏向锁:

        就是当前只有一个线程访问,在对象头Mark Word中记录线程id,下次此线程访问时,可以直接

获取锁。

轻量级锁:

        当锁的状态为偏向锁时,此时继续有线程来访问,升级为轻量级锁,会让线程以自旋的方式获取

锁,,先程不会阻塞。

重量级锁:

        当锁的状态为轻量级锁时,线程自旋获取锁的次数到达一定数量时,锁的状态升级为重量级

锁,会让自旋次数多的线程进入到阻塞状态。 原因是访问大时,线程都自旋获得锁,cpu消耗大。  

以上的状态设计,是java为了优化synchronized锁

轻量级锁自旋

重量级锁需要操作系统调度

你可能感兴趣的:(java,开发语言)