什么是线程并发安全
- 线程安全的本质是能够让并发线程,有序的运行(这个有序可能是先来后到的排队,有可能有人插队,但是不管怎么着,同一时刻只能一个线程有权访问同步资源),线程执行的结果,能够对其他线程可见。
线程安全的几种分类
- synchronized关键字
- ReentrantLock锁
- AtomicInteger…原子类
- 锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
- 原子类适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
如何保证线程安全
- AtomicInteger原子包装类,CAS实现无锁数据更新。自旋的设计有效避免线程因阻塞-唤醒带来的系统资源开销。自旋其实就是一个do-while循环。
- 适用场景:多线程计数,原子操作,并发数量小的场景。
-
synchronized
锁java对象,锁class对象,锁代码块-
锁方法。加在方法上,未获取到对象锁的其他线程都不可以访问该方法。
-
锁class对象。加在static方法上相当于给class对象加锁,哪怕是不同的java对象实例,也需要排队执行。
-
锁代码块。未获取到对象锁的其他线程可以执行同步块之外的代码。
-
-
ReentrantLock 悲观锁,可重入锁,公平锁,非公平锁
- 基本用法
package com.example.myapplication.demo;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
static class ReentrantLockTask{
ReentrantLock reentrantLock = new ReentrantLock();
void buyTicket(){
String name= Thread.currentThread().getName();
try {
reentrantLock.lock();
System.out.println(name+":准备好了");
Thread.sleep(100);
System.out.println(name+":买好了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
public static void main(String[] args) {
final ReentrantLockTask task = new ReentrantLockTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
task.buyTicket();
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
//执行结果:
/*
Thread-2:准备好了
Thread-2:买好了
Thread-3:准备好了
Thread-3:买好了
Thread-4:准备好了
Thread-4:买好了
Thread-5:准备好了
Thread-5:买好了
Thread-6:准备好了
Thread-6:买好了
Thread-7:准备好了
Thread-7:买好了
Thread-8:准备好了
Thread-8:买好了
Thread-9:准备好了
Thread-9:买好了
Thread-10:准备好了
Thread-10:买好了
Thread-11:准备好了
Thread-11:买好了
*/
- 可重入,避免死锁
package com.example.myapplication.demo;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
static class ReentrantLockTask{
ReentrantLock reentrantLock = new ReentrantLock();
void buyTicket(){
String name= Thread.currentThread().getName();
try {
reentrantLock.lock();
System.out.println(name+":准备好了");
Thread.sleep(100);
System.out.println(name+":买好了");
reentrantLock.lock();
System.out.println(name+":又准备好了");
Thread.sleep(100);
System.out.println(name+":又买好了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
reentrantLock.unlock();
}
}
}
public static void main(String[] args) {
final ReentrantLockTask task = new ReentrantLockTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
task.buyTicket();
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
/*
Thread-2:准备好了
Thread-2:买好了
Thread-2:又准备好了
Thread-2:又买好了
Thread-3:准备好了
Thread-3:买好了
Thread-3:又准备好了
Thread-3:又买好了
Thread-4:准备好了
Thread-4:买好了
Thread-4:又准备好了
Thread-4:又买好了
Thread-5:准备好了
Thread-5:买好了
Thread-5:又准备好了
Thread-5:又买好了
Thread-6:准备好了
Thread-6:买好了
Thread-6:又准备好了
Thread-6:又买好了
Thread-7:准备好了
Thread-7:买好了
Thread-7:又准备好了
Thread-7:又买好了
Thread-8:准备好了
Thread-8:买好了
Thread-8:又准备好了
Thread-8:又买好了
Thread-9:准备好了
Thread-9:买好了
Thread-9:又准备好了
Thread-9:又买好了
Thread-10:准备好了
Thread-10:买好了
Thread-10:又准备好了
Thread-10:又买好了
Thread-11:准备好了
Thread-11:买好了
Thread-11:又准备好了
Thread-11:又买好了
*/
-
公平锁与非公平锁
- 公平锁,所有进入阻塞的线程排队依次均有机会执行。使用场景:交易
- 默认非公平锁,允许线程插队,避免每一个线程都进入阻塞,再唤醒,性能高。因为线程可以插队,导致队列中可能会存在线程饿死的情况,一直得不到锁,一直得不到执行。
ReentrantLock reentrantLock = new ReentrantLock(true/false);//true:公平;false不公平
package com.example.myapplication.demo.lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo2 {
static class ReentrantLockTask{
ReentrantLock lock = new ReentrantLock(false);
void print(){
String name = Thread.currentThread().getName();
try {
lock.lock();
System.out.println(name+"第一次打印");
Thread.sleep(1000);
lock.unlock();
lock.lock();
System.out.println(name+"第二次打印");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final ReentrantLockTask task = new ReentrantLockTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
task.print();
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
/*
Thread-2第一次打印
Thread-2第二次打印
Thread-3第一次打印
Thread-3第二次打印
Thread-4第一次打印
Thread-4第二次打印
Thread-5第一次打印
Thread-5第二次打印
Thread-6第一次打印
Thread-6第二次打印
Thread-7第一次打印
Thread-7第二次打印
Thread-8第一次打印
Thread-8第二次打印
Thread-9第一次打印
Thread-9第二次打印
Thread-10第一次打印
Thread-10第二次打印
Thread-11第一次打印
Thread-11第二次打印
*/
package com.example.myapplication.demo.lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo2 {
static class ReentrantLockTask{
ReentrantLock lock = new ReentrantLock(true);
void print(){
String name = Thread.currentThread().getName();
try {
lock.lock();
System.out.println(name+"第一次打印");
Thread.sleep(1000);
lock.unlock();
lock.lock();
System.out.println(name+"第二次打印");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final ReentrantLockTask task = new ReentrantLockTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
task.print();
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
/*
Thread-2第一次打印
Thread-3第一次打印
Thread-4第一次打印
Thread-5第一次打印
Thread-6第一次打印
Thread-7第一次打印
Thread-8第一次打印
Thread-9第一次打印
Thread-10第一次打印
Thread-11第一次打印
Thread-2第二次打印
Thread-3第二次打印
Thread-4第二次打印
Thread-5第二次打印
Thread-6第二次打印
Thread-7第二次打印
Thread-8第二次打印
Thread-9第二次打印
Thread-10第二次打印
Thread-11第二次打印
*/
- ReentrantLock进阶用法 ——Condition条件对象
可以使用它的await-singnal指定唤醒一个(组)线程。相比于wait-notify要么全部唤醒,要么只能唤醒一个,更加灵活可控。
package com.example.myapplication.demo.lock;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo3 {
static class ReentrantLockTask {
private Condition workerCondition, worker2Condition;
ReentrantLock lock = new ReentrantLock(true);
volatile int flag = 0;
public ReentrantLockTask() {
workerCondition = lock.newCondition();
worker2Condition = lock.newCondition();
}
void work1() {
try {
lock.lock();
if (flag == 0 || flag % 2 == 0) {
System.out.println("worker1 休息会");
workerCondition.await();
}
System.out.println("worker1 搬到的砖是:" + flag);
flag = 0;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void work2() {
try {
lock.lock();
if (flag == 0 || flag % 2 != 0) {
System.out.println("worker2 休息会");
worker2Condition.await();
}
System.out.println("worker2 搬到的砖是:" + flag);
flag = 0;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void boss() {
try {
lock.lock();
flag = new Random().nextInt(100);
if (flag % 2 == 0) {
worker2Condition.signal();
System.out.println("唤醒工人2:"+flag);
}else {
workerCondition.signal();
System.out.println("唤醒工人1:"+flag);
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final ReentrantLockTask lockTask = new ReentrantLockTask();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
lockTask.work1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
lockTask.work2();
}
}
}).start();
for (int i = 0; i < 10; i++) {
lockTask.boss();
}
}
}
/*
worker1 休息会
唤醒工人2:82
worker2 搬到的砖是:82
唤醒工人1:17
worker2 休息会
worker1 搬到的砖是:17
唤醒工人1:71
worker1 搬到的砖是:71
唤醒工人1:59
worker1 搬到的砖是:59
唤醒工人1:77
worker1 搬到的砖是:77
唤醒工人1:87
worker1 搬到的砖是:87
唤醒工人2:54
worker1 休息会
worker2 搬到的砖是:54
唤醒工人2:80
worker2 搬到的砖是:80
唤醒工人2:42
worker2 搬到的砖是:42
唤醒工人2:56
worker2 搬到的砖是:56
worker2 休息会
*/
-
ReentrantReadWriteLock 共享锁、排他锁
- 共享锁,所有线程均可同时获得,并发量高,比如在线文档查看
- 排他锁,同一时刻只有一个线程有权修改资源,比如在线文档编辑
package com.example.myapplication.demo.lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantLockDemo4 {
static class ReentrantReadWriteLockTask{
private final ReentrantReadWriteLock.ReadLock readLock;
private final ReentrantReadWriteLock.WriteLock writeLock;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLockTask(){
readLock = lock.readLock();
writeLock = lock.writeLock();
}
void read(){
String name = Thread.currentThread().getName();
try {
readLock.lock();
System.out.println("线程"+name+" 正在获取数据...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
System.out.println("线程"+name+" 释放了读锁...");
}
}
void write(){
String name = Thread.currentThread().getName();
try {
writeLock.lock();
System.out.println("线程"+name+" 正在写入数据...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
System.out.println("线程"+name+" 释放了写锁...");
}
}
}
public static void main(String[] args) {
final ReentrantReadWriteLockTask task = new ReentrantReadWriteLockTask();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.read();//因为是读写锁,所以3个线程的日志会一起打印出来
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.write();
}
}).start();
}
}
}
/*
线程Thread-2 正在获取数据...
线程Thread-3 正在获取数据...
线程Thread-4 正在获取数据...
线程Thread-3 释放了读锁...
线程Thread-6 正在写入数据...
线程Thread-2 释放了读锁...
线程Thread-4 释放了读锁...
线程Thread-6 释放了写锁...
线程Thread-5 正在写入数据...
线程Thread-5 释放了写锁...
线程Thread-7 正在写入数据...
线程Thread-7 释放了写锁...
*/
如何正确的使用锁&原子类
-
减少持锁时间
-
锁分离
-
锁粗化
多次加锁,释放锁合并成一次