一、引入
先了解什么是synchronized
不加 synchronzied 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的),记住一点,synchronized不是锁住了方法,而是锁住了使用了该方法的类对象或者对象实例,如:
1. 锁住对象实例:
class Test{
public synchronized void test() {
}
}
//等价于
class Test{
public void test() {
synchronized(this) {
}
}
}
2. 锁住类对象:
class Test{
public synchronized static void test() {
}
}
//等价于
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
线程八锁的重点:
- 非静态方法的默认锁是this,静态方法的默认锁是class
- 某一时刻内,只能有一个线程有锁,无论几个方法
- 同一个类对象中的静态方法和非静态方法上锁,即对象实例上锁和类对象上锁,之间不会冲突
- 不同对象实例间非静态方法执行不会冲突,非静态方法属于实例
- 不同对象实例间静态方法执行会冲突,静态方法属于类
二、“八锁”
1. 对象实例:
情况1:12 或 21
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
解释:
线程数:2个线程
锁:对象实例
两个线程是对同一个实例对象上锁,a()
方法或b()
方法谁都可能先执行,先执行的会上锁,后面线程得等前一个线程结束才可以执行,通准确来说是同步,通俗讲是串行
情况2:1s后12,或 2 1s后 1
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
解释:
线程数:2个线程
锁:对象实例
两个线程是对同一个实例对象上锁,a()
方法的sleep
并不是线程阻塞,是这个线程在运行中模拟有这么一段处理时间,a()
方法或b()
方法谁都可能先执行,先执行的会上锁,后面线程得等前一个线程结束才可以执行
情况3:3 1s 12 或 23 1s 1 或 32 1s 1
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
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();
}
解释:
线程数:3个线程
锁:对象实例
a()
方法和b()
方法是对同一个实例对象上锁,两者不能并行
运行,只能串行
执行,c()方法没有锁,a()
方法或b()
运行时不影响c()
方法并行运行,反过来c()
方法运行a()
和b()
方法依旧可以运行
通过并行运行可以得出,最先输出的只能是2或3,因为a()
方法可以先执行,但不会先输出1,如果对哪个方法先执行有疑虑,我可以告诉你,哪个方法都可以先执行,只是只能通过输出来看
情况4:2 1s 后 1
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
解释:
线程数:2个线程
锁:对象实例
两个线程是对不同实例对象上锁,线程之间不会影响,可以并行执行,但b()
方法比a()
更早输出
2. 对象实例和类对象混合:
情况1:2 1s 后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
解释:
线程数:2个线程
锁:对象实例和类对象
引入那里我已经给大家补了,a()
上的锁是类对象,b()
方法上的锁是实例对象,即重点第三点,两者是独立执行,谁都不会阻塞,因此,b()
方法输出比a()
更快
情况2:1s 后12, 或 2 1s后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
解释:
线程数:2个线程
锁:类对象
两个线程是对同一个类对象上锁,先执行的上锁,后面的阻塞,因此先执行第一个线程,第二个才可以执行,结果会出现两种
情况3:2 1s 后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
解释:
线程数:2个线程
锁:对象实例和类对象
两个线程执行的是不同对象的方法,非静态方法和静态方法(类和实例)、非静态方法和非静态方法(实例与实例,两个不同实例)之间不会冲突,可以并行运行,并行是相对于多核的,单核那叫时间片轮询执行,看起来也像是并行,不过是内核在做上下文切换,影响的是性能
情况4:1s 后12, 或 2 1s后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
解释:
线程数:2个线程
锁:类对象
与情况二类似,上锁的根本在于同一个类,对不同实例是没有毛钱关系,所以这里也会有上锁,会发生阻塞
三、总结
线程八锁,实际是总结出来也就是,上锁【类、实例】和线程执行方法【是否同实例】的划分,而增加sleep是为了能够更好的展示效果,能够更好去理解线程。