- 一. 线程安全问题
- 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个 共享、可变资源 的情况,这种资源可以是:一个变量、一个对象、一个文件等。特别注意以下两点,所以,当多个线程同时访问这种资源的时候,就会存在一个问题:由于每个线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问。
- 共享: 意味着该资源可以由多个线程同时访问;
- 可变: 意味着该资源可以在其生命周期内被修改。
- 多个个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。这里面,这个资源被称为:临界资源。也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。不过,当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性,不会导致线程安全问题。
- 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个 共享、可变资源 的情况,这种资源可以是:一个变量、一个对象、一个文件等。特别注意以下两点,所以,当多个线程同时访问这种资源的时候,就会存在一个问题:由于每个线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问。
- 二. 如何解决线程安全问题
- 实际上,所有的并发模式在解决线程安全问题时,采用的方案都是 序列化访问临界资源 。即在同一时刻,只能有一个线程访问临界资源,也称作 同步互斥访问。换句话说,就是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。在 Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock。
- 三. synchronized 同步方法或者同步块
- 在了解 synchronized 关键字的使用方法之前,我们先来看一个概念:互斥锁,即 能到达到互斥访问目的的锁。举个简单的例子,如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。
- 在 Java 中,可以使用 synchronized 关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
- 1) synchronized方法:在一个对象的方法前加synchronized,使其具有锁,一个线程对其进行调用,只有此线程完成调用后,其他线程才能进行操作。
- 1)当一个线程正在访问一个对象的 synchronized 方法,那么其他线程不能访问该对象的其他 synchronized 方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
- 2)当一个线程正在访问一个对象的 synchronized 方法,那么其他线程能访问该对象的非 synchronized 方法。这个原因很简单,访问非 synchronized 方法不需要获得该对象的锁,假如一个方法没用 synchronized 关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,
- 3)如果一个线程 A 需要访问对象 object1 的 synchronized 方法 fun1,另外一个线程 B 需要访问对象 object2 的 synchronized 方法 fun1,即使 object1 和 object2 是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
- 2) synchronized 同步块:
- 当在某个线程中执行这段代码块,该线程会获取对象lock的锁,从而使得其他线程无法同时访问该代码块。其中,lock 可以是 this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。特别地, 实例同步方法 与 synchronized(this)同步块 是互斥的,因为它们锁的是同一个对象。但与 synchronized(非this)同步块 是异步的,因为它们锁的是不同对象。
- 实现同步的方法:1)利用同步代码块实现;2)利用同步方法实现;
- 1)synchronized(锁对象任意,不可以用匿名对象,不代表同一个锁){需要完整执行的代码块};
- 2)使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的;
- 非静态同步函数的锁是:this
- 静态的同步函数的锁是:该类的字节码对象.class;------------------在方法区由appclassload加载
- 3) class 对象锁
- 特别地,每个类也会有一个锁,静态的 synchronized方法 就是以Class对象作为锁。另外,它可以用来控制对 static 数据成员 (static 数据成员不专属于任何一个对象,是类成员) 的并发访问。并且,如果一个线程执行一个对象的非static synchronized 方法,另外一个线程需要执行这个对象所属类的 static synchronized 方法,也不会发生互斥现象。因为访问 static synchronized 方法占用的是类锁,而访问非 static synchronized 方法占用的是对象锁,所以不存在互斥现象。将synchronized修饰在方法上JVM将隐式调用(this),flag=acc-synchronized;
- 4)内置锁synchronized的实质就是锁在对象的monitor上,反编译之后字节码加入synchronized之后一般会有1个monitorenter,和2个monitorexit(1个对应正常结束释放锁,一个对应异常释放锁----athrow)。只要是同一对象的monitor就可锁上
- 四. 可重入性
- 一般地,当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于 Java 的内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁时,那么这个请求就会成功。可重入锁最大的作用是避免死锁。
- 五. 注意事项
- 1). 内置锁与字符串常量:由于字符串常量池的原因,在大多数情况下,同步synchronized代码块 都不使用 String 作为锁对象,而改用其他,比如 new Object() 实例化一个 Object 对象,因为它并不会被放入缓存中。
- 2). 锁的是对象而非引用:在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程将同时去竞争该锁对象:
- 1).若它们将同时竞争同一把锁,则这些线程之间就是同步的;
- 2).否则,这些线程之间就是异步的。
- 六. 总结
- 用一句话来说,synchronized 内置锁 是一种 对象锁 (锁的是对象而非引用), 作用粒度是对象 ,可以用来实现对 临界资源的同步互斥访问 ,是 可重入 的。特别地,对于 临界资源 有:
- 若该资源是静态的,即被 static 关键字修饰,那么访问它的方法必须是同步且是静态的,synchronized 块必须是 class锁;
- 若该资源是非静态的,即没有被 static 关键字修饰,那么访问它的方法必须是同步的,synchronized 块是实例对象锁;
- 实质上,关键字synchronized 主要包含两个特征:
- 互斥性:保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块;
- 可见性:保证线程工作内存中的变量与公共内存中的变量同步,使多线程读取共享变量时可以获得最新值的使用。
- 用一句话来说,synchronized 内置锁 是一种 对象锁 (锁的是对象而非引用), 作用粒度是对象 ,可以用来实现对 临界资源的同步互斥访问 ,是 可重入 的。特别地,对于 临界资源 有:
Java 并发:内置锁 Synchronized
最新推荐文章于 2024-02-25 17:55:19 发布