目录
二、 synchronized(对类、方法、代码块加锁-非公平锁)
四、ReentrantLock(实现lock接口的AQS锁)
一、前言:
对于上文中,我们知道了什么是线程和进程,同时也知道了对于多线程进行同一个资源(变量)操作的时候,会产生线程安全的问题,那么为了这个问题,我们最合理的方式一般有两种:
其一:对该资源进行控制,同一时刻只允许单个线程对此资源进行操作 ;
其二:将该资源分割成若干等分并分配给单个线程,作为私有资源进行处理。
往往第二种方式瞬息万变的外在环境下不太好进行分割控制,故一般我们采用第一种方式;第一种方式的落地方案就是多线程的锁机制。
二、 synchronized(对类、方法、代码块加锁-非公平锁)
2.1 概念
synchronized是java关键字,主要作用是对以一个类或其对象作为标识,对某段代码块进行锁定,只有获取该标识的线程才能执行synchronized标识作用域(代码块或方法)的代码。当代码块中的代码发生异常时,synchronized会自动释放锁。
一般的如果是直接将synchronized关键字用在了普通方法上,那么默认是加了一个对象锁,如果是放在了静态方法上面,那么就是使用了类锁。对于代码块,形如 xxxx.class的代码块则相当于加上了类锁,如果是使用某个实例对象,这是对这个对象加锁。对于加了锁的执行都需要获取锁之后才能进行,如果中间发生了任何异常,synchronized会自动释放锁资源。
特别注意: synchronized锁对象不要用String、Integer等包装类型的对象。(1. 当其值发生变化的时候,其对象也发生了改变,容易被忽略。2. 例如String lockKey = 123+12等进行凭借的时候,会发生意想不到的情况。)
2.2 用法
2.2.1 对方法中的代码块进行加对象锁(对象锁)
示例:
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");
}
}
结果:
2.2.2 对普通方法进行加锁(对象锁)
示例:
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");
}
}
结果:
2.2.3 对静态方法加锁(类锁)
示例:
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 (声明对多线程都可见的资源信息)
3.1 概念
volatile也是java中的一个较为常见的关键字,主要用于修饰java中的变量,使得这个变量具有在各个线程中的可见性。其主要作用有:
1. 可见性,当被volatile修饰的变量在发生修改时,使用其变量的线程回去主内存更新该值数据,以保证其修改后的数据被各个线程可见。(可见但并不保证其原子性)
2. 有序性,当变量volatile被修饰后,不会被指令重排序掉。
3.2 用法
3.2.1 可见性(一个线程修改,一个线程观察)
示例:
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();
}
}
结果:
3.2.2 有序性(经典的单例设计模式的例子)
示例:
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锁)
4.1 概念
ReentrantLock是是实现了lock接口的AQS(AbstractQueuedSynchronizer)锁,其和synchronized区别主要有:1. 使用lock和unlock方法进行加锁和解锁过程。2. 当发生异常时,synchronized会主动进行释放过程,但是ReentrantLock这需要使用try{}cathe进行异常捕捉和进行释放。3. 其二都是可重入的锁。4. synchronized是非公平锁,但是ReentrantLock是公平锁。
4.2 用法
4.2.1 使用ReentrantLock进行加解锁
public class T_ReentrantLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
try {
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
4.2.2 公平锁示范(按照顺序获取到锁)
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.3 非公平锁示范 (随机获取到锁)
主要是在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 (compare and swap 比较交换)
5.1 cas概念
cas:从字面意思是先比较再进行值的交换;也就是说比如某个变量x,我们需要将其从值A变成B时,首先我们先知道这个要变的变量x,并且知道其当前的值是x=A,下一步进行值变换的时候要比较一下,在我在准备和正在变换的过程中这个变量x的值还是不是A,如果是这进行变换,不是则失败,这就是cas的概念和核心原理。
ABA问题: 当进行cas的时候会发现一个事情,就是当我们需要将A转换成B时,可能这个值已经从A转换成C再转换成了A,那么此时再进行变更的时候发现其状态已经发生了改变,那么这个问题如何解决了?很简答,加个版本号,那么比较不仅仅只是值比较了,还有其版本比较,每个将此资源修改之后都将更新其版本号,便可以杜绝此类问题 。
5.2 synchronized与CAS进行比较
我们可以进行以下比较,将数字从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<Thread> 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<Thread> 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<Thread> 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");
}
}
结果:
上一章:多线程(一)线程与进程