程序都避免不了使用线程,但是当不同的线程同时操作同一个变量、同一个对象时,如果不进行控制,那么程序的结果就极大可能不是我们想要的。
例如:有一个变量a,初始值为0;
线程A、线程B都分别对变量a进行累加100次,每次增加1;
如果不加任何控制,那最后的结果就不会是200了。
这种时候,我们就需要对相应部分的代码进行控制,就是加个锁。
什么是锁?
①类锁
以一个类来作为锁,常见于单例模式,还有修饰静态的方法。
因为方法是静态的,也就是说这个类和方法不需要经过实例化就可以调用,所以如果要加锁控制,同样也不能要求实例化,所以叫类锁。
/**
* synchronized 内置🔒 JDK提供的 内部已经封装
*/
public class GPSEngine {
private static GPSEngine gpsEngine;
public static synchronized GPSEngine getInstance() {
if (gpsEngine == null) {
// Thread-1 释放CPU执行器 Thread-0来执行了 结果 new GPSEngine 此时Thread-1获得执行资格 有 new
gpsEngine = new GPSEngine();
}
return gpsEngine;
}
/*public static GPSEngine getGpsEngine() {
if (null == gpsEngine) {
synchronized (GPSEngine.class) { // 类锁 还没有new GPSEngine == 没有this
if (null == gpsEngine) {
gpsEngine = new GPSEngine();
}
}
}
return gpsEngine;
}*/
}
②对象锁
区别于类锁,对象锁就是用一个对象类作为锁。
/**
* synchronized 内置
*
* 类说明:synchronized的作用
*
* 对象锁
*
*/
public class SynTest {
private long count =0;
private Object obj = new Object(); // 作为一个锁 对象锁obj
private String str = new String(); // 作为一个锁 对象锁str
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
// count进行累加
public void incCount(){
// T1 T2
synchronized (obj){ // 使用一把锁
// T0 T1 T2 == 多个线程 并发问题
count++;
}
}
// count进行累加
// synchronized == 对象锁 == this
public synchronized void incCount2(){
count++;
}
// count进行累加
// this == 对象锁
public void incCount3(){
synchronized (this){
count++;
}
}
// 线程
private static class Count extends Thread{
private SynTest simplOper;
public Count(SynTest simplOper) {
this.simplOper = simplOper;
}
@Override
public void run() {
for(int i=0;i<10000;i++){
simplOper.incCount(); // count = count+10000
}
}
}
public static void main(String[] args) throws InterruptedException {
SynTest simplOper = new SynTest();
// 启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
// 2w
System.out.println(simplOper.count);//20000
}
}
③显示锁
显示锁,其实就是自定义锁,就是说锁的行为可以由我们自己来自由控制。
而上面的类锁和对象锁,都是由synchronize来帮我们实现的,我们不需要管太多。
/**
* 使用显示锁的范式
*/
public class LockDemo {
private int count = 0;
// 内置锁 == this
private synchronized void test() {
}
// 内置锁 == LockDemo.class
private static synchronized void test2() {
}
private synchronized void test3() {
// 业务逻辑,无法被中断
}
// 声明一个显示锁之可重入锁 new 可重入锁
// 非公平锁
private Lock lock = new ReentrantLock();
public void incr(){
// 使用 显示锁 的规范
lock.lock();
try{
count++;
} finally { // 打死都要执行 最后一定会执行
lock.unlock();
}
}
// 可重入锁🔒 意思就是递归调用自己,锁可以释放出来
// synchronized == 天生就是 可重入锁🔒
// 如果是非重入锁🔒 ,就会自己把自己锁死
public synchronized void incr2(){
count++;
incr2();
}
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
}
}
死锁是怎么造成的?
死锁就是程序被彻底锁住了,导致程序无法继续执行。
一般死锁的原因就是synchronize嵌套使用了,避免这种情况,基本就避免了死锁。
线程安全的应用场景
生产者消费者模式,就是很经典的例子。
而其中音视频的处理,就是其中之一,因为音频和视频我们需要达到同步。