线程安全、线程同步

一、线程安全

多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的。

讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会引起此共享资源的不一致性。因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问。

线程安全问题多是由全局变量和静态变量引起的,当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的;当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题。

 用模拟票显示线程安全

package com.oracle.demo07;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Tickets3 implements Runnable {

    private int tickect = 100;
//    创建锁接口对象
Lock lock=new ReentrantLock();

    public void run() {
while(true){
    lock.lock();
    if(tickect>0){
        try {
            Thread.sleep(50);
            System.out.println(Thread.currentThread().getName()+":出售第"+tickect+"张票");
            tickect--;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        不管有没有异常都执行
        finally {
            lock.unlock();
            
        }
        
    }}
    
}
    }
package com.oracle.demo07;

public class demo01 {
    public static void main(String[] args) {
//        创建线程任务
        Tickets3 tickects=new Tickets3();
        Thread t1=new Thread(tickects);
        Thread t2=new Thread(tickects);
        Thread t3=new Thread(tickects);
        t1.start();
        t2.start();
        t3.start();
        
    }

}

二、线程同步(synchronized/Lock)

线程同步:将操作共享数据的代码行作为一个整体,同一时间只允许一个线程执行,执行过程中其他线程不能参与执行。目的是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

(1)同步方法(synchronized)

对共享资源进行访问的方法定义中加上synchronized关键字修饰,使得此方法称为同步方法。可以简单理解成对此方法进行了加锁,其锁对象为当前方法所在的对象自身。多线程环境下,当执行此方法时,首先都要获得此同步锁(且同时最多只有一个线程能够获得),只有当线程执行完此同步方法后,才会释放锁对象,其他的线程才有可能获取此同步锁,以此类推...格式如下:

public synchronized void run() {        
     // ....
}

(2)同步代码块(synchronized)

使用同步方法时,使得整个方法体都成为了同步执行状态,会使得可能出现同步范围过大的情况,于是,针对需要同步的代码可以直接另一种同步方式——同步代码块来解决。格式如下:

synchronized (obj) {        
     // ....
}

其中,obj为锁对象,因此,选择哪一个对象作为锁是至关重要的。一般情况下,都是选择此共享资源对象作为锁对象。

(3)同步锁(Lock)

 使用Lock对象同步锁可以方便地解决选择锁对象的问题,唯一需要注意的一点是Lock对象需要与资源对象同样具有一对一的关系。Lock对象同步锁一般格式为:

复制代码
class X {
    // 显示定义Lock同步锁对象,此对象与共享资源具有一对一关系
    private final Lock lock = new ReentrantLock();
    
    public void m(){
        // 加锁
        lock.lock();
        
        //...  需要进行线程安全同步的代码
        
        // 释放Lock锁
        lock.unlock();
    }
}
复制代码

什么时候需要同步:

(1)可见性同步:在以下情况中必须同步: 1)读取上一次可能是由另一个线程写入的变量 ;2)写入下一次可能由另一个线程读取的变量

(2)一致性同步:当修改多个相关值时,您想要其它线程原子地看到这组更改—— 要么看到全部更改,要么什么也看不到。

这适用于相关数据项(如粒子的位置和速率)和元数据项(如链表中包含的数据值和列表自身中的数据项的链)。

在某些情况中,您不必用同步来将数据从一个线程传递到另一个,因为 JVM 已经隐含地为您执行同步。这些情况包括:

  1. 由静态初始化器(在静态字段上或 static{} 块中的初始化器)
  2. 初始化数据时 
  3. 访问 final 字段时
  4. 在创建线程之前创建对象时 
  5. 线程可以看见它将要处理的对象时

锁的原理:

  • Java中每个对象都有一个内置锁
  • 当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
  • 当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
  • 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
  • 释放锁是指持锁线程退出了synchronized同步方法或代码块。
锁与同步要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放。
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。
10)、同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。
 
线程安全与线程同步的关系:
实现线程安全就是在类的方法里加上了synchronized
如果是多线程同时操作(读取或者修改重点是修改)一个数据 如果这个数据没有在设成synchronized的方法里的加 会造成更新丢失或者数据损坏 这会对你的程序有致命的影响
如果给方法加上synchronized 那这个方法里的数据就都会是线程安全的 不会造成更新丢失或者数据损坏 缺点是会带来额外的系统资源开销
说了这么多其实意思就是你要是写多线程程序就用hashmap 如果是单线程就用hashtable
 
原文地址:https://www.cnblogs.com/-lwl/p/11100996.html