目录
二、 synchronized(对类、方法、代码块加锁-非公平锁)
四、ReentrantLock(实现lock接口的AQS锁)
对于上文中,我们知道了什么是线程和进程,同时也知道了对于多线程进行同一个资源(变量)操作的时候,会产生线程安全的问题,那么为了这个问题,我们最合理的方式一般有两种:
其一:对该资源进行控制,同一时刻只允许单个线程对此资源进行操作 ;
其二:将该资源分割成若干等分并分配给单个线程,作为私有资源进行处理。
往往第二种方式瞬息万变的外在环境下不太好进行分割控制,故一般我们采用第一种方式;第一种方式的落地方案就是多线程的锁机制。
synchronized是java关键字,主要作用是对以一个类或其对象作为标识,对某段代码块进行锁定,只有获取该标识的线程才能执行synchronized标识作用域(代码块或方法)的代码。当代码块中的代码发生异常时,synchronized会自动释放锁。
一般的如果是直接将synchronized关键字用在了普通方法上,那么默认是加了一个对象锁,如果是放在了静态方法上面,那么就是使用了类锁。对于代码块,形如 xxxx.class的代码块则相当于加上了类锁,如果是使用某个实例对象,这是对这个对象加锁。对于加了锁的执行都需要获取锁之后才能进行,如果中间发生了任何异常,synchronized会自动释放锁资源。
特别注意: synchronized锁对象不要用String、Integer等包装类型的对象。(1. 当其值发生变化的时候,其对象也发生了改变,容易被忽略。2. 例如String lockKey = 123+12等进行凭借的时候,会发生意想不到的情况。)
示例:
- public static void main(String[] args) {
- Object o = new Object();
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (o) {
- System.out.println("I get the lock and please all thread waiting 60s");
- TimeUtils.sleep(1000);
- System.out.println("waiting end!");
- }
- }
- }).start();
-
- // 防止下面代码在run方法之前执行,休息0.1秒
- TimeUtils.sleep(100);
-
- synchronized (o) {
- System.out.println("I get the lock");
- }
- }
结果:

示例:
- public class T_Synchronized {
-
-
- public static void main(String[] args) {
- T_Synchronized t = new T_Synchronized();
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- t.print("我是新建的线程");
- }
- }).start();
-
- // 保证run方法先运行
- TimeUtils.sleep(100);
-
- t.print("我是主函数里面的线程");
- }
-
-
- public synchronized void print(String words) {
- System.out.println("thread:" + Thread.currentThread().getName() + ",print[" + words + "]");
- TimeUtils.sleep(2000);
- System.out.println("thread:" + Thread.currentThread().getName() + " print end");
- }
-
-
- }
结果:

示例:
- public class T03_Synchronized {
-
- public synchronized static void print() {
- System.out.println("T03_Synchronized created " + TimeUtils.currentTime());
- }
-
-
- public static void main(String[] args) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (T03_Synchronized.class) {
- TimeUtils.sleep(1000);
- System.out.println("sleep end");
- }
- }
- }).start();
-
- TimeUtils.sleep(100);
-
- T03_Synchronized.print();
- }
- }
结果:

volatile也是java中的一个较为常见的关键字,主要用于修饰java中的变量,使得这个变量具有在各个线程中的可见性。其主要作用有:
1. 可见性,当被volatile修饰的变量在发生修改时,使用其变量的线程回去主内存更新该值数据,以保证其修改后的数据被各个线程可见。(可见但并不保证其原子性)
2. 有序性,当变量volatile被修饰后,不会被指令重排序掉。
示例:
- public class T01_volatile {
-
- public volatile static Integer num = 0;
-
- public static void main(String[] args) {
-
- // 写线程
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (num < 5) {
- TimeUtils.sleep(1000);
- num++;
- System.out.println(Thread.currentThread().getName() + "--> num has add and now is " + num);
- }
- }
- }).start();
-
- TimeUtils.sleep(500);
-
-
- // 读线程
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (num < 5) {
- TimeUtils.sleep(1000);
- System.out.println(Thread.currentThread().getName() + "--> I read num is " + num);
- }
- }
- }).start();
-
-
- }
-
- }
结果:

示例:
- public class T02_volatile {
-
-
- private static volatile T02_volatile instance;
-
-
- private T02_volatile() {
- }
-
- public T02_volatile getInstance() {
-
- if (instance == null) {
- synchronized (this) {
- if (instance == null) {
- instance = new T02_volatile();
- }
- }
- }
-
- return instance;
-
- }
-
- }
ReentrantLock是是实现了lock接口的AQS(AbstractQueuedSynchronizer)锁,其和synchronized区别主要有:1. 使用lock和unlock方法进行加锁和解锁过程。2. 当发生异常时,synchronized会主动进行释放过程,但是ReentrantLock这需要使用try{}cathe进行异常捕捉和进行释放。3. 其二都是可重入的锁。4. synchronized是非公平锁,但是ReentrantLock是公平锁。
- public class T_ReentrantLock {
-
-
- public static void main(String[] args) {
- Lock lock = new ReentrantLock();
-
- lock.lock();
-
- try {
-
- } catch (Exception e) {
-
- } finally {
- lock.unlock();
- }
-
-
- }
-
-
- }
- public class T01_ReentrantLock {
-
-
- public static void main(String[] args) {
-
- // 创建一个公平锁的对象
- Test test = new Test(true);
-
- for (int i = 0; i < 5; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- test.print();
- }
- }).start();
- }
-
- }
-
-
- public static class Test {
-
- private ReentrantLock lock;
-
- public Test() {
- // 创建非公平的ReentrantLock锁
- this.lock = new ReentrantLock(true);
- }
-
- public Test(boolean fair) {
- this.lock = new ReentrantLock(fair);
- }
-
- public void print() {
- lock.lock();
-
- try {
- System.out.println(Thread.currentThread().getName() + "获取到锁");
- TimeUtils.sleep(1000);
- } catch (Exception e) {
-
- } finally {
- System.out.println(Thread.currentThread().getName() + "释放锁");
- lock.unlock();
- }
- }
-
-
- }
-
-
- }
运行结果:

