Synchronized 的使用

一、Synchronized 的意义

Synchronized 关键字是 Java 用来解决多线程之间访问共享变量的线程安全问题的,它是一种阻塞式的解决方案,可以保证只有一个线程来访问临界区,进而确保线程安全。

下面是线程不安全的例子:

/**
 * 共享带来的问题
 * @author 落霞不孤
 */
@Slf4j(topic = "c.Test17")
public class Test17 {
    static int counter = 0;
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
               counter--;
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", counter);
    }
}

一共有两个线程,一个线程对共享变量 counter 自增,另外一个线程自减。在多次运行后可以发现,结果是不稳定的,有时是整数,有时是负数或者0。之所以出现这个现象,是因为共享变量 counter 的自加或自减不是原子性的。下面是变量 counter++ 在 JVM 的字节码:

getstatic counter // 获取静态变量counter的值
iconst_1 // 准备常量1
iadd // 自增
putstatic counter // 将修改后的值存入静态变量counter

如果 t1 线程先执行,获取到 counter 的值(0),执行了自增操作,还没有来得及写入,此时发生线程上下文切换。那么 t2 线程获取到 counter 的值是 0,执行自减操作,并写入 counter,即 counter = -1t2 线程执行完毕。轮到 t1 线程执行,根据程序计数器找到需要执行的语句,把上次的自增结果 1 写入,执行完毕。我们可以发现,此时的结果是 1 并不是预料中的 0。出现负数的情况也类似,这里就不叙述了。

为什么会出现这种结果呢?聪明的你一定能猜到,不就是因为访问共享变量的操作不是原子性的吗?我们只要确保两个线程访问共享变量的操作是同步的就行,就好像有一间房子,两个人都想入住,但房间只能给一个人住。谁也不想被打扰,我们只要在门口加把锁就行。等我们不住了,就把锁开了,给其他人住。而 Java 实现这种机制正是靠关键字 syschronized,锁的对象是任意的,但是要确保访问同一个共享变量的所有线程用的都是同一把锁。

二、Synchronized 的使用以及注意事项

使用

基本语法:
java synchronized(对象) { 临界区 }

三种使用方式:

  1. 加在同步代码块

    class Test{
        public void test() {
            synchronized(obj) {
            }
        }
    } 
    
  2. 加在普通方法上

    class Test {
        public synchronized void test() {
            
        }
    }
    等价于
    class Test{
        public void test() {
            synchronized(this) {
            }
        }
    } 
    
  3. 加在静态方法上

    class Test{
        public synchronized static void test() {
            
        }
    }
    等价于
    class Test{
        public static void test() {
            synchronized(Test.class) {
            }
        }
    }
    

注意事项

当我们使用 synchronized时,要注意线程使用的锁对象是不是同一个。如果不是同一个,那线程的安全是无法保证的,这点务必牢记。我们也可以通过这个特点,快速地判断出一段程序是不是线程安全的。

三、字节码的反编译

首先准备好需要反编译的 java 源文件,可以直接通过 java 的命名实现字节码的反编译(需要配置 JDK 环境变量

  1. 编译 java 源文件

     javac fileName.java
    
  2. 反编译生成的字节码

      javap -c fileName
    

如果需要把反编译的结果保存到文件中的话,可以使用重定向技术。
cmd javap -c fileName > testFile.txt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值