JAVA多线程基础:同步代码块基本操作与可重入机制(Synchronized Block)

Java中可以使用内置锁机制来支持原子性。具体来说可以使用Synchronized 来修饰代码块,被修饰的代码块在同一个时间只能由获取了锁的线程执行。
同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象当锁。线程会在执行代码块之前尝试获取锁,如果获取不到就进行等待,无论以何种方式执行结束都会释放锁。

同步代码块使用对象作为锁

/**
 * @author eventime
 * 同步代码块演示代码
 */
public class SynchronizedTest {
    private  int getValue() {
        // 定义第一个getValue操作
        return 1;
    }

    private  int getValue2() {
        return 2;
    }
    
    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();
        Thread t1 = new Thread(()-> System.out.println(test.getValue()));
        Thread t2 = new Thread(()-> System.out.println(test.getValue2()));
        t1.start();
        t2.start();

    }
}

执行上面无同步代码块的程序,得到的结果总是1 2 或者 2 1
这是因为代码运行时共有3个thread(main, t1, t2),他们之间并不是串行执行,而是并发执行

/**
 * @author eventime
 * 同步代码块演示代码
 */
public class SynchronizedTest {
    private  synchronized  int getValue() {
        // 定义第一个getValue操作
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return 1;
    }

    private synchronized int getValue2() {
        // 定义第二个getValue操作
        return 2;
    }


    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();
        Thread t1 = new Thread(()-> System.out.println(test.getValue()));
        Thread t2 = new Thread(()-> System.out.println(test.getValue2()));
        t1.start();
        // 主线程sleep,留时间给t1获取锁。
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();

    }
}

我们修改了代码,将两个函数使用synchronized修饰。
在调用t1.start()与t2.start() 之间加入了sleep留给t1充足的时间去获取对象的锁。程序执行结果为 1 2

分析执行结果我们可以看到,在t1获取了test对象的锁后,会先进行5s的休眠。而这个时候主线程1s的休眠结束执行t2.start();而因为test的锁已经被t1获取,那么t2只能等t1执行完毕释放锁后再获取锁执行。

值得注意的是,如果代码块没有被 synchronized 修饰那么访问这个代码块便不需要获取锁,在本例中,如果将getValue2的synchronized去除,那么即使锁被t1占用,t2依旧能正常执行,在主线程睡眠结束后便可输入。最后的执行结果为: 2 1。

可重入机制

简单来说,可重入机制是为了使得当前拥有锁的进程可以再次进入同步代码块。

/**
 * @author eventime
 * 同步代码块演示代码
 */
public class SynchronizedTest {
    private  synchronized  int getValue() {
        // 定义第一个getValue操作
        //这里会首先调用getValue2()
        System.out.println(getValue2());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return 1;
    }

    private synchronized int getValue2() {
        // 定义第二个getValue操作
        return 2;
    }


    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();
        Thread t1 = new Thread(()-> System.out.println(test.getValue()));
        Thread t2 = new Thread(()-> System.out.println(test.getValue2()));
        t1.start();
        // 主线程sleep,留时间给t1获取锁。
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();

    }
}

首先java的同步代码块已经实现了可重入的机制,我们只需要理解他的使用场景。

在上例中,t1已经获取了对象test的锁,我们也知道当线程需要执行同步代码块的时候需要获取锁。在更改后的getValue()方法中,会去执行同步代码块getValue2()。

如果没有可重入机制,那么线程t1会再次去尝试获取test对象的锁,然而这个锁已经被自身获取。无法获取锁那么将无法执行下去,无法执行完毕便无法释放锁。这就出现了死锁。

在可重入机制下,在判断线程t1已经获取了test的锁后,便可直接去执行getValue2()或者其他同步代码块,从而避免了上述问题。具体来说每个锁都会有一个计数值和一个所有者线程,当所有者线程请求锁时,便会在计数值上增加1,当同步代码块执行完毕后,便会在计数值减去1。当技术值为0时便会释放锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值