主要是在4.2.2的基础上,将 Test test = new Test(true); 改成 Test test = new Test(false);
- public class T01_ReentrantLock {
-
-
- public static void main(String[] args) {
-
- Test test = new Test(false);
- for (int i = 0; i < 5; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- test.print();
- }
- }).start();
- }
-
- }
-
-
- public static class Test {
-
- private ReentrantLock lock;
-
- public Test() {
- // 创建非公平的ReentrantLock锁
- this.lock = new ReentrantLock(true);
- }
-
- public Test(boolean fair) {
- this.lock = new ReentrantLock(fair);
- }
-
- public void print() {
- lock.lock();
-
- try {
- System.out.println(Thread.currentThread().getName() + "获取到锁");
- TimeUtils.sleep(1000);
- } catch (Exception e) {
-
- } finally {
- System.out.println(Thread.currentThread().getName() + "释放锁");
- lock.unlock();
- }
- }
-
-
- }
-
-
- }
结果:

cas:从字面意思是先比较再进行值的交换;也就是说比如某个变量x,我们需要将其从值A变成B时,首先我们先知道这个要变的变量x,并且知道其当前的值是x=A,下一步进行值变换的时候要比较一下,在我在准备和正在变换的过程中这个变量x的值还是不是A,如果是这进行变换,不是则失败,这就是cas的概念和核心原理。
ABA问题: 当进行cas的时候会发现一个事情,就是当我们需要将A转换成B时,可能这个值已经从A转换成C再转换成了A,那么此时再进行变更的时候发现其状态已经发生了改变,那么这个问题如何解决了?很简答,加个版本号,那么比较不仅仅只是值比较了,还有其版本比较,每个将此资源修改之后都将更新其版本号,便可以杜绝此类问题 。
我们可以进行以下比较,将数字从0加到一百万,来比较 synchronized、AutomaticInteger、LongAdder的运行效率,可以得出一些结果:
synchronized:所有线程都会被这个锁住,获取到锁的更新后被不公平的分配给其他线程进行增加,速度较慢。
AutomaticInteger:基于CAS的自旋锁,没有加锁和解锁的过程,拼手速了,速度较快。
LongAdder:分段CAS锁,这个会将这个按照策略分配不同的粒度,分段计算后进行相加,对于数量比较大的数据速度应该是这三者最快的。
示例和结果如下:
- public class T01_Cas {
-
- // sync 锁,线程都要阻塞
- static long count1 = 0L;
-
- //CAS操作,无锁原子操作,效率更高
- static AtomicLong count2 = new AtomicLong(0L);
-
- // 分段锁(锁内CAS操作)--将所有的线程分成几个等分,然后将几个线程的数据统一再加起来
- static LongAdder count3 = new LongAdder();
-
- public static void main(String[] args) {
- T01_Cas t01 = new T01_Cas();
-
- t01.syncCount();
-
- t01.AtomicCount();
-
- t01.LongAdderCount();
- }
-
- public void syncCount() {
-
- final Object o = new Object();
- List
threads = new ArrayList<>(10); -
- Long start = System.currentTimeMillis();
-
- for (int i = 0; i < 10; i++) {
- threads.add(new Thread(new Runnable() {
- @Override
- public void run() {
-
- for (int i = 0; i < 100000; i++) {
- synchronized (o) {
- count1++;
- }
- }
-
- }
- }));
- }
-
- for (Thread t : threads) t.start();
- for (Thread t : threads) {
- try {
- t.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- System.out.println("sync 执行结果是:" + count1 + ",执行时间为:" + (System.currentTimeMillis() - start) + "ms");
-
- }
-
- public void AtomicCount() {
-
- final Object o = new Object();
- List
threads = new ArrayList<>(10); -
- Long start = System.currentTimeMillis();
-
- for (int i = 0; i < 10; i++) {
- threads.add(new Thread(new Runnable() {
- @Override
- public void run() {
-
- for (int i = 0; i < 100000; i++) {
- count2.incrementAndGet();
- }
-
- }
- }));
- }
-
- for (Thread t : threads) t.start();
- for (Thread t : threads) {
- try {
- t.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- System.out.println("AtomicLong 执行结果是:" + count2 + ",执行时间为:" + (System.currentTimeMillis() - start) + "ms");
-
- }
-
- public void LongAdderCount() {
-
- final Object o = new Object();
- List
threads = new ArrayList<>(10); -
- Long start = System.currentTimeMillis();
-
- for (int i = 0; i < 10; i++) {
- threads.add(new Thread(new Runnable() {
- @Override
- public void run() {
-
- for (int i = 0; i < 100000; i++) {
- count3.increment();
- }
-
- }
- }));
- }
-
- for (Thread t : threads) t.start();
- for (Thread t : threads) {
- try {
- t.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- System.out.println("LongAdder 执行结果是:" + count1 + ",执行时间为:" + (System.currentTimeMillis() - start) + "ms");
-
- }
- }
结果:

上一章:多线程(一)线程与进程