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

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

    什么是线程安全

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

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

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

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

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

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

    解决线程安全问题

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

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

    展开全文
  • 同步锁-线程安全问题解决方案

    万次阅读 多人点赞 2021-03-21 15:12:05
    经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象. 上节笔记点这里-进程与线程笔记 我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件: 在多线程程序中 +...

    1 同步锁

    1.1 前言

    经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象.
    上节笔记点这里-进程与线程笔记

    我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件:

    在多线程程序中 + 有共享数据 + 多条语句操作共享数据

    多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务)
    所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题
    那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行

    1.2 同步与异步

    那怎么"把有可能出现问题的代码都包裹起来"呢?我们可以使用synchronized关键字来实现同步效果
    也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的

    接下来介绍下同步与异步的概念:
    同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。
    坏处就是效率会降低,不过保证了安全。
    异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
    坏处就是有安全隐患,效率要高一些。

    1.3 synchronized同步关键字

    1.3.1 写法

    synchronized (锁对象){
    需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
    }

    1.3.2 前提

    同步效果的使用有两个前提:

    • 前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
    • 前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

    1.3.3 特点

    1. synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一!
    2. synchronized同步关键字可以用来修饰方法,称为同步方法
    3. 同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
    4. 但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了

    为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
    因为同步代码块可以保证同一个时刻只有一个线程进入
    但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步

    同步与异步

    1.4.1练习-改造售票案例

    创建包: cn.tedu.tickets
    创建类:TestRunnableV2.java

    package cn.tedu.tickets;
    
    /*本类用于改造多线程售票案例,解决数据安全问题*/
    public class TestRunnableV2 {
        public static void main(String[] args) {
            //5.创建目标业务类对象
            TicketR2 target = new TicketR2();
            //6.创建线程对象
            Thread t1 = new Thread(target);
            Thread t2 = new Thread(target);
            Thread t3 = new Thread(target);
            Thread t4 = new Thread(target);
            //7.以多线程的方式运行
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    /*1.多线程中出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
    /*2.同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
     * 加锁之后,就有了同步(排队)的效果,但是加锁的话,需要考虑:
     * 锁的范围:不能太大,太大,干啥都得排队,也不能太小,太小,锁不住,还是会有安全隐患*/
    //1.创建自定义多线程类
    class TicketR2 implements Runnable {
        //3.定义成员变量,保存票数
        int tickets = 100;
        //创建锁对象
        Object o = new Object();
    
        //2.实现接口中未实现的方法,run()中放着的是我们的业务
        @Override
        public void run() {
            //4.通过循环结构完成业务
            while (true) {
                /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
                 * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
                /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
                //synchronized (new Object()){
                //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
                synchronized (o) {//同步代码块解决的是重卖的问题
                    //如果票数>0就卖票
                    if (tickets > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //4.1打印当前正在售票的线程名以及票数-1
                        System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                    }
                    //4.2退出死循环--没票的时候就结束
                    if (tickets <= 0) break;
                }
            }
        }
    }
    

    1.4.2 练习-改造售票案例

    创建包: cn.tedu.tickets
    创建类:TestThreadV2.java

    package cn.tedu.tickets;
    
    /*本类用于改造多线程售票案例,解决数据安全问题*/
    public class TestThreadV2 {
        public static void main(String[] args) {
            //5.创建多个线程对象并以多线程的方式运行
            TickectT2 t1 = new TickectT2();
            TickectT2 t2 = new TickectT2();
            TickectT2 t3 = new TickectT2();
            TickectT2 t4 = new TickectT2();
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    //1.自定义多线程类
    class TickectT2 extends Thread {
        //3.新增成员变量用来保存票数
        static int tickets = 100;
        //static Object o = new Object();
    
        //2.添加重写的run()来完成业务
        @Override
        public void run() {
            //3.创建循环结构用来卖票
            while (true) {
                //Ctrl+Alt+L调整代码缩进
                //7.添加同步代码块,解决数据安全问题
                //synchronized (new Object()) {
                /*static的Object的对象o这种写法也可以*/
                //synchronized (o) {
                /*我们每通过class关键字创建一个类,就会在工作空间中生成一个唯一对应的类名.class字节码文件
                * 这个类名.class对应的对象我们称之为这个类的字节码对象
                * 字节码对象极其重要,是反射技术的基石,字节码对象中包含了当前类所有的关键信息
                * 所以,用这样一个唯一且明确的对象作为同步代码块的锁对象,再合适不过了*/
                synchronized (TickectT2.class) {/*比较标准的写法*/
                    if(tickets > 0){
                        //6.添加线程休眠,暴露问题
                        try {
                            Thread.sleep(10);//让线程休眠,增加线程状态切换的频率
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //4.1打印当前正在售票的线程名与票数-1
                        System.out.println(getName() + "=" + tickets--);
                    }
                    //4.2给程序设置一个出口,没有票的时候就停止卖票
                    if (tickets <= 0) break;
                }
            }
        }
    }
    

    注意:如果是继承的方式的话,锁对象最好用"类名.class",否则创建自定义线程类多个对象时,无法保证锁的唯一

    1.5 之前遇到过的同步例子

    StringBuffer JDK1.0
    加了synchronized ,性能相对较低(要排队,同步),安全性高
    StringBuilder JDK1.5
    去掉了synchronized,性能更高(不排队,异步),存在安全隐患
    其他同步异步的例子

    快速查找某个类的快捷键:Ctrl+Shift+T

    2 线程创建的其他方式

    2.1 ExecutorService/Executors

    ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理

    • execute(Runnable任务对象) 把任务丢到线程池

    Executors 辅助创建线程池的工具类

    • newFixedThreadPool(int nThreads) 最多n个线程的线程池
    • newCachedThreadPool() 足够多的线程,使任务不必等待
    • newSingleThreadExecutor() 只有一个线程的线程池

    2.2 练习:线程的其他创建方式

    创建包: cn.tedu.tickets
    创建类: TestThreadPool.java

    package cn.tedu.tickets;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /*本类用于测试线程池*/
    public class TestThreadPool {
        public static void main(String[] args) {
            //5.创建接口实现类TicketR3类的对象作为目标业务对象
            TicketR3 target = new TicketR3();
            /*Executors是用来辅助创建线程池的工具类对象
            * 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
            * 创建出来的线程池对象是ExecutorService:用来存储线程的池子,负责:新建/启动/关闭线程*/
            //6.使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService池对象
            ExecutorService pool = Executors.newFixedThreadPool(5);
            for (int i = 0; i < 5; i++) {
                /*execute()让线程池中的线程来执行业务,每次调用都会将一个线程加入到就绪队列*/
                pool.execute(target);/*本方法的参数就是你要执行的业务,也就是目标业务类对象*/
            }
        }
    }
    //同步锁问题解决方案笔记:1.4.1从26行复制到58行,TicketR2改成TicketR3
    //1.创建自定义多线程类
    class TicketR3 implements Runnable {
        //3.定义成员变量,保存票数
        int tickets = 100;
        //创建锁对象
        Object o = new Object();
    
        //2.实现接口中未实现的方法,run()中放着的是我们的业务
        @Override
        public void run() {
            //4.通过循环结构完成业务
            while (true) {
                /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
                 * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
                /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
                //synchronized (new Object()){
                //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
                synchronized (o) {//同步代码块解决的是重卖的问题
                    //如果票数>0就卖票
                    if (tickets > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //4.1打印当前正在售票的线程名以及票数-1
                        System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                    }
                    //4.2退出死循环--没票的时候就结束
                    if (tickets <= 0) break;
                }
            }
        }
    }
    

    3 拓展:线程锁

    3.1 悲观锁和乐观锁

    悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
    悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

    乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
    乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

    3.2 两种常见的锁

    synchronized 互斥锁(悲观锁,有罪假设)

    采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
    每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

    ReentrantLock 排他锁(悲观锁,有罪假设)

    ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

    ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)

    因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
    读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
    读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

    3.3 尝试用读写锁改造售票案例

    package cn.tedu.thread;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * 本类用于改造售票案例,使用可重入读写锁
     * ReentrantReadWriteLock
     * */
    public class TestSaleTicketsV3 {
    	public static void main(String[] args) {
    		SaleTicketsV3 target = new SaleTicketsV3();
    		Thread t1 = new Thread(target);
    		Thread t2 = new Thread(target);
    		Thread t3 = new Thread(target);
    		Thread t4 = new Thread(target);
    		t1.start();
    		t2.start();
    		t3.start();
    		t4.start();
    	}
    }
    class SaleTicketsV3 implements Runnable{
    	static int tickets = 100;
    	//1.定义可重入读写锁对象,静态保证全局唯一
    	static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    	@Override
    	public void run() {
    		while(true) {
    			//2.在操作共享资源前上锁
    			lock.writeLock().lock();
    			try {
    				if(tickets > 0) {
    					try {
    						Thread.sleep(10);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "=" + tickets--);
    				}
    				if(tickets <= 0) break;
    			} catch (Exception e) {
    				e.printStackTrace();
    			}finally {
    				//3.finally{}中释放锁,注意一定要手动释放,防止死锁,否则就独占报错了
    				lock.writeLock().unlock();
    			}
    		}
    	}
    } 
    

    3.4 两种方式的区别

    需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
    与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。

    恭喜你,线程与线程锁的学习可以暂时告一段落啦,接着我们可以继续学习别的内容

    下一节 设计模式 点这里

    展开全文
  • 如何解决线程安全问题

    万次阅读 2021-03-11 11:14:03
    如何解决线程安全问题 怎么解决线程的安全问题呢? 基本上所有解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操作临界资源,操作完了才能让其他线程进行操作,也称作同步...

    如何解决线程安全问题

    怎么解决线程的安全问题呢?

    基本上所有解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操作临界资源,操作完了才能让其他线程进行操作,也称作同步互斥访问。

    在Java中一般采用synchronizedLock来实现同步互斥访问。

    synchronized关键字

    首先我们先来了解一下互斥锁,互斥锁:就是能达到互斥访问目的的锁。

    如果对一个变量加上互斥锁,那么在同一时刻,该变量只能有一个线程能访问,即当一个线程访问临界资源时,其他线程只能等待。

    在Java中,每一个对象都有一个锁标记(monitor),也被称为监视器,当多个线程访问对象时,只有获取了对象的锁才能访问。

    在我们编写代码的时候,可以使用synchronized修饰对象的方法或者代码块,当某个线程访问这个对象synchronized方法或者代码块时,就获取到了这个对象的锁,这个时候其他对象是不能访问的,只能等待获取到锁的这个线程执行完该方法或者代码块之后,才能执行该对象的方法。

    我们来看个示例进一步理解synchronized关键字:

    public class Example {
        public static void main(String[] args)  {
            final InsertData insertData = new InsertData();
            new Thread() {
                public void run() {
                    insertData.insert(Thread.currentThread());
                };
            }.start();
            new Thread() {
                public void run() {
                    insertData.insert(Thread.currentThread());
                };
            }.start();
        }  
    }
    class InsertData {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        public void insert(Thread thread){
            for(int i=0;i<5;i++){
                System.out.println(thread.getName()+"在插入数据"+i);
                arrayList.add(i);
            }
        }
    }
    

    这段代码的执行是随机的(每次结果都不一样):

    Thread-0在插入数据0` `Thread-1在插入数据0` `Thread-1在插入数据1` `Thread-1在插入数据2` `Thread-1在插入数据3` `Thread-1在插入数据4` `Thread-0在插入数据1` `Thread-0在插入数据2` `Thread-0在插入数据3` `Thread-0在插入数据4
    

    现在我们加上synchronized关键字来看看执行结果:

    public synchronized void insert(Thread thread){
         for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入数据"+i);
            arrayList.add(i);
        }
    }
    

    输出:

    Thread-0在插入数据0` `Thread-0在插入数据1` `Thread-0在插入数据2` `Thread-0在插入数据3` `Thread-0在插入数据4` `Thread-1在插入数据0` `Thread-1在插入数据1` `Thread-1在插入数据2` `Thread-1在插入数据3` `Thread-1在插入数据4
    

    可以发现,线程1会等待线程0插入完数据之后再执行,说明线程0和线程1是顺序执行的。

    从这两个示例中,我们可以知道synchronized关键字可以实现方法同步互斥访问。

    在使用synchronized关键字的时候有几个问题需要我们注意:

    1. 在线程调用synchronized的方法时,其他synchronized的方法是不能被访问的,道理很简单,一个对象只有一把锁;
    2. 当一个线程在访问对象的synchronized方法时,其他线程可以访问该对象的非synchronized方法,因为访问非synchronized不需要获取锁,是可以随意访问的;
    3. 如果一个线程A需要访问对象object1synchronized方法fun1,另外一个线程B需要访问对象object2synchronized方法fun1,即使object1object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

    synchronized代码块

    synchronized代码块对于我们优化多线程的代码很有帮助,首先我们来看看它长啥样:

    synchronized(synObject) {}
    

    当在某个线程中执行该段代码时,该线程会获取到该对象的synObject锁,此时其他线程无法访问这段代码块,synchronized的值可以是this代表当前对象,也可以是对象的属性,用对象的属性时,表示的是对象属性的锁。

    有了synchronized代码块,我们可以将上述添加数据的例子修改成如下两种形式:

    class InsertData {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        public void insert(Thread thread){
            synchronized (this) {
                for(int i=0;i<100;i++){
                    System.out.println(thread.getName()+"在插入数据"+i);
                    arrayList.add(i);
                }
            }
        }
    }
    

    上述代码就是synchronized代码块添加锁的两种方式,可以发现添加synchronized代码块,要比直接在方法上添加synchronized关键字更加灵活。

    当我们用sychronized关键字修饰方法时,这个方法只能同时让一个线程访问,但是有时候很可能只有一部分代码需要同步,而这个时候使用sychronized关键字修饰的方法是做不到的,但是使用sychronized代码块就可以实现这个功能。

    并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

    来看一段代码:

    class InsertData {
        private ArrayList<Integer> arrayList = new ArrayList<Integer>();
        private Object object = new Object();
        public void insert(Thread thread){
            synchronized (object) {
                for(int i=0;i<100;i++){
                    System.out.println(thread.getName()+"在插入数据"+i);
                    arrayList.add(i);
                }
            }
        }
    }
    

    执行结果:

    执行insert` `执行insert1` `执行insert1完毕` `执行insert完毕
    
    展开全文
  • 最新的详细测试 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() 的类

    展开全文
  • Java线程安全和非线程安全

    万次阅读 多人点赞 2013-05-16 14:09:47
    ArrayList和Vector有什么区别?...面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuff
  • 线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染...
  • 线程安全 & 线程安全函数 & 线程不安全函数 线程安全 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不...
  • 什么是线程安全?如何保证线程安全

    万次阅读 多人点赞 2019-05-27 23:22:44
    什么是线程安全 参考: 《Java并发编程实践》中对线程安全的定义: 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作...
  • 线程安全与线程不安全

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

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

    千次阅读 2021-09-15 20:58:56
    System.Collections.Concurrent 命名空间下提供多个线程安全集合类,只要多个线程同时访问集合,就应使用这些类来代替 System.Collections 和 System.Collections.Generic 命名空间中的相应类型。 但是,不保证通过...
  • 文章目录目录线程安全与线程非安全C 语言的线程非安全函数(不可重入函数) 线程安全与线程非安全 多线程程序中,线程安全是必须要考虑的因素。 线程安全(Thread Safe)就是在多线程环境中,多个线程在同一时刻对同...
  • 什么叫做线程安全 HashMap Hashtable Collections.synchronizedMap()
  • 线程安全

    万次阅读 2019-01-19 15:32:57
    什么是线程安全  当多个线程同时共享,同一个全局变量或静态变量,做写操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作时不会发生数据冲突问题。 public class ThreadDemo { public ...
  • 证明StringBuffer线程安全,StringBuilder线程不安全证明StringBuffer线程安全StringBuilder线程不安全 不多说直接列代码 解释 结果 源码分析不多说直接列代码 @Test public void testStringBuilderAndStringBuffer...
  • Java多线程:线程安全和非线程安全的集合对象

    万次阅读 多人点赞 2017-01-19 12:03:58
    线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染...
  • 1.什么是线程安全问题 就是 多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的 程序中如果...
  • Java多线程——什么是线程安全和线程不安全

    万次阅读 多人点赞 2016-08-09 14:39:33
    线程安全 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全 就是不提供数据...
  • ArrayList线程不安全与Vector线程安全

    千次阅读 2018-08-30 10:46:22
    首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。...
  • HashMap 线程安全问题

    万次阅读 多人点赞 2019-03-21 21:55:33
    我们紧接着上节ArrayList 线程安全问题讲下HashMap的线程安全问题. 之前看书,书中经常会提及.HashTable是线程安全的,HashMap是线程非安全的.在多线程的情况下, HashMap会出现死循环的情况.此外,还会推荐使用新的JUC...
  • 1.关键点 PHP的线程安全与非线程安全版本,【暂时可认为】只存在与Windows版本中,即常是我们的本地开发环境【如果您使用mac,省略这句】,*nix中则不... 什么是线程安全? Thread Safety means that ...
  • 如果你的代码所在的进程中有多个线程在同时运行,而这些线可能会同时运行这段代码。...很显然可以将集合分为两组,线程安全和非线程安全,Vectore是用同步方法来是实现线程安全的而和他相似的ArrayList是线程不安全的。
  • ConcurrentHashMap是如何实现线程安全

    万次阅读 多人点赞 2019-05-25 18:08:40
    文章目录ConcurrentHashMap是如何实现线程安全的前言相关概念Amdahl定律初始化数据结构时的线程安全总结put操作的线程安全总结扩容操作的线程安全扩容时的get操作多线程协助扩容在什么情况下会进行扩容操作?...
  • 3.3 线程安全分析

    千次阅读 2021-01-31 01:17:44
    3.3 线程安全        线程安全是指当我们多个线程去同时去执行我们写的同一段代码不会发生安全问题,这就是线程安全线程安全意味着代码有效的处理了资源竞争的问题(Race ...
  • Java线程(一):线程安全与不安全

    万次阅读 多人点赞 2012-04-02 12:13:29
    作为一个Java web开发人员,很少也不需要去处理线程,因为服务器已经帮我们处理好了。记得大一刚学Java的时候,老师带着我们做了一个局域网聊天室,用到了AWT、Socket、多线程、I/O,编写的客户端和服务器,当时做...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 914,607
精华内容 365,842
关键字:

线程安全