java关键字【synchronized】

2023-12-29 12:25:04

一、是什么

synchronized关键字是java中用于解决多线程并发问题的一种机制,它可以确保同一时刻只有一个线程能够访问共享资源,从而避免了数据竞争和其它并发问题。

1.1. 作用

  • 防止线程干扰和内存一致性错误:通过确保同一时刻只有一个线程能够访问共享资源,可以避免多个线程同时修改共享资源的情况,从而防止线程干扰和内存一致性错误。
  • 确保监视资源的可见性:在多线程环境中,使用synchronized关键字可以确保监视资源的可见性,即当一个线程获得Monitor后,将共享内存中的数据复制到该线程的site的缓存中,其它线程也可以看到该数据。
  • 确保线程间的有序运行:通过synchronized关键字可以确保方法或代码块在操作中的原子性,即同一时间最多只有一个线程访问该方法或代码块,并且不会触发JMM指令重排机制。

1.2. 修饰情况

  • 方法:可以修饰实例方法可静态方法。当一个方法被syncronized修饰时,该方法一次只能被一个线程访问。
  • 代码块:可以修饰实例方法和静态方法中的代码块,叫同步代码块。通常需要在synchronized()中指明进入同步代码块的key,这里的key可以是Object、this或者class。

二、修饰方法

注意:为了保证逻辑中变量count�的原子性,其类型使用AtomicInteger�。

2.1. 代码示例(修饰普通方法)

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> { //线程1
            counter.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            counter.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public synchronized void increase(AtomicInteger count) {
        count.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + ":" + count);
    }
}

输出结果为:
图片.png
说明:因为thread0中的increase()方法和thread1中的increase()方法都是通过counter对象调用,它们用的是同一个对象锁,互斥。所以,thread1一直等到thread0中的increase()方法执行完释放后才开始执行thread1的increase()方法。
修改代码,每个线程分别创建一个Counter对象:

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> { //线程1
            Counter counter1 = new Counter();
            counter1.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            Counter counter2 = new Counter();
            counter2.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public synchronized void increase(AtomicInteger count) {
        count.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + ":" + count);
    }
}

输出结果为:
图片.png
说明:因为thread0中的increase()方法是通过counter1对象调用的,thread1中的increase()方法是通过counter2对象调用,它们用的是两个不同的对象锁,并不互斥。所以,thread1中的increase()方法和thread0中的increase()方法是同时进行的。
结论:使用"同一对象"去调用其被synchronized修饰的普通方法才互斥。

2.2. 代码示例(修饰静态方法)

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> { //线程1
            counter.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            counter.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public synchronized static void increase(AtomicInteger count) {
        count.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + ":" + count);
    }
}

输出结果为:
图片.png
修改代码,每个线程分别创建一个Counter对象:

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> { //线程1
            Counter counter1 = new Counter();
            counter1.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            Counter counter2 = new Counter();
            counter2.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public synchronized static void increase(AtomicInteger count) {
        count.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + ":" + count);
    }
}

输出结果为:
图片.png
说明:因为increase()方法是Counter类里的静态方法,并且被synchronized修饰,所以这个方法是靠唯一的Counter类锁工作,所以无论是通过Counter类调用还是new不同的counter对象,都是互斥的。
结论: 被synchronized修饰的静态方法靠类锁工作。当多个线程同时访问某个被synchronized修饰的静态方法时,一旦某个进程抢得该类的类锁后,其它进程只能排队。如果该类包含多个被synchronized修饰的静态方法,只要有一个静态方法被某一线程获得类锁,即使其它线程调用其它被synchronized修饰的静态方法也需要排队。

2.3. 原理

