精华内容
下载资源
问答
  • 什么是块损坏: 所谓损坏的数据没有采用可识别的 Oracle 格式,或者其内容在内部不一致。通常情况下,损坏由硬件故障或操作系统问题引起的。Oracle 数据库将损坏的标识为“逻辑损坏”或“介质损坏”。...
  • Oracle 数据损坏与恢复详解

    千次阅读 2014-10-15 13:58:57
    1.什么是块损坏: 所谓损坏的数据没有采用可识别的 Oracle 格式,或者其内容在内部不一致。通常情况下,损坏由硬件故障或操作系统问题引起的。Oracle 数据库将损坏的标识为“逻辑损坏”或“介质损坏”...

    1.什么是块损坏:

    所谓损坏的数据块,是指块没有采用可识别的 Oracle 格式,或者其内容在内部不一致。通常情况下,损坏是由硬件故障或操作系统问题引起的。Oracle 数据库将损坏的块标识为“逻辑损坏”或“介质损坏”。如果是逻辑损坏,则是 Oracle 内部错误。Oracle 数据库检测到不一致之后,就将逻辑损坏的块标记为损坏。如果是介质损坏,则是块格式不正确;从磁盘读取的块不包含有意义的信息。 

    通过恢复块,或者删除包含损坏块的数据库对象(或同时采用这两种方式),可以修复介质损坏的块。如果介质损坏是由硬件故障引起的,则只有修复了硬件故障后,才能彻底解决问题。

    只要对块执行读或写操作,就会执行下列一致性检查:
    --块版本

    --高速缓存中的 DBA(数据块地址)值与块缓冲区中的 DBA 值比较的结果

    --块校验和(如果启用)

    损坏的块被标识为以下类别:

    --介质损坏

    --逻辑(或软件)损坏

    2.块损坏故障现象:ORA-01578

    ORA-01578 错误:“ORACLE data block corrupted (file # %s, block # %s)”:

    --发现损坏的数据块时生成此信息

    --始终返回相对文件号和块号

    --返回到发出查询的会话(该查询在发现损坏时执行)

    --显示在 alert.log 文件中


    一般情况下,ORA-01578 错误是由硬件问题引起的。如果 ORA-01578 错误始终返回相同的参数,则最可能的原因是块介质损坏。
    如果返回的参数每次都有变化,则可能存在硬件问题。应检查内存和页面空间,并检查 I/O 子系统,以便查找有问题的控制器。
    注:ORA-01578 会返回相对文件号,但随之出现的 ORA-01110 错误会显示绝对文件号。

    3.如何处理损坏

    --检查预警日志和操作系统日志文件。

    --使用可用的诊断工具,找出损坏的类型。

    --多次运行检查功能,确定错误是否持续存在。

    --根据需要,从损坏的对象中恢复数据。

    --解决硬件问题:
    内存条、
    磁盘控制器、
    磁盘

    --根据需要,从损坏的对象中恢复或还原数据。

    始终尝试确定错误是否持续出现。多次运行 ANALYZE 命令;如果可能,可执行关闭再启动操作,然后再次尝试早先发生故障的操作。查找是否有其它损坏。如果发现一个损坏的块,则可能还存在其它损坏的块。


    硬件故障必须立即解决。遇到硬件问题时,应与供应商取得联系,在检查并修复了计算机后再继续工作。此时应运行一次全面的硬件诊断会话。


    硬件故障的类型可能会有很多种:
    --I/O 硬件或固件故障

    --操作系统
    --I/O 或高速缓存问题

    --内存或分页问题

    --磁盘修复实用程序


    4.实时验证块完整性:DB_BLOCK_CHECKING :

    可通过将 DB_BLOCK_CHECKING 初始化参数设置为 TRUE 启用数据库块检查。只要修改了数据块或索引块,此项检查就会检查数据块和索引块的内部一致性。DB_BLOCK_CHECKING 是一个动态参数,可使用 ALTER SYSTEM SET 语句修改此参数。对于系统表空间,将始终启用块检查。块检查通常会产生 1% 到 10% 的开销,具体取决于工作量。正在执行的更新或插入操作越多,执行块检查的开销就会越高。DB_BLOCK_CHECKING 有以下四个可能的值:

    --OFF:除 SYSTEM 之外的所有表空间都不执行块检查。

    --LOW:在内存中块的内容发生更改之后(例如,在执行 UPDATE 或 INSERT 语句以及在执行磁盘上读取后),执行基本的块头检查。

    --MEDIUM:执行所有 LOW 检查, 对所有不是按索引组织的表块执行语义块检查。

    --FULL:执行所有 LOW 和 MEDIUM 检查, 对索引块执行语义检查。

    初始化参数 DB_BLOCK_CHECKING:

    --在对每个块执行自我一致性检查时,控制检查的处理程度

    --可防止内存和数据损坏

    --可使用 ALTER SESSION 命令或 ALTER SYSTEM DEFERRED 命令进行设置

    5.块介质恢复

    大多数情况下,第一次遇到损坏时,数据库会将块标记为介质损坏,然后将其写到磁盘上。在该块得到恢复之前,不能对其执行任何后续读取操作。只能对标记为损坏或者未通过损坏检查的块执行块恢复。可使用 RMAN RECOVER...BLOCK 命令执行块介质恢复。默认情况下,RMAN 会在闪回日志中搜索好的块副本,然后在完全备份或 0 级增量备份中搜索块。如果 RMAN 找到了好的副本,则会还原这些副本,并对块执行介质恢复。块介质恢复只能将重做日志用于介质恢复,不能使用增量备份。

    V$DATABASE_BLOCK_CORRUPTION 视图显示由数据库组件(如 RMAN 命令、ANALYZE、dbv、SQL 查询等)标记为损坏的块。对于以下类型的损坏此视图会增加相应的行:

    --物理/介质损坏:数据库无法识别块:校验和无效、块内容全部为零或者块头不完整。默认情况下,物理损坏检查处于启用状态。

    --逻辑损坏:块的校验和有效,块头和块尾也匹配,但是内容不一致。块介质恢复不能修复逻辑块损坏。默认情况下,逻辑损坏检查处于禁用状态。通过指定 BACKUP、RESTORE、RECOVER 和 VALIDATE 命令的 CHECK LOGICAL 选项,可以启用逻辑损坏检查。


    块介质恢复:
    --降低平均恢复时间 (MTTR)

    --提高介质恢复期间的可用性

        --恢复期间数据文件保持联机状态

        --只有正在恢复的块是不可访问的

    --使用 RMAN RECOVER...BLOCK 命令调用

        --使用闪回日志以及完全备份或 0 级备份还原块

        --使用重做日志执行介质恢复

    --V$DATABASE_BLOCK_CORRUPTION 视图显示标记为损坏的块

    6.块介质恢复的先决条件

    --目标数据库必须处于 ARCHIVELOG 模式

    --包含损坏块的数据文件的备份必须是完全备份或 0 级备份。

        --要使用代理副本,必须先将它们还原到非默认位置
    --RMAN 只能使用归档的重做日志进行恢复

    --要使用闪回日志,必须启用闪回数据库


    以下先决条件适用于 RECOVER ... BLOCK 命令:


    --目标数据库必须以 ARCHIVELOG 模式运行,并且必须是打开的,或是使用当前控制文件装载的。

    --包含损坏块的数据文件备份必须是完全备份或 0 级备份,不能是代理副本。如果只存在代理副本备份,则可将它们还原到磁盘上的非默认位置;在这种情况下,RMAN 会认为它们是数据文件副本,在块介质恢复过程中会在其中搜索块。


    --RMAN 只能使用归档的重做日志进行恢复。RMAN 不能使用 1 级增量备份。块介质恢复不能恢复丢失或无法访问的归档重做日志,但有时可以恢复丢失的重做记录。 


    --必须在目标数据库上启用闪回数据库,这样 RMAN 才能在闪回日志中搜索损坏块的好副本。如果启用了闪回事件记录,并且此事件记录包含损坏块的较旧但未损坏的版本,则 RMAN 可以使用这些块,因而可能会提高恢复的速度。

    7.RECOVER...BLOCK 命令


    --确定包含要进行恢复的块的备份

    --读取备份并将请求的块累积到内存缓冲区

    --必要时,通过从备份中读取归档日志来管理块介质恢复会话

    RECOVER DATAFILE 6 BLOCK 3;  Recover a single block
     


    RECOVER                   Recover multiple blocks

    DATAFILE 2 BLOCK 43   in multiple data files

    DATAFILE 2 BLOCK 79

    DATAFILE 6 BLOCK 183;
     


    RECOVER CORRUPTION LIST;         Recover all blocks logged in                     V$DATABASE_BLOCK_CORRUPTION


    恢复单个块:

    在进行块恢复之前,必须确定损坏的块。一般情况下,会在以下位置中报告块损坏:

    --LIST FAILURE、VALIDATE 或 BACKUP ...VALIDATE 命令的结果
    --V$DATABASE_BLOCK_CORRUPTION 视图

    --标准输出中的错误消息

    --预警日志文件和用户跟踪文件(在 V$DIAG_INFO 视图中标识)
    --SQL ANALYZE TABLE 和 ANALYZE INDEX 命令的结果
    --DBVERIFY 实用程序的结果


    例如,可能会在用户跟踪文件中发现以下消息:

    ORA-01578: ORACLE data block corrupted (file # 7, block # 3)

    ORA-01110: data file 7: '/oracle/oradata/orcl/tools01.dbf'

    ORA-01578: ORACLE data block corrupted (file # 2, block # 235)

    ORA-01110: data file 2: '/oracle/oradata/orcl/undotbs01.dbf'

    --确定了块以后,在 RMAN 提示符下运行 RECOVER ...BLOCK 命令,指定损坏块的文件号和块编号。

    RECOVER

    DATAFILE 7 BLOCK 3

    DATAFILE 2 BLOCK 235;

     

     

    8.使用10231事件进行处理:
    (块损坏但没有备份,没办法回复的情况下)

    在sqlplus中执行如下命令:
    ALTER SYSTEM SET EVENTS='10231 trace name context forever,level 10';
    然后导出该表:
    exp test/test file=t.dmp tables=t;
    在数据库中删除该表
    drop table t;
    然后导入
    imp test/test file=t.dmp tables=t;
    最后关闭10231事件:
    ALTER SYSTEM SET EVENTS='10231 trace name context off';

     

     

     

     

     

     

    展开全文
  • volatile关键字到底有什么作用

    千次阅读 多人点赞 2020-03-27 09:42:19
    Java 语言包含两种内在的同步机制:同步(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同...

    提示:更多优秀博文请移步博主的GitHub仓库:GitHub学习笔记Gitee学习笔记

    volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

    1. 并发编程的三个基本概念

    1.1 原子性

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

    原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。

    Java中的原子性操作包括

    • 基本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间的相互赋值不是原子性操作。
    • 所有引用reference的赋值操作
    • java.concurrent.Atomic.* 包中所有类的一切操作

    1.2 可见性

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

    在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

    1.3 有序性

    定义:即程序执行的顺序按照代码的先后顺序执行。

    Java内存模型中的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

    在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

    2. Java内存模型以及共享变量的可见性

    JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory,有些文章将其称为工作内存),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

    在这里插入图片描述
    对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,比较合理的方式其实就是volatile。

    需要注意的是,JMM是个抽象的内存模型,所以所谓的本地内存,主内存都是抽象概念,并不一定就真实的对应cpu缓存和物理内存

    3. 锁的互斥与可见性

    锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。

    1. 互斥即一次只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据。
    2. 可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。也即当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的。如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

    4. volatile变量的特性

    4.1 保证可见性,不保证原子性

    • 可见性:当某个线程修改volatile变量时,JMM会强制将这个修改更新到主内存中,并且让其他线程工作内存中存储的副本失效。
    • volatile变量并不能保证其操作的原子性,具体来说像i++这种操作并不是原子操作,使用volatile修饰变量后仍然不能保证这一点。具体体现我们看下面的代码:
    public class Test {
        private static volatile int count = 0;
    
        public static void main(String[] args){
            Thread[] threads = new Thread[5];
            for(int i = 0; i<5; i++){
                threads[i] = new Thread(()->{
                    try{
                        for(int j = 0; j<10; j++){
                            System.out.println(++count);
                            Thread.sleep(500);
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                });
                threads[i].start();
            }
        }
    }
    

    生成5条线程,每条线程都对count执行10次自增操作,我们预期结果是1-50均匀的打印出来,但不管运行多少次,都无法得到期望的结果:
    在这里插入图片描述
    这说明仅仅保证数据的可见性并不能保证线程安全,具体原因我们来分析一下:

    首先我们需要明确的是,count++操作并不是原子操作,因为自增操作包括三个基本指令:读取数据、计算数据、返回结果,可以看看i++相关的字节码:

    Code:
           0: getstatic	//读取原数据
           3: iconst_1	//定义常量1
           4: iadd	//计算数据
           5: putstatic  //输出结果                 // Field count:I
           8: return
    

    现象一:数据没有按顺序输出

    假如线程A获取执行权,并在“返回结果”后停止(未打印),而转为线程B执行操作,巧合的是线程B这三步操作在一个时间片中完成:读取数据、计算数据、返回结果、打印数据,然后时间片转回线程A,线程打印刚刚计算的数据,此时就会发生先打印的数据比后打印的数据大的问题。

    现象二:数据输出重复

    假如线程A获取执行权,并在“读取数据”后停止;线程B开始执行,由于线程A还没有进行数据计算和数据返回操作,也就是说主内存中的数据并没有更新,而此时B线程完成自增操作,并且输出结果。转回线程A,由于它已经完成了数据读取工作,它将继续往下执行,但是它现在读取的数据已经是一个过期数据了,最终输出结果会和B线程输出的一样。

    所以保证数据可见性并不能保证线程安全,事实上就是保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。而锁机制刚好能保证操作的原子性和可见性。而锁机制之所以能保证原子性,是因为锁有互斥性,并且对于一个已经竞争到同步锁的线程,在还没有走出同步块的时候,即使时间片结束也不会释放锁。

    实事求是的说,笔者在此使用字节码来分析问题,仍然不够严谨,因为即使编译出来的字节码只有一条指令,也并不意味着执行是一个原子操作。一条字节码指令在解释执行时,解释器将要运行多行代码才能实现它的语义。但是在这里通过字节码就能够说明自增操作不是原子操作,所以此处用字节码进行分析。

    4.2 禁止指令重排

    指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序

    指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。需要注意的是,这里所说的不改变执行结果,指的是不改变单线程下的程序执行结果。

    重排序操作不会对存在数据依赖关系的操作进行重排序。比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运时这两个操作不会被重排序。

    重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

    然而,指令重排是一把双刃剑,重排序在单线程下一定能保证结果的正确性,但是在多线程环境下,可能发生重排序,影响结果,下例中的1和2由于不存在数据依赖关系,则有可能会被重排序,先执行status=true再执行a=2。而此时线程B会顺利到达4处,而线程A中a=2这个操作还未被执行,所以b=a+1的结果也有可能依然等于2。

    public class TestVolatile {
        int a = 1;
        boolean status = false;
    
        /**
         * 状态切换为true
         */
        public void changeStatus() {
            a = 2;
            status = true;
        }
    
        /**
         * 若状态为true,则running
         */
        public void run() {
            if (status) {
                int b = a + 1;
                System.out.println(b);
            }
        }
    }
    

    使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:

    1. 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
    2. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

    通俗的说就是执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见

    5. volatile的原理

    volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

    1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
    2. 它会强制将对缓存的修改操作立即写入主存;
    3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

    6. 单例模式的双重锁为什么要加volatile

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ocKJ6ro3-1585273171038)(…/images/6.png)]

    需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码:

    • memory = allocate() //分配内存
    • ctorInstanc(memory) //初始化对象
    • instance = memory //设置instance指向刚分配的地址

    上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。(synchronized并不能禁止指令重排和处理器优化)

    拓展:双锁校验的优势在于在大多数情况下,不需要进入synchronized的代码块中,大大加快了程序的执行效率,而如果在第一次执行getInstance()方法时,也能保证对象创建的安全性。

    参考文章

    • https://blog.csdn.net/u012723673/article/details/80682208

    • https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653192450&idx=2&sn=ad95717051c0c4af83923b736a5bc637&chksm=8c99f3d8bbee7aceb123e4f6aa9a220630b5aa17743ba812d82308bfb6a8ed8303bdd181f144&mpshare=1&scene=23&srcid=0929Elncx0R0l02WWFrc9Cpt&sharer_sharetime=1569734533458&sharer_shareid=e81601a95b901aeca142bbe3b957819a#rd

    展开全文
  • 1. 什么是volatile?   有关volatile的介绍网上有很多介绍,题主总结能力有限就引用一下百度上的描述: volatileJava提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步(或方法)和 ...

    关于volatile提供的可见性无法保证并发编程的思考

    题主最近在准备面试,正好复习到了多线程编程的知识点。其中有关validate于并发上的使用引起了我的思考。

    一、介绍volatile

    1. 什么是volatile?

      有关volatile的介绍网上有很多介绍,题主总结能力有限就引用一下百度上的描述:

    volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

    2. 可见性

      在volatile的描述中,最常见的一个描述是volatile提供了可见性,可以使不同的线程立刻获取到变量的变化。至于这点描述主要和JMM(java内存模型)有关,此处博主不在展开描述。

    二、原子性

    1. 什么原子性

      原子性指的是一条或是一串命令,要么不执行要么全部执行。

    2. 通过a++窥见原子性

      这里举一个常见的例子:a++。在代码中a++是一个单独的代码,但是在实际运行的时候大致分为四个步骤。 当然我们假设a的值为1。
      1. 从a变量中读出实际的值1;
      2. 从常量池中出一个值1;
      3. 将这两个值进行相加算出值2;
      4. 再将结果赋值到a上a=2;
      经过这四步后,变量a的值才会变成2。这四步每一步都是原子操作,但是a++本身不是原子操作。这就说明两个线程在同时执行a++的时候会出现交替执行现象
      说一个极端情况,两个线程AB同时运行a++,运行的过程是交替的,即:A.1,B.1,A.2,B.2……我们会发现,最后a的值应该是2,但实际上我们执行了两次a++。

    三、可见性无法维持并发安全的原因。

    1.结论

      通过对原子性和可见性的描述后,某些同学可能已经猜出来为什么可见性无法保证并发安全了

      可见性可以保证当前获取变量的值为最新状态,但是无法保证后续操作的过程中此值再次改变后能够自动维护成新的值。

    2.解释

      我们还是按照a++的例子去说。当我对a变量加上volatile修饰后,可以保证a++操作第一步每次获得值是最新值。但是之后++操作是对读到的值进行操作,与a没有关系了。因此最后赋值操作过程中已经读到的值很可能是过时的

    四、结论

    1. volatile不能保证安全并发的原因

      分析问题一定要抓住问题的关键。分析volatile安全并发的问题其关键不在于变量是否具备可见性,而在于其无法保证操作的原子性,导致一条命令执行中会自动执行其他命令,导致读取到的值变成过时的

    2. volatile怎么使用

      经过上述描述,我们会发现其实volatile无法保证并发是因为无法保证后续操作并不是原子操作,因此配合java提供的锁进行使用。那就没有什么问题了。


    后记

    博主写完之后发现已经有大佬写出了一篇描述和解释更好的文章,大家可以直接去看大佬的文章。但依然欢迎大家讨论指正~~
    Java volatile关键字最全总结:原理剖析与实例讲解(简单易懂)

    展开全文
  • 那么为什么引起TCP粘包呢原因有以下两点 1、TCP基于字节流的,虽然应用层和传输层之间的数据交互大小不等的数据,但是TCP把这些数据仅仅看成一连串无结构的字节流,没有边界; 2、在TCP的首部没有表示数据...

    最近在工作中遇到了要自己写Socket服务器和客户端的问题,我解决了关于TCP的粘包问题。那么为什么会引起TCP粘包呢原因有以下两点

    1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;

    2、在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能

    假如没有做处理的话,发过来的数据包会有三种情况
    1.没有粘包(这个问题不讨论,是正常情况)
    2.两个或多个整包粘在一起
    3.一个或多个包+下个包的部分数据

    我的处理方式是发送包的时候在包的头上加上包头(包头上可以加一些自定义字符,如加密信息等,这次我们讲的是粘包问题所以不加那些东西),包头上标记了我这次发送的数据有多长。这样会有四种情况会出现
    1.没有粘包(这个问题不讨论,是正常情况)
    2.两个或多个整包粘在一起
    3.一个或多个包+下个包的部分或者整个包头
    4.一个或者多个包+下个包的包头+数据发送过来

    下面是解决粘包的代码,我把要讲的都加载注释里面了

       using System;
       
       public class SocketBuffer
       {
           //定义消息头
           private byte[] headBytes;
           //包头长度
           private byte headLength = 4;
       	   //缓存的包数据(不全的包会缓存在这个里面,等待下次发送)
           private byte[] allReceiveData;
       
           //当前接受到数据的长度
           private int currentReceiveLength;
           //总的数据长度
           private int allDataLength;
       
           public SocketBuffer(byte headLength,CallBackReciveOver OvercallBack)
           {
               this.headLength = headLength;
               headBytes = new byte[headLength];
               this.callBackReceiveOver = OvercallBack;
           }
           /// <summary>
           /// 解包(改函数是递归函数,自己内部在调用)
           /// </summary>
           /// <param name="receive">收到的数据</param>
           /// <param name="realLength">当前包的真实长度</param>
           public void ReceiveByte(byte[] receive,int realLength)
           {
               if (realLength == 0)
                   return;
               //当前接收的数据小于头的长度(将所有数据存入缓存中)
               if(currentReceiveLength<headBytes.Length)
               {
                   ReciveHead(receive, realLength);
               }
               else
               {
                   //接收的总长度
                   int tempLength = currentReceiveLength+ realLength;
                   if(tempLength==allDataLength)
                   {
                       //刚好相等(发送过来的是整个包,不用解包)
                       ReceiveOneAll(receive,realLength);
                   }
                   else if(tempLength>allDataLength)    //接收的数据比这个消息长(多个包粘连>1个包长度)
                   {
                       ReceiveLarger(receive,realLength);
                   }
                   else 												//接收的数据比当前整包要短(存入缓存中)
                   {
                       ReceiveSmaller(receive, realLength);
                   }
               }
           }
           private void ReceiveLarger(byte[] receiveByte,int realLength)
           {
               int tempLength = allDataLength - currentReceiveLength;
       
               Buffer.BlockCopy(receiveByte, 0, allReceiveData, currentReceiveLength,tempLength);
       
               currentReceiveLength += tempLength;
       
               ReceiveOneMessageOver();
       
               int remainLength = realLength - tempLength;
       
               byte[] remainByte = new byte[remainLength];
       
               Buffer.BlockCopy(receiveByte, tempLength,remainByte, 0, remainLength);
       
               //看成从Socket里取出来放入处理
               ReceiveByte(remainByte,remainLength);
           }
           private void ReceiveSmaller(byte[] receiveByte, int realLength)
           {
               Buffer.BlockCopy(receiveByte, 0,allReceiveData, currentReceiveLength,realLength);
               currentReceiveLength += realLength;
           }
       
       
           private void ReceiveOneAll(byte[] receiveByte, int realLength)
           {
               Buffer.BlockCopy(receiveByte, 0, allReceiveData, currentReceiveLength, realLength);
               currentReceiveLength += realLength;
               ReceiveOneMessageOver();
           }
           private void ReciveHead(byte[] receiveByte, int realLength)
           {
               //差多少个字节才能组成一个头
               int tempReal = headBytes.Length - currentReceiveLength;
       
               //现在接收的和已经接收的总长度是多少
               int tempLength = currentReceiveLength + realLength;
               //总长度还小于头
               if(tempLength<headBytes.Length)
               {
                   Buffer.BlockCopy(receiveByte, 0, headBytes, currentReceiveLength,realLength);
                   currentReceiveLength += realLength;
               }
               else //大于等于头
               {
                   Buffer.BlockCopy(receiveByte, 0,headBytes, currentReceiveLength,tempReal);
                   currentReceiveLength += tempReal;
                   //头部已经凑齐
       
                   //取出四个字节转换成int
                   byte[] datalength = new byte[] {headBytes[0], headBytes[1],headBytes[2], headBytes[3]};
                   int bodyLength =int.Parse(Encoding.Default.GetString(datalength));
                   allDataLength = bodyLength +headLength;
                   allReceiveData = new byte[allDataLength];
                   if(datalength!=null)
                   {
                       datalength = null;
                   }
                   //已经包含了头部了
                   Buffer.BlockCopy(headBytes, 0,allReceiveData, 0, headLength);
       
                   int tempRemin = realLength - tempReal;
                   //表示receiveByte是否还有数据
                   if(tempRemin>0)
                   {
                       byte[] tempByte = new byte[tempRemin];
                       //表示将剩下的字节送入tempByte里去   
                       Buffer.BlockCopy(receiveByte,tempReal,tempByte , 0,  tempRemin);
                       ReceiveByte(tempByte,tempRemin);
                   }
                   else
                   {
                       //只有消息头的情况
                       ReceiveOneMessageOver();
                   }
               } 
           }
       
           #region ReceiveOverCallback
           public delegate void CallBackReciveOver(byte[] allData);
           CallBackReciveOver callBackReceiveOver;
           //回调给上层处理好的数据(上层需要把包头文件再进行处理)
           private void ReceiveOneMessageOver()
           {
               if (callBackReceiveOver != null)
               {
                   callBackReceiveOver(allReceiveData);
               }
               currentReceiveLength = 0;
               allDataLength = 0;
               allReceiveData = null;
           }
           #endregion
       
       }   
    
    下面是制作数据包的代码,请参考。
    
      public byte[] MakePackage(byte[] data)
      {
               int length = data.Length;
               byte[] bytes = BitConverter.GetBytes(length);
               
               byte[] package= new byte[data.Length + bytes.Length];
    
               Array.Copy(bytes,0,package,0,bytes.Length);
    
               Array.Copy(data,0, package, bytes.Length, data.Length);
    
               return package;
      }
    
    展开全文
  • 1,晚上吃饭后给钱时,应该给的钱4,... 当然服务员也没说什么,我觉得这有不足容易引起误会的地方,需要改进.   2,昨晚一聊天时,另外一个不怎么熟的人说给我们名片,为了认识.他给我名片时我很注意地站起来用...
  • 什么是“共享”?多CPU同时访问同一内存区域就是“共享”,就会产生冲突,需要控制协议来协调访问。会引起“共享”的最小内存区域大小就是一个cache line。因此,当两个以上CPU都要访问同一个cache line大小的...
  • 什么是var_dump var_dump一个进行中的chrome扩展,它将从DOM中解析出var_dump并使其漂亮! 此外,仅供参考,我们现在已经在chrome商店中了: 。 这交易 尝试阅读var_dumps就像尝试阅读多行文本而没有标点符号...
  • 跑的多快,跳的多高,一拳能够打出什么反应,这都能引起视觉反应,简单的说,就是攻击究竟有没有力道感,就看你怎么打,用什么姿势打,然后被打的人是什么反映,由于被攻击部位的不同,所作的反应自然要不同,如果都用拳皇那...
  • 疯狂的程序员

    热门讨论 2012-07-18 18:05:32
    程序员是什么?他不知道。他问:“程序员能找到教书的工作吗?” “当然,一点问题都没有。随便哪个学校都能教。想我那个年代,这城市有多少程序员,数都能数出来。我还报了高程,唉……差一点。” 能去教书当然好,...
  • 27、GC是什么? 为什么要有GC?  GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象...
  • 6.2.5 约束引起的错误 242 6.2.6 DML触发器 244 6.2.7 处理来自触发器和约束的错误 277 6.3 手动数据保护 280 6.4 更多最佳实践 287 6.5 总结 287 第7章 模式与查询技术 289 7.1 预计算值 290 7.1.1 序列表 ...
  • 6.2.5 约束引起的错误 242 6.2.6 DML触发器 244 6.2.7 处理来自触发器和约束的错误 277 6.3 手动数据保护 280 6.4 更多最佳实践 287 6.5 总结 287 第7章 模式与查询技术 289 7.1 预计算值 290 7.1.1 序列表 ...
  • 关于依赖的疑问

    2020-11-23 05:52:27
    <div><p>hi 又来打扰了: 我有一个 util 模块,模块的目录结构如下 <pre><code> util/ package.json ...这是什么原因引起的?</p><p>该提问来源于开源项目:spmjs/spm</p></div>
  • 木马防护软件引起的DB Link故障 74 坏与数据恢复 76 IP地址冲突引发的ORA-12541错误 82 ORA-8103错误的处理 83 半瓶的DBA工作手记(作者:邹德平) 87 SQL执行计划改变导致查询变慢 88 大数据量操作导致...
  •  在这一章里,将介绍Oracle数据库中锁的起因及由锁引起的性能问题—阻塞,并将讨论常见的几种阻塞的起因。  第3章 Latch和等待  这一章讨论Latch,它Oracle中比锁更轻量级的一种串行机制。热或是SQL未绑定...
  • 深入理解css之BFC

    2018-09-30 15:11:33
    当BFC起作用的时候,其元素内部无论怎么翻江倒海都不会影响到外部元素,同样,外部元素的变化也不会影响到BFC内部元素,这就跟结界一样,你可以理解为有一个很强的护盾在包裹着BFC元素,这也什么BFC元素不会发生...
  • 1、什么是volatile volatileJava提供的一种轻量级的同步机制。Java语言包含两种同步机制:1、同步(或同步方法) 2、volatile变量。相比于synchronized加锁同步,volatile关键字比较轻量级,开销更低,因为他不会...
  • 还是由新的编程风格引起的细微错误? 有没有学到的教训? 有没有最佳做法? 如果您有兴趣了解转移到Java编程的功能样式时遇到的挑战,应避免使用哪些代码构造以及使用的最佳实践,那么本课程非常适合您。 关于我们 ...
  • 号,以防电源开关或电源插头分-合过程中引起的抖动而影响复位。 为了方便我们选择RC 复位电路可以实现上述基本功能如图3.2 所示。 但是该电路解决不了电源毛刺(A 点)和电源缓慢下降(电池电压不足)等 问题而且...
  • 不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露,...

空空如也

空空如也

1 2 3 4 5
收藏数 88
精华内容 35
关键字:

包块是什么引起的