一些概念

临界区

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码为临界区。

竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称为发生了竞态条件。

使用synchronized可以解决解决线程安全问题

synchronized避免临界区的竞态条件

synchronized俗称对象锁,它采用互斥的方式让同一时刻最多只有一个线程持有【对象锁】,其它线程想再获取这个对象锁时就会阻塞住,这样就能保证拥有锁的线程可以安全执行临界区内代码,不用担心线程上下文切换。

语法

synchronized(对象) // 线程1, 线程2(blocked)
{
 临界区
}

其实还有一种,就是加在方法上,相当于把整个方法的代码都锁起来,

public synchronized void t(){
    
}

等价于

public void t(){
    synchronized(this){
        
    }
} 

其实还有一种,就是加在静态方法上,锁住类对象

class Test{
    public synchronized static void t(){
    
    }
}
class Test{
    public static void t(){
    	synchronized(Test.class){
            
        }
    }
}

基本使用

static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            // 锁住room,其它线程拿不到锁
            synchronized (room) {
                counter++;
            }
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            synchronized (room) {
                counter--;
            }
        }
    }, "t2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    // 0
    log.debug("{}",counter);
}

思考

synchronized实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打破。

  • 如果把 synchronized(obj) 放在 for 循环的外面,如何理解?

    一样的能锁住,只不过范围变大了,在i<5000加完/减完之前都不会释放锁

  • 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?-

    锁的是不同对象,无效

  • 如果 t1 synchronized(obj) 而 t2 没有加会怎么样?如何理解?

    t2没有锁,进入临界区不用获取锁,不会阻塞,原子性得不到保障

线程八锁

考察锁住的是哪个对象!

synchronized锁this,同一个锁

12或者21

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(n1::a).start();
        new Thread(n1::b).start();
    }
}
@Slf4j
class Number {
    public synchronized void a() {
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }
}

抱着锁等待,没执行完不释放

(等1秒)12或者2(等1秒)1

这里也可知道,sleep不释放锁

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(n1::a).start();
        new Thread(n1::b).start();
    }
}
@Slf4j
class Number {
    public synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }
}

没有加锁,进入临界区不阻塞互斥

3肯定会打印,因为它没有锁,直接进去了。

3(等1秒)12 // c无锁,直接运行,然后a先抢到,再运行b

23(等1秒)1 // b先抢到,然后执行a或c,但是c不用等待而且直接能运行,所以马上输出3

32(等1秒)1 // c无锁,然后b抢线程,运行后最后到a

(但是经实测,还是3(等1秒)12更多,可能是c无需加锁更快,而a先开启线程,所以会比b更快抢占到)

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(n1::a).start();
        new Thread(n1::b).start();
        new Thread(n1::c).start();
    }
}
@Slf4j
class Number {
    public synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
    public void c() {
        log.debug("3");
    }
}

锁对象需要是同一个才有效

2(等1秒)1

它们之前没有互斥,直接执行

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(n1::a).start();
        new Thread(n2::b).start();
    }
}
@Slf4j
class Number {
    public synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}

类对象和this是不同的

synchronized放在静态方法上,锁的是Number.class,而普通方法锁住的是当前对象this。

因此,他们锁的都不是同一样东西,它们同时执行,不互斥。

2(等1秒)1

(等1秒)12

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number(); 
        new Thread(()->{
            n1.a();
        }).start();
        new Thread(n1::b).start();
    }
}
@Slf4j
class Number {
    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}

类对象只有一份

它们锁住同一个对象,就是Number.class,有互斥,所以看谁先执行。

2(等1秒)1

(等1秒)12

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n1.b();
        }).start();
    }
}

@Slf4j
class Number {
    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }

    public static synchronized void b() {
        log.debug("2");
    }
}

类对象和当前对象不是同一份

2(等1秒)1

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n2.b();
        }).start();
    }
}

@Slf4j
class Number {
    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }

}

类对象只有一份

多个对象调用static修饰的synchronized方法,是有互斥的,因为它们的锁是类对象。

2(等1秒)1

(等1秒)12

public class EightLock {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n2.b();
        }).start();
    }
}

@Slf4j
class Number {
    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("1");
    }
    public static synchronized void b() {
        log.debug("2");
    } 
}