java中的同步块使用synchronized标记,所有同步在一个对象上的同步块在同时只能被一个线程进入并执行,可保证其内部的共享变量实现多线程可见性,也叫内置锁

基本使用

1
2
3
4
5

public synchronized void test(){

}

执行方式

  1. 尝试获取锁
  2. 如果获得锁则执行方法体
  3. 如果无法获取锁则等待,并且不断尝试获得锁,一旦锁释放可能发生锁竞争问题 在什么时候获取锁?

线程在加入同步代码块之前获取锁

什么时候释放锁?

退出同步代码块时自动释放锁(不论是正常退出还是抛出异常)

缺点:锁竞争问题,在高并发、线程数量高时会引起 CPU 占用居高不下,或者直接宕机。

JMM 对 synchroized 相关规定

  • 线程解锁前,必须将工作内存中数据刷新到主内存。
  • 线程加锁时,必须先清空工作内存,然后将主内存数据刷新到工作内存

类锁和对象锁

类锁:作用在静态方法上,所有对象共享一把锁,存在锁竞争

对象锁:作用在非静态方法上,一个对象一把锁,多个对象之间不会锁竞争

对象锁

实例方法同步

java实例方法同步在拥有该方法的对象上

​例子

1
2
3
public synchronized void add(int value){
    this.count += value;
}

实例方法中的同步块

用来同步方法中的一部分,同步实例方法使用调用方法本身的实例作为监视器对象

例子

1
2
3
4
5
 public void add(int value){
    synchronized(this){
    this.count += value;
    }
    }

使用this关键字即为调用add方法的实例本身​

synchronized里面除了传入this还可以是任意对象

使用同步块的好处

  • 细度更小
  • 更灵活,可以是任意对象,任意类

类锁

synchronized (类.class)

1
2
3
4
5
6
7
public void test(){

synchronized(类名.class){

}
}

静态方法中的同步块

public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}

和synchronized (类.class)效果相同

内置锁的同步和异步

第一种情况:同时执行同一个对象的两个对象锁方法,出现等待

第二种情况:同时执行同一个对象的一个对象锁方法和普通方法,不等待

第三种情况:同时执行同一个对象/两个对象的两个类锁方法,出现等待

第四种情况:一个类锁一个普通方法(不论是同一个对象还是两个对象),不等待

第五种情况:一个类锁和一个对象锁(不论是同一个对象还是两个对象),不等待

结论:

  1. 类锁和对象锁互不影响
  2. 对象锁只对synchronized修饰的方法同步执行,普通方法异步执行,类锁同理

synchronized的锁重入

  • 同一个线程得到了一个对象的锁后,再次请求该对象时可以再次获得该对象的锁
  • 父子类也可重入

锁失效问题

修改对象的引用锁会失效

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class TheardTest002 {

    private int count0;

    private String lock="lock";

    public void doSomething() {

        synchronized (lock){
            count0++;
            lock="";//修改对象
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "," + new Date().getTime() + ","+ count0);
        }
    }

    public static void main(String[] args) {

        TheardTest002 theardTest0 = new TheardTest002();

        Thread thread0 = new Thread(new Runnable() {
            @Override
            public void run() {
                theardTest0.doSomething();
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                theardTest0.doSomething();
            }
        });
        thread0.start();
        thread1.start();
    }
}

输出

1
2
Thread-0,1522237994517,2
Thread-1,1522237994517,2

上面的代码如果不对lock的引用做修改则是线程安全的依次输出1,2,但是因为lock被改变了,变成两个对象锁了,线程2获取到了新的对象锁,不再互斥

死锁问题

两个或者两个以上线程互相持有锁,永远在互相等待

例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class TheardTest003 {


    public synchronized void doSomething(TheardTest004 theardTest004) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("进入->doSomething");
        //持有theardTest003的锁,尝试获取theardTest004的锁
        theardTest004.doSomething2(this);
        System.out.println("结束->doSomething");
    }


    public class TheardTest004 {
        public synchronized void doSomething2(TheardTest003 theardTest003) {
            //持有theardTest004锁,尝试获取theardTest003的锁
            System.out.println("进入->doSomething2");
            theardTest003.doSomething(this);
            System.out.println("结束->doSomething2");
        }
    }


    public static void main(String[] args) {
        TheardTest003 theardTest003 = new TheardTest003();
        TheardTest004 theardTest004 = new TheardTest003().new TheardTest004();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                theardTest003.doSomething(theardTest004);
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                theardTest004.doSomething2(theardTest003);
            }
        });

        thread1.start();
        thread2.start();
    }

}