它的原理是在方法的flags中增加ACC_SYNCHRONIZED标记,有ACC_SYNCHRONIZED标记的方法在被调用时,调用指令会先去检查方法的ACC_SYNCHRONIZED访问标志是否设置,如果设置了执行线程先要持有同步锁,然后才去执行方法,否则相关线程会被阻塞。
通过以上第一个修饰普通方法代码,反编译increase()结果如下(javap -c -v Counter.class):
图片.png注意:方法要public修饰,否则在反编译代码中看不到。
从反编译结果可以看出ACC_SYNCHRONIZED标记,表明该方法是synchronized的,同时还可能会有ACC_PUBLIC和ACC_STATIC,这两个标记分别表示访问权限和是否是静态方法。

三、修饰代码块

3.1. 代码示例(修饰class)

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> { //线程1
            counter.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            counter.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public void increase(AtomicInteger count) {
        synchronized (Counter.class) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果为:
图片.png
修改代码,每个线程分别创建一个Counter对象:

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> { //线程1
            Counter counter1 = new Counter();
            counter1.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            Counter counter2 = new Counter();
            counter2.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public void increase(AtomicInteger count) {
        synchronized (Counter.class) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
结论:synchronized在修饰class时,同步代码块只有同一个类的实例访问。因此,如果有多个实例属于同一个类,它们将共享同一个锁,并且只能有一个线程同时执行该类的同步代码块。所以,即使创建多个对象也会排序执行被锁的代码块。

3.2. 示例代码(修饰this)

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> { //线程1
            counter.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            counter.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public void increase(AtomicInteger count) {
        synchronized (this) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
修改代码,每个线程分别创建一个Counter对象:

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> { //线程1
            Counter counter1 = new Counter();
            counter1.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            Counter counter2 = new Counter();
            counter2.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    public void increase(AtomicInteger count) {
        synchronized (this) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
结论:synchronized在修饰this时,只能确保同一对象下所有线程代码之间的同步代码块互斥(线程安全),而不同对象的的线程是不受影响的(线程不安全)。

3.3. 示例代码(修饰非静态Object)

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> { //线程1
            counter.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            counter.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    private Object lockObj = new Object();
    public void increase(AtomicInteger count) {
        synchronized (lockObj) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
修改代码,每个线程分别创建一个Counter对象:

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> { //线程1
            Counter counter1 = new Counter();
            counter1.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            Counter counter2 = new Counter();
            counter2.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    private Object lockObj = new Object();
    public void increase(AtomicInteger count) {
        synchronized (lockObj) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
结论:synchronized在修饰非静态object时,只能确保同一对象下所有线程代码之间的同步代码块互斥(线程安全),而不同对象的的线程是不受影响的(线程不安全),类似于this。

3.4. 示例代码(修饰静态Object)

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> { //线程1
            counter.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            counter.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    private static Object lockObj = new Object();
    public void increase(AtomicInteger count) {
        synchronized (lockObj) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
修改代码,每个线程分别创建一个Counter对象:

public class test {
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> { //线程1
            Counter counter1 = new Counter();
            counter1.increase(count);
        });
        Thread thread2 = new Thread(() -> { //线程2
            Counter counter2 = new Counter();
            counter2.increase(count);
        });
        thread1.start();
        thread2.start();
    }
}
class Counter {
    private static Object lockObj = new Object();
    public void increase(AtomicInteger count) {
        synchronized (lockObj) {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }
}

输出结果:
图片.png
结论:synchronized在修饰静态object时,同步代码块只有同一个类的实例访问。因此,如果有多个实例属于同一个类,它们将共享同一个锁,并且只能有一个线程同时执行该类的同步代码块。所以,即使创建多个对象也会排序执行被锁的代码块,类似于class。

3.5. 原理

synchronized修饰代码块是通过monitorenter和monitorexit指令进行同步处理的。monitorenter指令用于进入synchronized代码块或方法,它会尝试获取一个锁,如果锁已经被其它线程持有,则该线程将被阻塞;monitorexit指令用于退出synchronized代码块或方法,它会释放锁,这样其它等待的线程就可以获取到锁并执行。
通过以上第一个修饰class代码,反编译increase()结果如下(javap -c -v Counter.class):
图片.png

文章来源:https://blog.csdn.net/weixin_42049505/article/details/135263147
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。