线程安全 订阅
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。 展开全文
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
信息
外文名
thread
作    用
保证各线程正常且正确的执行
中文名
线程安全
线程安全简介
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
收起全文
精华内容
参与话题
问答
  • 线程安全

    千次阅读 2019-06-22 00:28:56
    什么是线程安全 在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以 正常且正确的执行,不会出现数据污染等意外情况。 如果你的代码在多线程和单线程的情况下执行永远都...

    什么是线程安全

      在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以
      正常且正确的执行,不会出现数据污染等意外情况。
      如果你的代码在多线程和单线程的情况下执行永远都获得相同的结果,那么你的代码就是线程安全的
    

    线程安全问题毫无疑问就是由于多个线程访问的情况下引起的一系列问题;也就是说在多个线程运行的情况下,我们的代码还能按照我们预期的行为去正确的执行。
    当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。

    为什么会出现线程安全问题

    线程安全问题都是由于全局变量及静态变量引起的。当多个线程同时访问临界资源(也称共享资源,
    比如一个对象,对象 中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。
    

    如果每个线程对全局变量或者静态变量只有读操作没有写操作,一般来说是线程安全的;但是如果多个线程同时进行写操作,这时就会出现线程安全问题
    总结几种变量的线程安全问题:

    • 静态变量
      线程不安全。静态变量被该类的所有实例共享,一旦值发生改变,则对于其他实例来说都是可见的,因此线程不安全。
    • 实例变量
      单例时线程不安全,非单例时线程安全。实例变量是实例对象私有的,如果系统中只有一个实例变量;在多线程的情况下,如果值发生改变,则对于其他的对象都可见,所以是非线程安全的。但是如果每个线程都在不同的实例对象中执行,则对象之间的修改互相不影响,所以线程安全。
    • 局部变量
      线程安全。局部变量是定义在方法内部,线程之间不共享,所以是线程安全的。
    • 静态方法
      如果静态方法中没有静态变量,那么就不存在线程安全问题;

    解决线程安全问题

    基本上所有的并发模式在解决线程安全问题上,都采用“序列化访问临界资源”的方案,即在同一时
    刻,只能有一个线程访问临界资源,也称同步互斥访问。
    

    在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。
    使用synchronized同步方法或者同步块解决线程安全问题
    使用Lock解决线程安全问题
    第一次写博客,从此好好学习,天天向上。

    展开全文
  • 【2】多线程线程安全

    万次阅读 2019-11-22 20:58:01
    知识点1:什么是线程安全? 1、为什么有线程安全问题? 知识点2:线程安全解决办法 1、内置的锁 2、同步代码块synchronized 3、同步方法 (1)什么是同步方法? (2)同步方法使用的是什么锁? (3)静态...

    目录

     

    知识点1:什么是线程安全?

    1、为什么有线程安全问题?

    知识点2:线程安全解决办法

    1、内置的锁

    2、同步代码块synchronized

    3、同步方法

    (1)什么是同步方法?

    (2)同步方法使用的是什么锁?

    (3)静态同步函数

    知识点3:多线程死锁

    1、什么是多线程死锁?

    知识点4:Threadlocal

    1、什么是Threadlocal

    2、ThreadLoca实现原理

    知识点5:多线程有三大特性

    1、什么是原子性

    2、什么是可见性

    3、什么是有序性


    知识点1:什么是线程安全?

    1、为什么有线程安全问题?

    当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

    案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。

    代码:

    public class ThreadTrain implements Runnable {
    	private int trainCount = 100;
    
    	@Override
    	public void run() {
    		while (trainCount > 0) {
    			try {
    				Thread.sleep(50);
    			} catch (Exception e) {
    
    			}
    			sale();
    		}
    	}
    
    	public void sale() {
    		if (trainCount > 0) {
    			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
    			trainCount--;
    		}
    	}
    
    	public static void main(String[] args) {
    		ThreadTrain threadTrain = new ThreadTrain();
    		Thread t1 = new Thread(threadTrain, "①号");
    		Thread t2 = new Thread(threadTrain, "②号");
    		t1.start();
    		t2.start();
    	}
    
    }
    

    运行结果:

    一号窗口和二号窗口同时出售火车第九九张,部分火车票会重复出售。

    结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。


    知识点2:线程安全解决办法

    问:如何解决多线程之间线程安全问题

    答:使用多线程之间同步synchronized或使用锁(lock)。

    问:为什么使用线程同步或使用锁能解决线程安全问题呢?

    答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

    问:什么是多线程之间同步

    答:当多个线程共享同一个资源,不会受到其他线程的干扰。

    问:什么是多线程同步

    答:当多个线程共享同一个资源,不会受到其他线程的干扰。

    1、内置的锁

    Java提供了一种内置的锁机制来支持原子性

    每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁

    内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁

    内置锁使用synchronized关键字实现,synchronized关键字有两种用法:

    1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象

    2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活

    2、同步代码块synchronized

    就是将可能会发生线程安全问题的代码,给包括起来。
    synchronized(同一个数据){
     可能会发生线程冲突问题
    }
    就是同步代码块 
    synchronized(对象)//这个对象可以为任意对象 
    { 
        需要被同步的代码 
    } 
    

    对象如同锁,持有锁的线程可以在同步中执行 

    没持有锁的线程即使获取CPU的执行权,也进不去 

    同步的前提: 

    1,必须要有两个或者两个以上的线程 

    2,必须是多个线程使用同一个锁 ,必须保证同步中只能有一个线程在运行 

    好处:解决了多线程的安全问题 

    弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。 

     代码样例:

    public void sale() {
    	synchronized (this) {
    		if (trainCount > 0) {
    			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
    				trainCount--;
    		}
    	}
    }
    

    3、同步方法

    (1)什么是同步方法?

    答:在方法上修饰synchronized 称为同步方法

    代码样例

    public synchronized void sale() {
    		if (trainCount > 0) {
    			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
    			trainCount--;
    		}
    }
    

    (2)同步方法使用的是什么锁?

    答:同步函数使用this锁。

    证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。

    代码:

    class Thread009 implements Runnable {
    	private int trainCount = 100;
    	private Object oj = new Object();
    	public boolean flag = true;
    
    	public void run() {
    
    		if (flag) {
    			while (trainCount > 0) {
    				synchronized (this) {
    					try {
    						Thread.sleep(10);
    					} catch (Exception e) {
    						// TODO: handle exception
    					}
    					if (trainCount > 0) {
    						System.out.println(Thread.currentThread().getName() 
                    + "," + "出售第" + (100 - trainCount + 1) + "票");
    						trainCount--;
    					}
    				}
    
    			}
    		} else {
    			while (trainCount > 0) {
    				sale();
    			}
    
    		}
    
    	}
    
    	public synchronized void sale() {
    
    		try {
    			Thread.sleep(10);
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    		if (trainCount > 0) {
    			System.out.println(Thread.currentThread().getName() 
                   + "," + "出售第" + (100 - trainCount + 1) + "票");
    			trainCount--;
    		}
    
    	}
    }
    
    public class Test009 {
    	public static void main(String[] args) throws InterruptedException {
    		Thread009 threadTrain = new Thread009();
    		Thread t1 = new Thread(threadTrain, "窗口1");
    		Thread t2 = new Thread(threadTrain, "窗口2");
    		t1.start();
    		Thread.sleep(40);
    		threadTrain.flag = false;
    		t2.start();
    
    	}
    }
    

    (3)静态同步函数

    答:什么是静态同步函数?

    方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。

    静态的同步函数使用的锁是  该函数所属字节码文件对象

    可以用 getClass方法获取,也可以用当前  类名.class 表示。

    代码样例:

    public static void sale() {
    		synchronized (ThreadTrain3.class) {
    			if (trainCount > 0) {
    				System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
    				trainCount--;
    			}
    		}
    }
    

    总结:

    synchronized 修饰方法使用锁是当前this锁。

    synchronized 修饰静态方法使用锁是当前类的字节码文件


    知识点3:多线程死锁

    1、什么是多线程死锁?

       答:同步中嵌套同步,导致锁无法释放

     代码:

    class Thread009 implements Runnable {
    	private int trainCount = 100;
    	private Object oj = new Object();
    	public boolean flag = true;
    
    	public void run() {
    
    		if (flag) {
    			while (trainCount > 0) {
    				synchronized (oj) {
    					try {
    						Thread.sleep(10);
    					} catch (Exception e) {
    						// TODO: handle exception
    					}
    					sale();
    				}
    
    			}
    		} else {
    			while (trainCount > 0) {
    				sale();
    			}
    
    		}
    
    	}
    
    	public synchronized void sale() {
    		synchronized (oj) {
    			try {
    				Thread.sleep(10);
    			} catch (Exception e) {
    
    			}
    			if (trainCount > 0) {
    				System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
    				trainCount--;
    			}
    		}
    	}
    }
    
    public class Test009 {
    	public static void main(String[] args) throws InterruptedException {
    		Thread009 threadTrain = new Thread009();
    		Thread t1 = new Thread(threadTrain, "窗口1");
    		Thread t2 = new Thread(threadTrain, "窗口2");
    		t1.start();
    		Thread.sleep(40);
    		threadTrain.flag = false;
    		t2.start();
    
    	}
    }}
    

    知识点4:Threadlocal

    1、什么是Threadlocal

    ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。

     当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

    ThreadLocal的接口方法

    ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

    •       void set(Object value)设置当前线程的线程局部变量的值。

    •       public Object get()该方法返回当前线程所对应的线程局部变量。

    •       public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

    •       protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

    案例:创建三个线程,每个线程生成自己独立序列号。

    代码:

    class Res {
    	// 生成序列号共享变量
    	public static Integer count = 0;
    	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    		protected Integer initialValue() {
    
    			return 0;
    		};
    
    	};
    
    	public Integer getNum() {
    		int count = threadLocal.get() + 1;
    		threadLocal.set(count);
    		return count;
    	}
    }
    
    public class ThreadLocaDemo2 extends Thread {
    	private Res res;
    
    	public ThreadLocaDemo2(Res res) {
    		this.res = res;
    	}
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 3; i++) {
    			System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
    		}
    
    	}
    
    	public static void main(String[] args) {
    		Res res = new Res();
    		ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
    		ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
    		ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
    		threadLocaDemo1.start();
    		threadLocaDemo2.start();
    		threadLocaDemo3.start();
    	}
    
    }
    
    

    2、ThreadLoca实现原理

    ThreadLoca通过map集合

    Map.put(“当前线程”,值);


    知识点5:多线程有三大特性

    原子性、可见性、有序性

    1、什么是原子性

    即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

    一个很经典的例子就是银行账户转账问题:
    比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。

    我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。

    原子性其实就是保证数据一致、线程安全一部分

    2、什么是可见性

    当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

    3、什么是有序性

    程序执行的顺序按照代码的先后顺序执行。

    一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:

    int a = 10;    //语句1

    int r = 2;    //语句2

    a = a + 3;    //语句3

    r = a*a;     //语句4

    则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
    但绝不可能 2-1-4-3,因为这打破了依赖关系。
    显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

     

    展开全文
  • 最新的详细测试 https://www.cnblogs.com/shangxiaofei/p/10465031.html ... String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全) 简要的说, String 类型...

    最新的详细测试

    https://www.cnblogs.com/shangxiaofei/p/10465031.html

     

    转载自https://www.cnblogs.com/xingzc/p/6277581.html侵权删

    String 字符串常量
    StringBuffer 字符串变量(线程安全)
    StringBuilder 字符串变量(非线程安全)

     简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
     而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
     String S1 = “This is only a” + “ simple” + “ test”;
     StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
     你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
     String S1 = “This is only a” + “ simple” + “test”; 其实就是:
     String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
    String S2 = “This is only a”;
    String S3 = “ simple”;
    String S4 = “ test”;
    String S1 = S2 +S3 + S4;
    这时候 JVM 会规规矩矩的按照原来的方式去做


    在大部分情况下 StringBuffer > String
    StringBuffer
    Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
    可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
    StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
    例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
    在大部分情况下 StringBuilder > StringBuffer

    java.lang.StringBuilde
    java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

     

    关于线程和线程不安全:

     

    概述

    编辑

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

    线程安全问题都是由全局变量静态变量引起的。

    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    安全性

    编辑

    类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。

    此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。

    正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

    举例

    编辑

    比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

    单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

    而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。

    那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

    安全程度

    编辑

    线程安全性不是一个非真即假的命题。 Vector 的方法都是同步的,并且 Vector 明确地设计为在多线程环境中工作。但是它的线程安全性是有限制的,即在某些方法之间有状态依赖(类似地,如果在迭代过程中 Vector 被其他线程修改,那么由 Vector.iterator() 返回的 iterator会抛出ConcurrentModificationException)。

    对于 Java 类中常见的线程安全性级别,没有一种分类系统可被广泛接受,不过重要的是在编写类时尽量记录下它们的线程安全行为。

    Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 -- 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 -- 但是这套系统是一个很好的起点。这种分类系统的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)。下面几节分别描述了线程安全性的这五种类别。

    不可变

    不可变的对象一定是线程安全的,并且永远也不需要额外的同步[1] 。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。

    需要注意的是,对于Integer,该类不提供add方法,加法是使用+来直接操作。而+操作是不具线程安全的。这是提供原子操作类AtomicInteger的原因。

    线程安全

    线程安全的对象具有在上面“线程安全”一节中描述的属性 -- 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。

    有条件的

    有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器 -- 由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的 -- 并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。

    如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。

    线程兼容

    线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。

    许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。

    线程对立

    线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类

    展开全文
  • Window线程安全与线程控制函数

    万次阅读 2020-06-21 03:17:52
    线程安全问题 多线程同时访问全局变量:一个线程取值后失去CPU另一个线程取值后也失去CPU,此时它们保存的就是相同的值。也是是说,比如两个线程再次对全区变量做++操作时变量只会被修改为同样的值。 二. 临界...

    一. 线程安全问题

    多线程同时访问全局变量:一个线程取值后失去CPU另一个线程取值后也失去CPU,此时它们保存的就是相同的值。也是是说,比如两个线程再次对全区变量做++操作时变量只会被修改为同样的值。

     

    二. 临界区(线程锁)

    一. 临界区

    临界区:Critical Section (Critical:临界的、关键的)

    1、创建CRITICAL_SECTION:            

    CRITICAL_SECTION cs;            
                
    2、在使用前进行初始化            

    InitializeCriticalSection(&cs);            
                    
    3、在函数中使用:            
                
    DWORD WINAPI 线程A(PVOID pvParam) {            
          EnterCriticalSection(&cs);                        
          //对全局遍历X的操作            
          LeaveCriticalSection(&cs);            
          return(0);            
    }            
    DWORD WINAPI 线程B(PVOID pvParam) {            
          EnterCriticalSection(&g_cs); 
          //对全局遍历X的操作      
          LeaveCriticalSection(&g_cs);            
          return(0);            
    }            
                
    4、删除CRITICAL_SECTION            

    VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);            

     

    临界区结构体:

    typedef struct _RTL_CRITICAL_SECTION {        
        PRTL_CRITICAL_SECTION_DEBUG DebugInfo;        
        LONG LockCount;        
        LONG RecursionCount;        
        HANDLE OwningThread;    
              
        HANDLE LockSemaphore;        
        DWORD SpinCount;        
    } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;        
            
    LockCount:
    它被初始化为数值 -1        
    此数值等于或大于 0 时,表示此临界区被占用        
            
    等待获得临界区的线程数:LockCount - (RecursionCount -1)        
            
    RecursionCount:
    此字段包含所有者线程已经获得该临界区的次数        
            
    OwningThread:
    此字段包含当前占用此临界区的线程的线程标识符        
    此线程 ID 与GetCurrentThreadId 所返回的 ID 相同        
     

    注意:

    1. 只对可能产生安全问题的最小单元进行加锁,否则会降低运行效率。

    2. 一般有几个资源就定义几把锁。

     

    线程死锁(哲学家就餐问题的简化版):

    死锁造成的原因很简单,1. 嵌套加锁 2. 加锁顺序不一致。

    这样就有可能线程1先拿到资源A,线程2又拿到资源B,而且此时都需要对方的资源才能执行完毕,这样就出现了死锁。解决方案也就是避免上面的两条原因就好了。

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <locale.h>
    #include <Windows.h>
    using namespace std;
    HANDLE hThread;
    CRITICAL_SECTION cs;
    CRITICAL_SECTION g_cs;
    int number1 = 0;
    int number2 = 0;
    
    DWORD WINAPI ThreadProc1(LPVOID a)
    {
    	while (1) {
    		EnterCriticalSection(&g_cs);
    		EnterCriticalSection(&cs);
    		cout << ++number1  << "----------" << ++number2 << endl;
    		LeaveCriticalSection(&cs);
    		LeaveCriticalSection(&g_cs);
    	}
    	DeleteCriticalSection(&cs);
    	return 0;
    }
    DWORD WINAPI ThreadProc2(LPVOID a)
    {
    	while (1) {
    		EnterCriticalSection(&cs);
    		EnterCriticalSection(&g_cs);
    		cout << ++number2 << "----------" << ++number1 << endl;
    		LeaveCriticalSection(&g_cs);
    		LeaveCriticalSection(&cs);
    	}
    	DeleteCriticalSection(&g_cs);
    	return 0;
    }
    
    
    int main(int argc, char *argv[])
    {
    	int a = 10;
    	int b = 20;
    	HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1, &a, 0, NULL);
    	HANDLE hThread2 = hThread = ::CreateThread(NULL, 0, ThreadProc2, &b, 0, NULL);
    	InitializeCriticalSection(&cs);
    	InitializeCriticalSection(&g_cs);
    	CloseHandle(hThread1);
    	CloseHandle(hThread2);
    
    	while (1);
    
    	system("pause");
    	return 0;
    }

     

    三. 互斥体

    先来看来个等待函数:

    一. 等待一个对象 

    1. 功能:等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止.

    hHandle:内核对象句柄,可以是进程也可以是线程。

    dwMilliseconds:等待时间,单位是毫秒  INFINITE(-1)一直等待。 

    2. 返回值:            
    WAIT_OBJECT_0(0)            等待对象变为已通知
    WAIT_TIMEOUT(0x102)            超时

    3. 已通知与未通知:

    1、内核对象中的每种对象都可以说是处于已通知或未通知的状态之中。
    2、当线程正在运行的时候,线程内核对象处于未通知状态。
    3、当线程终止运行的时候,它就变为已通知状态。
    4、在内核中就是个BOOL值,运行时FALSE 结束TRUE

    DWORD WaitForSingleObject(			
      HANDLE hHandle,        // handle to object			
      DWORD dwMilliseconds   // time-out interval			
    );			
    

    注意:此函数在传入句柄是线程或进程的时候,函数执行完,不会将已通知状态改为未通知状态。但是对于其他内核对象不是如此,所以避免多次调用以使用终止的内核对象。

     

    二. 等待多个对象 

    1. 字段:

    1、nCount:要查看内核对象的数量
    2、lpHandles:内核对象数组
    3、bWaitAll:等到类型  TRUE 等到所有变为已通知  FALSE 只要有一个变为已通知
    4、dwMilliseconds:超时时间,INFINITE一直等待

    2. 返回值:                                    

    bWaitAll为TRUE时,返回WAIT_OBJECT_0(0) 代码所以内核对象都变成已通知
    bWaitAll为FALSE时,返回最先变成已通知的内核对象在数组中的索引
    WAIT_TIMEOUT(0x102) 超时

    DWORD WaitForMultipleObjects(			
      DWORD nCount,             // number of handles in array			
      CONST HANDLE *lpHandles,  // object-handle array			
      BOOL bWaitAll,            // wait option			
      DWORD dwMilliseconds      // time-out interval			
    );			
    

     

    三. 互斥体

    	//进程一:
    	HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");
    	
    	//进程二:
    	HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ"); //打开形式 是否希望被继承 互斥体名字
    	WaitForSingleObject(g_hMutex,INFINITE);
    	//逻辑代码
    	ReleaseMutex(g_hMutex);
    	
    	//进程三:
    	HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
    	WaitForSingleObject(g_hMutex,INFINITE);
    	//逻辑代码
    	ReleaseMutex(g_hMutex);
    

    LPSECURITY_ATTRIBUTES(安全 参数)这是互斥体第一个参数类型,一般可以通过有没有此类型判断是不是内核对象。 

    互斥体与临界区的区别:

    1、临界区只能用于单个进程间的线程控制。

    2、互斥体可以设定等待超时,但临界区不能。

    3、线程意外终结时,Mutex可以避免无限等待。

    4、Mutex效率没有临界区高。

    互斥体的效率是低于临界区的,因为互斥体是内核对象,需要用户态和内核态的切换。

     

    四. 事件

    一. 内核对象:

    进程、线程、文件、文件映射、事件、互斥体等等都是内核对象。创建内核对象的函数都要求设置一个安全参数(LPSECURITY_ATTRIBUTES),一般传入NULL即可。

    二. 事件

    1. 事件对象的创建:

    HANDLE CreateEvent(                            
      LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性 NULL时为系统默认                            
      BOOL bManualReset,                       // TRUE 通过调用ResetEvent将事件对象标记为未通知 
              TRUE:需要手动改,在使用多线程的时候不改就可以让其他线程也wait到
             
     FALSE:WaitFor之后会自动改为未通知状态
      BOOL bInitialState,                      // 在创建时:TRUE 已通知状态  FALSE未通知状态                            
      LPCTSTR lpName                           // 对象名称 以NULL结尾的字符串:希望在跨进程时得到则添加名字,否则不添加。
    );                            
    返回的是指针的指针,不能直接访问内核,安全。

    2. 事件对象的控制

    BOOL SetEvent(HANDLE hEvent);                       //将对象设置为已通知    
    Resetevent                        //设置为未通知                    

    3、关闭时间对象句柄

    CloseHandle();                            
    关闭句柄后内核对象未必消失,只是让内核对象的计数器减一,需要create和open的引用计数共计为0时才会销毁。

    线程同步:线程互斥,而且还要按照次序对数据进行操作。

    使用临界区并添加flag的方式可以实现。但是这样也会占用CPU时间片,影响效率,互斥体也是如此。线程同步可以依靠事件来实现:
    1. CreateEvent的第二个参数设置为修改,即False。
    2. 生产者线程第三个参数为true能够被wait,消费者线程为false。
    3. 生产者线程先执行生产+1,后将消费者线程设置为活跃;因为第二参数为False,所以生产者线程改为通知状态。
    4. 消费者线程消费-1,后将设置生产者线程执行。

    就这样,生产者线程先执行,然后未通知,设置对方执行(通知)。这就是生产消费者模型。

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <locale.h>
    #include <Windows.h>
    using namespace std;
    //事件和临界区		
    HANDLE g_hSet, g_hClear;
    int g_Max = 10;
    int g_Number = 0;
    
    //生产者线程函数  		
    DWORD WINAPI ThreadProduct(LPVOID pM)
    {
    	for (int i = 0; i < g_Max; i++)
    	{
    		WaitForSingleObject(g_hSet, INFINITE);
    		g_Number = 1;
    		DWORD id = GetCurrentThreadId();
    		printf("生产者%d将数据%d放入缓冲区\n", id, g_Number);
    		SetEvent(g_hClear);
    	}
    	return 0;
    }
    //消费者线程函数		
    DWORD WINAPI ThreadConsumer(LPVOID pM)
    {
    	for (int i = 0; i < g_Max; i++)
    	{
    		WaitForSingleObject(g_hClear, INFINITE);
    		g_Number = 0;
    		DWORD id = GetCurrentThreadId();
    		printf("----消费者%d将数据%d放入缓冲区\n", id, g_Number);
    		SetEvent(g_hSet);
    	}
    	return 0;
    }
    
    int main(int argc, char* argv[])
    {
    
    	HANDLE hThread[2];
    
    	g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL);
    	g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL);
    
    	hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);
    	hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);
    
    	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    	CloseHandle(hThread[0]);
    	CloseHandle(hThread[1]);
    
    	//销毁 	
    	CloseHandle(g_hSet);
    	CloseHandle(g_hClear);
    
    	return 0;
    }
    

     

    五. 信号量

    HANDLE CreateSemaphore(  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
      LONG lInitialCount,                          // initial count 最大并发数
      LONG lMaximumCount,                          // maximum count 线程总数
      LPCTSTR lpName                               // object name Semaphore的名字
    );

    创建                                递减计数                                    递增计数                          销毁

    CreateSemaphore        WaitForSingleObject            ReleaseSemaphore            CloseHandle

    相对于事件,信号量可以用于相当复杂的线程同步控制。

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <locale.h>
    #include <Windows.h>
    using namespace std;
    HANDLE hSemaphore;
    DWORD WINAPI ThreadProduct1(LPVOID lpParameter) {
    	while (1) {
    		WaitForSingleObject(hSemaphore, INFINITE);
    		cout << "ThreadProduct1" << endl;
    		Sleep(500);
    		ReleaseSemaphore(hSemaphore, 1, NULL);
    	}
    	return 0;
    }
    DWORD WINAPI ThreadProduct2(LPVOID lpParameter) {
    	while (1) {
    		WaitForSingleObject(hSemaphore, INFINITE);
    		cout << "ThreadProduct2" << endl;
    		Sleep(500);
    		ReleaseSemaphore(hSemaphore, 1, NULL);
    	}
    	return 0;
    }
    DWORD WINAPI ThreadProduct3(LPVOID lpParameter) {
    	while (1) {
    		WaitForSingleObject(hSemaphore, INFINITE);
    		cout << "ThreadProduct3" << endl;
    		Sleep(500);
    		ReleaseSemaphore(hSemaphore, 1, NULL);
    	}
    	return 0;
    }
    int main(int argc, char* argv[])
    {
    	hSemaphore = CreateSemaphore(NULL, 0, 3, NULL);
    	
    	HANDLE hThread[3];
    	hThread[0] = ::CreateThread(NULL, 0, ThreadProduct1, NULL, 0, NULL);
    	hThread[1] = ::CreateThread(NULL, 0, ThreadProduct2, NULL, 0, NULL);
    	hThread[2] = ::CreateThread(NULL, 0, ThreadProduct3, NULL, 0, NULL);
    	
    	ReleaseSemaphore(hSemaphore, 1, NULL);
    	WaitForMultipleObjects(3, hThread, TRUE, INFINITE);
    	
    	CloseHandle(hThread[0]);
    	CloseHandle(hThread[1]);
    	CloseHandle(hThread[2]);
    	CloseHandle(hSemaphore);
    
    	system("pause");
    	return 0;
    }
    

     

    展开全文
  • ArrayList扩容机制以及线程安全

    万次阅读 2020-05-06 20:49:10
    List扩容实现步骤 总的来说就是分两步: 1、扩容 ​ 把原来的数组复制到另一个内存空间更大的数组中 2、添加元素 ​ 把新元素添加到扩容以后的数组中 性能分析 ArrayList作为动态数组,其内部元素以数组形式顺序...
  • 线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染...
  • 文章目录目录线程安全与线程非安全C 语言的线程非安全函数(不可重入函数) 线程安全与线程非安全 多线程程序中,线程安全是必须要考虑的因素。 线程安全(Thread Safe)就是在多线程环境中,多个线程在同一时刻对同...
  • 文章目录不共享实例变量共享实例变量解决方案 不共享实例变量 测试代码 public class MyThreadNotShared extends Thread { private int count = 5; public MyThreadNotShared(String name){ ...
  • ArrayList 线程安全问题

    万次阅读 2019-03-21 17:33:39
    皆会说明, ArrayList与HashMap类型都不是线程安全的. 那么,在传统的集合包内的集合类到底为什么线程非安全呢?在新的JUC包类又有什么可以替代呢? 让我们开始今天的部分. 本章主要包括如下几个部分: 为什么ArrayList...
  • 线程安全与线程不安全

    千次阅读 2019-06-08 16:08:29
    1、是线程安全与线程不安全 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 ...
  • 1.什么是线程安全问题 就是 多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的 程序中如果...
  • java中的线程安全是什么: 就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问。 什么叫线程...
  • 线程安全:是多线程访问时,采用加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 非线程安全:是多线程访问时,...
  • HashMap 线程安全问题

    千次阅读 2019-03-21 21:55:33
    我们紧接着上节ArrayList 线程安全问题讲下HashMap的线程安全问题. 之前看书,书中经常会提及.HashTable是线程安全的,HashMap是线程非安全的.在多线程的情况下, HashMap会出现死循环的情况.此外,还会推荐使用新的JUC...
  • PHP的线程安全与非线程安全版本

    千次阅读 2019-07-20 16:08:48
    1.关键点 PHP的线程安全与非线程安全版本,【暂时可认为】只存在与Windows版本中,即常是我们的本地开发环境【如果您使用mac,省略这句】,*nix中则不... 什么是线程安全? Thread Safety means that ...
  • 有如下代码,想问下这种情况下会有线程安全问题吗,我回调方法里去赋值this.result1和this.result2,然后在最后去读者两个字段,读和写不是同一线程不用同步吗? ``` public void newInit() throws ...
  • 线程安全

    万次阅读 2019-01-19 15:32:57
    什么是线程安全  当多个线程同时共享,同一个全局变量或静态变量,做写操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作时不会发生数据冲突问题。 public class ThreadDemo { public ...
  • 5个步骤,教你瞬间明白线程和线程安全

    万次阅读 多人点赞 2018-09-01 16:23:22
    作者 |一个程序员的成长责编 | 胡巍巍记得今年3月份刚来杭州面试的时候,有一家公司的技术总监问了我这样一个问题:你来说说有哪些线程安全的类?我心里一想,这我早都背好了...
  • 1、线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 2、线程不安全: 是指不提供加锁机制保护,有可能出现多个线程...
  • lettuce底层通过netty实现了多线程安全,即一个tcp连接可以同时处理多个redis操作, 我现在比较疑问的是,lettuce怎么保证返回的结果能够对应各自发送的redis操作。 ``` @Test public void testLettuce() ...
  • Java 提供了不同层面的线程安全支持。在传统集合框架内部,除了 Hashtable 等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用 Collections 工具类提供的包装方法,来获取一个同步的包装...
  • 哪些集合类是线程安全

    千次阅读 2019-05-06 16:43:48
    哪些集合类是线程安全的? Vector Stack Hashtable java.util.concurrent包下所有的集合类 ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque... 【Java面试题与...
  • Java线程安全与不安全

    万次阅读 2019-06-05 10:12:54
    Java非线程安全线程安全 ArrayList和Vector的区别在哪里? HashMap和HashTable区别在哪里? StringBuilder和StringBuffer区别在哪里? 张口即答,区别在于前者是非线程安全的,后者是线程是线程安全的。 那么...

空空如也

1 2 3 4 5 ... 20
收藏数 101,393
精华内容 40,557
关键字:

线程安全