Java 并发编程 Synchronized

一、Synchronized

1.1 概念

       关键字synchronized是一种内置的锁机制,用来支持原子性(指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉)。
       被synchronized包括的代码叫做同步代码块,synchronized可以保证在同一时刻,只有一个线程可以执行这个同步代码块,同时synchronized可以保证可见性(volatile也可以保证可见性)。
       每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁(而且无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出都能自动释放锁)。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
       Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能持有这种锁。 当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,直到线程B释放这 个锁。如果B永远不释放锁,那么A也将永远地等下去。
       由于每次只能有一个线程执行内置锁保护的代码块,因此,由这个锁保护的同步代码块会以原子方式执行,多个线程在执行该代码块时也不会相互干扰。任何一个执行同步代码块的线程,都不可能看到有其他线程正在执行由同一个锁保护的同步代码块。

1.2 示例
public class Test {
    Object lock = new Object();

    public void hello () {
        synchronized(lock) {
            System.out.println(Thread.currentThread().getName());
            for (int i= 0; i < 10; i++) {
                System.out.println("i: " + i);
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    test.hello();
                }
            });
        }

    }
}
1.3 结果
pool-1-thread-2
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
pool-1-thread-6
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
。。。。。。

从结果可以看出同一时间只有一个线程可以获取锁,在使用完成释放后,下一个线程才能获取。

二、重入

       当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重人”意味着获取锁的操作的粒度是“线程”,而不是“调用”。
       重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没 有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取 计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时, 计数器会相应地递减。当计数值为0时,这个锁将被释放。

public class Widget {
    public synchronized void doSomething() {
    }
}

public class LoggingWidget extends Widget {

    @Override
    public synchronized void doSomething() {
        super.doSomething(); // 掉用父类的doSomething()方法,如果没有重入机制线程将永远阻塞。
    }
}

三、用锁来保护状态

       由于锁能使其保护的代码路径以串行形式来访问,因此可以通过锁来构造一些协议以实现对共享状态的独占访问。只要始终遵循这些协议,就能确保状态的一致性。
       对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。
       每个共享的和可变的变量都应该只由一个锁来保栌,从而使維护人员知道是哪一个锁。
       一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所 有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。在许多线程安全类 中都使用了这种模式,例如Vector和其他的同步集合类。
       并非所有数据都需要锁的保护,只有被多个线程同时访问的可变数据才需要通过锁来保护。
       当某个变量由锁来保护时,意味着在每次访问这个变量时都需要首先获得锁,这样就确保 在同一时刻只有一个线程可以访问这个变量。当类的不变性条件涉及多个状态变量时,那么还 有另外一个需求:在不变性条件中的每个变量都必须由同一个锁来保护。因此可以在单个原子 操作中访问或更新这些变量,从而确保不变性条件不被破坏。
       对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。
       如果同步可以避免竞态条件问题,那么为什么不在每个方法声明时都使用关键字 synchronized呢?事实上,如果不加区别地滥用synchronized,可能导致程序中出现过多的同步。此外,将每个方法都作为同步方法还可能导致活跃性问题 (Liveness)或性能问题(Performance)。此外,如果只是将每个方法都作为同步方法,例如Vector,那么并不足以确保Vector上复合操 作都是原子的:

// 线程不安全
if (!vector.contains(element)){
	vector.add(element)}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书香水墨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值