java 同步锁静态方法_Java多线程-synchronized使用分析

本文详细介绍了Java中synchronized关键字的使用,包括同步方法、同步代码块以及静态同步方法。同时,讨论了同步锁的概念、锁对象的选择以及可重入锁的特性。此外,还分析了synchronized如何确保原子性和内存可见性,通过实例解释了未使用同步可能导致的问题以及如何使用同步解决可见性问题。
摘要由CSDN通过智能技术生成

synchronized是Jdk的内置同步锁,用于实现多线程对共享资源的同步访问。

使用方式:

1.synchronized普通方法

// 同步方法

public synchronized void test() {

// 同步代码块

// 锁对象为当前对象

}

2.synchronized静态方法

// 同步静态方法

public synchronized static void test() {

// 同步代码块

// 锁对象为当前类对象

}

3.synchronized普通代码块

public void test() {

synchronized (this){

// 同步代码块

// 锁对象为当前对象

}

}

4.synchronized静态代码块

public static void test() {

synchronized (Demo.class){

// 同步代码块

// 锁对象为当前类对象

}

}

同步锁

在Java中每一个对象有且仅有一个同步锁,同步锁是依赖于对象而存在的。

同步锁使用场景:多个线程对同一个对象中的实例变量进行并发访问。

对某个方法或某个代码块使用synchronized时,那么当某个线程访问时,就会尝试获取相应对象的同步锁,实现多个线程对加锁部分代码的互斥访问,也就是在某个时间点,对象的同步锁只能被一个线程持有。

例:现有A、B两个线程都需要访问加锁资源,如果A线程先抢到了同步锁,那么B就会获取失败,处于阻塞状态,只能等到A线程释放了该锁资源之后,B线程才能获取到该同步锁,进而访问共享资源。

锁对象

synchronized是对存在同步问题的对象进行加锁。从上面4种不同synchronized方式可以看出,实现对共享资源同步操作的加锁机制:

非静态方法的锁对象是当前类实例;

静态方法的锁对象是当前类;

非静态代码块和静态代码块的锁对象则基于括号里配置的实例;

可重入锁

可重入就是某个线程已经获取某个锁,可以再次获取相同的锁而不会发生死锁。如下,一个线程进入同步方法test1则已经持有了锁,再调用同步方法test2,两个方法的同步锁都是demo对象,然而并不会发生死锁。test3中同步代码块同是如此(经典的案例就是双重检查锁的单例模式)。

public class Demo {

public static void main(String[] args) {

Demo demo = new Demo();

demo.test1();

}

public synchronized void test1() {

// 同步代码块

test2();

}

public synchronized void test2() {

// 同步代码块

}

public synchronized void test3(){

synchronized (this){

synchronized (this){

// 同步代码块

}

}

}

}

Java的可重入锁还有ReentrantLock。

原子性和可见性

综上,synchronized能够实现多线程对共享资源操作的原子性,使多个线程互斥地访问共享资源。此外,synchronized还具有内存可见性。

public class Demo {

public static void main(String[] args) throws InterruptedException {

MyThread myThread = new MyThread();

myThread.start();

while (true) {

if (myThread.flag) {

System.out.println("主线程读到flag=" + myThread.flag);

break;

}

}

}

}

class MyThread extends Thread {

public boolean flag = false;

@Override

public void run() {

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

flag = true;

System.out.println("MyThread线程修改后flag=" + flag);

}

}

以上程序,启动子线程MyThread,并修改子线程中的变量。主线程在启动子线程后监听读取子线程对象中的变量flag,如果flag==true,则结束监听。

但是运行程序后会发现,在子线程中正常修改了flag后,主线程依旧处于while死循环状态。以上存在问题就是:

1.子线程从主存读取flag到自己的工作空间,然后修改flag值,子线程还未来得及将修改后的flag值更新到主存,主线程就已经从主存把flag读到自己的工作内存,工作内存中的flag是false。

2.即使子线程修改flag后并同步到主存,主线程依然使用的的自己工作内存中的flag。(这里可以先去了解下JMM)

以上问题产生的原因就是:多线程操作共享变量时,彼此不可见。

所以如果能让flag被修改后及时更新到主存,并且主线程每次都是从主存访问flag,那么主线程就能正常读取到更新后的值,及时结束while循环。

内存可见性

某个线程更改了某个对象的状态,也需要访问该对象的其他线程能够及时地看到该对象修改后的状态。

JMM关于synchronized的规定:

线程加锁时,将清空工作内存中共享变量的值,从而重新读取最新的值;

线程解锁前必须把共享变量的最新值刷新到主内存中;

线程执行同步代码块的过程:

1.获得互斥锁

2.清空工作内存

3 从主内存中拷贝最新的变量副本到工作内存中

4 执行代码块

5.将更改后的共享变量的值刷新到主存中

6.释放互斥锁

要解决上述while死循环问题,就是对flag的访问使用synchronized加锁实现可见性,如下:

synchronized (myThread){

while (true) {

if (myThread.flag) {

System.out.println("主线程读到flag=" + myThread.flag);

break;

}

}

}

但是,有的小伙伴可能会这么想,使用如下的方式在子线程中对flag的修改进行加锁是否可行呢?

synchronized (this){

flag = true;

}

根据以上的可见性描述,执行完上述同步代码后,子线程会及时将flag的值同步到主存。但是主线程依旧使用的自己工作空间中的副本变量,并没有再次从主线程读取flag。所以在子线程中对flag的修改加锁也不会解决上述的主线程的可见性问题。

在之后锁机制章节中还会对synchronized的实现原理和JDK1.6的优化进行进一步的分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值