`

java同步异步基础【转】

 
阅读更多

-脏读:脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的

 

-java 同步和异步 很经典

举个例子:普通B/S模式(同步)AJAX技术(异步) 

         同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 

         异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

 

-同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。 

-异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。 

所以,要我请你吃饭就用同步的方法,要请我吃饭就用异步的方法,这样你可以省钱。

 

-同步:发送一个请求,等待返回,然后再发送下一个请求 

-异步:发送一个请求,不等待返回,随时可以再发送下一个请求 

-并发:同时发送多个请求

 

Java同步、异步相关知识点

一、关键字:

 

thread(线程)、thread-safe(线程安全)、intercurrent(并发的)

synchronized(同步的)、asynchronized(异步的)、

volatile(易变的)、atomic(原子的)、share(共享)

 

1、  什么时候必须同步?什么叫同步?如何同步?

 

    要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。

为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的。这归因于java语言规范的内存模型,它规定了:一个线程所做的变化何时以及如何变成对其它线程可见。

因为多线程将异步行为引进程序,所以在需要同步时,必须有一种方法强制进行。例如:如果2个线程想要通信并且要共享一个复杂的数据结构,如链表,此时需要确保它们互不冲突,也就是必须阻止B线程在A线程读数据的过程中向链表里面写数据(A获得了锁,B必须等A释放了该锁)。为了达到这个目的,java在一个旧的的进程同步模型——监控器(Monitor)的基础上实现了一个巧妙的方案:监控器是一个控制机制,可以认为是一个很小的、只能容纳一个线程的盒子,一旦一个线程进入监控器,其它的线程必须等待,直到那个线程退出监控为止。通过这种方式,一个监控器可以保证共享资源在同一时刻只可被一个线程使用。这种方式称之为同步。(一旦一个线程进入一个实例的任何同步方法,别的线程将不能进入该同一实例的其它同步方法,但是该实例的非同步方法仍然能够被调用)。

    错误的理解:同步嘛,就是几个线程可以同时进行访问。

    -同步和多线程关系:没多线程环境就不需要同步;有多线程环境也不一定需要同步。

    -同步和互斥的关系:相交进程之间的关系主要有两种,同步与互斥。所谓互斥,是指散步在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它 们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的 某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。

  显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

  也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!

 

    -互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

  -同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

 

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

    -互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。

    -可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题(说得不雅一点,就是:一个人上了厕所,另外一个人接着进去,要能闻到臭味。)

 

    小结:为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致(就是所谓的:线程安全。如java集合框架中 Hashtable和Vector是线程安全的。我们的大部分程序都不是线程安全的,因为没有进行同步,而且我们没有必要,因为大部分情况根本没有多线程环境)。

 

2、  什么叫原子的(原子操作)?

 

     Java原子操作是指:不会被打断地的操作。(就是做到互斥 和可见性?!)

     那难道原子操作就可以真的达到线程安全同步效果了吗?实际上有一些原子操作不一定是线程安全的。那么,原子操作在什么情况下不是线程安全的呢?也许是这个原因导致的:java线程允许线程在自己的内存区保存变量的副本。允许线程使用本地的私有拷贝进行工作而非每次都使用主存的值是为了提高性能(本人愚见:虽然原子操作是线程安全的,可各线程在得到变量(读操作)后,就是各自玩弄自己的副本了,更新操作(写操作)因未写入主存中,导致其它线程不可见)。

 

     那该如何解决呢?因此需要通过java同步机制。

 

     在java中,32位或者更少位数的赋值是原子的。在一个32位的硬件平台上,除了double和long型的其它原始类型通常都是使用32位进行表示,而double和long通常使用64位表示。另外,对象引用使用本机指针实现,通常也是32位的。对这些32位的类型的操作是原子的。

 

     这些原始类型通常使用32位或者64位表示,这又引入了另一个小小的神话:原始类型的大小是由语言保证的。这是不对的。java语言保证的是原始类型的表数范围而非JVM中的存储大小。因此,int型总是有相同的表数范围。在一个JVM上可能使用32位实现,而在另一个JVM上可能是64位的。在此再次强调:在所有平台上被保证的是表数范围,32位以及更小的值的操作是原子的。

 

3、  不要搞混了:同步、异步

 

     举个例子:普通B/S模式(同步)AJAX技术(异步)

 

     同步:提交请求->等待服务器处理->处理完返回 这个期间客户端浏览器不能干任何事

     异步:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

 

     注:彼“同步”非此“同步”——我们说的java中的那个共享数据同步(synchronized)一个同步的对象是指行为(动作),一个是同步的对象是指物质(共享数据)。

 

4、  Java同步机制有4种实现方式:(部分引用网上资源)

 

①    ThreadLocal ② synchronized( ) ③ wait() 与 notify() ④ volatile

 

     目的:都是为了解决多线程中的对同一变量的访问冲突

 

ThreadLocal

    ThreadLocal 保证不同线程拥有不同实例,相同线程一定拥有相同的实例,即为每一个使用该变量的线程提供一个该变量值的副本,每一个线程都可以独立改变自己的副本,而不是与其它线程的副本冲突。

 

    优势:提供了线程安全的共享对象

    与其它同步机制的区别:同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信;而 ThreadLocal 是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要多个线程进行同步了。

 

volatile

     volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。

 

     优势:这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

     缘由:Java 语言规范中指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而 volatile 关键字就是提示 VM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

     

     使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在 synchronized 代码块中,或者为常量时,不必使用。

     

     线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步,因此存在A和B不一致的情况。volatile就是用来避免这种情况的。 volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到)

 

   volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。

 

   您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

(1)对变量的写操作不依赖于当前值;

(2)该变量没有包含在具有其他变量的不变式中。

 

Synchronized既保证了多线程的并发有序性(即同步),又保证了多线程的内存可见性。

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

比较volatile & synchronized ==> 

关于volatile & synchronized的比较另见博文:http://blog.csdn.net/ghsau/article/details/7424694

 

  恐怕比较一下volatile和synchronized的不同是最容易解释清楚的。volatile是变量修饰符,而synchronized则作用于一段代码或方法;看如下三句get代码:

int i1;int geti1() {return i1;}

volatile int i2;int geti2() {return i2;}

int i3; synchronizedint geti3() {return i3;}

 

  geti1()得到存储在当前线程中i1的数值。多个线程有多个i1变量拷贝,而且这些i1之间可以互不相同。换句话说,另一个线程可能已经改变了它线程内的i1值,而这个值可以和当前线程中的i1值不相同。事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2,线程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,而且这个改变还没来得及传递给“主”内存区域或其他线程时就会发生。

  而geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝。换句话说,一个变量经volatile修饰后在所有线程中必须是同步的;任何线程中改变了它的值,所有其他线程立即获取到了相同的值。理所当然的,volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。

  既然volatile关键字已经实现了线程间数据同步,又要synchronized干什么呢?呵呵,它们之间有两点不同。首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。但是,synchronized也同步内存:事实上,synchronized在“主”内存区域同步整个线程的内存。因此,执行geti3()方法做了如下几步:

1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)

2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗])

3. 代码块被执行

4. 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)

5. 线程释放监视this对象的对象锁

  因此volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

sleep() vs wait()

  sleep()线程类(Thread)的静态方法,导致此线程睡眠执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时候会自动恢复。调用sleep不会释放对象锁

  yield()线程类(Thread)的静态方法,不能保障太多事情,他只会让处于运行状态的当前线程回到可运行状态,使得同优先级的线程有机会执行。但是无法保证yield()达到目的,因为让步的线程还有可能被调度程序再次选中

  wait()Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态

(如果变量被声明为volatile,在每次访问时都会和主存一致;如果变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁时变量被同步。)

 

例子:Demo1

package test.thread;

 

class SynTest{

    //非同步

    static void method(Thread thread){

        System.out.println("begin "+thread.getName());

        try{

Thread.sleep(2000);

        }catch(Exception ex){

ex.printStackTrace();

        }

        System.out.println("end "+thread.getName());

    }

 

    //同步方式一:同步方法(这个方法是同步的方法,每次只有一个线程可以进来)

    synchronized static void method1(Thread thread){

       System.out.println("begin "+thread.getName());

        try{

Thread.sleep(2000);

        }catch(Exception ex){

ex.printStackTrace();

        }

System.out.println("end "+thread.getName());

       }

 

    //同步方式二:同步代码块

    static void method2(Thread thread){

synchronized(SynTest.class) {

System.out.println("begin "+thread.getName());

try{

Thread.sleep(2000);

}catch(Exception ex){

ex.printStackTrace();

}

System.out.println("end "+thread.getName());

        }

    }

 

    //同步方式三:使用同步对象锁

    private static Object _lock1=new Object();

    private static byte _lock2[]={};//据说,此锁更可提高性能。源于:锁的对象越小越好

 

    static void method3(Thread thread){

        synchronized(_lock1) {

System.out.println("begin "+thread.getName());

try{

Thread.sleep(2000);

}catch(Exception ex){

ex.printStackTrace();

}

System.out.println("end "+thread.getName());

        }

    }

 

    public static void main(String[] args){

       //启动3个线程,这里用了匿名类

      for(int i=0;i<3;i++){

            new Thread(){

                public void run(){

                  method(this);

                  //method1(this);

                  //method2(this);

                  //method3(this);

                }

            }.start();

        }

    }

}

 

 

 

 

 

/**

 

    执行method()方法结果:

       begin Thread-0

       begin Thread-2

       begin Thread-1

       end Thread-1

       end Thread-0

       end Thread-2

 

    说明了:在没有同步限制的条件下,多个线程可以同时进入一个对象的方法中操作。

    这样对共享可变数据是不安全的,即常说的:非线程安全(non thread-safe)。

*/

 

/**

    执行method1()/method2()/method3()方法结果(可能线程进入的顺序不同):

       begin Thread-0

       end Thread-0

       begin Thread-1

       end Thread-1

       begin Thread-2

       end Thread-2

    说明了:在同步限制的条件下,同时只可有一个线程进入一个对象的方法中操作,其它线程必须等待先它的线程退出后才可进入。

    这样可保证共享可变数据是安全的,即常说的:线程安全(thread-safe )。

*/

 

Demo2:

package test.thread;

import com.util.LogUtil;

 

public class SynTest2 {

    public static void main(String[] args){

       Callme target=new Callme();

       Caller ob1=new Caller(target,"Hello");

       Caller ob2=new Caller(target,"Synchronized");

       Caller ob3=new Caller(target,"World");

    }

}

 

class Callme{

    //有和没有synchronized的时候,结果是不一样的

    synchronized void test(){

       LogUtil.log("测试是否是:一旦一个线程进入一个实例的任何同步方法,别的线程将不能进入该同一实例的其它同步方法,但是该实例的非同步方法仍然能够被调用");

    }

 

    void nonsynCall(String msg){

       LogUtil.log("["+msg);

       LogUtil.log("]");

    }

 

    synchronized void synCall(String msg){

       LogUtil.logPrint("["+msg);

       LogUtil.log("]");

    }

}

 

class Caller implements Runnable{

    String msg;

    Callme target;

    Thread t;

 

    Caller(Callme target,String msg){

       this.target=target;

       this.msg=msg;

       t=new Thread(this);

       t.start();

    }

   

    public void run() {

       //target.nonsynCall(msg);

       target.synCall(msg);

       target.test();

    }

}

 

五、XXXX:

    写程序到现在,还没有自己写过需要多线程并发访问的。看看前公司的底层代码,也没怎么发现到什么多线程的知识。也许,应用层很少用到这些东西。下个阶段准备学习学习JDK的并发包。

 

    在oracle存储过程中,我要订制跟单方案,我首先要在规则表中查询跟单人数是否小于最大人数跟单人数(如果超过最大跟单人数就不订单),然后才可订制跟单,但是由于高并发性,实际跟单人数总是要超过最大跟单人数。怎样才能解决这个问题,有什么好的方案,高手帮忙解答一下。。。。谢过。。。不知道我说得是否能听懂

问题补充:

    看下存储过程代码:Count是不允许锁的!!!!!!!!在两个表中查询锁定还有用吗???????看下看下

 SELECT COUNT(ai.autobuyid) INTO v_follnum FROM Autobuyinfo ai WHERE ai.gameid=pi_gameid AND ai.casttype=pi_casttype AND ai.superuserid=pi_superid AND ai.followtype=0;     

SELECT ar.maxfollownum INTO v_rulenum FROM Autobuyrule ar WHERE ar.gameid=pi_gameid AND ar.casttype=pi_casttype;                      

                IF v_follnum >= v_rulenum THEN 

                   po_errdesc:='订单人数已满,不可再订制跟单';

                   po_errcode:=-6;

                   raise DEFERROR;

                END IF;                   

                --添加自动跟单 

                INSERT INTO Autobuyinfo ai         (ai.autobuyid,ai.followu

最佳答案

    你这里要考虑并发与业务逻辑的一个平衡。但是事实上只能先保证业务正确,那么就需要对同一条记录(即同一个单子)进行线性操作了。

最简单的,利用oracle存储过程的事务原子性和行锁机制就可以解决。

比如:

create or replace procedure aaa( ...)

as

v_count number; 

begin

    ....

    select count(*) into v_count from tbl where id=123 for update; 

-- 利用for update锁住对应这个单号的这条记录。这样,在这个存储过程没有commit之前,别的存储过程对id号(你的单号)为123的记录的for update查询只能等待。

    ..... 处理你的业务逻辑;

    update tbl set amout=amout+1 where id=123; 这里把跟单人数加1.

   ... 其他逻辑

end;

/

 

分享到:
评论

相关推荐

    JAVA上百实例源码以及开源项目

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    JAVA上百实例源码以及开源项目源代码

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    Java整理的基础工具类项目

    Java整理的基础工具类项目 Spring+Redis实现无缝读写分离插入(com.shawntime.utils.rwdb) Redis操作封装(com.shawntime.utils.cache.redis) Redis分布式锁实现(com.shawntime.utils.lock) 读写锁控制强制读取...

    从Java走向Java+EE+.rar

    18.1.4 同步和异步方式 268 18.2 编程模型 268 18.2.1 管理对象 269 18.2.2 连接对象 270 18.2.3 会话 270 18.2.4 消息产生者 270 18.2.5 消息消费者 271 18.2.6 消息 272 18.2.7 异常处理 272 ...

    JAVA相关基础知识---面试题

    EJB是基于哪些技术实现的 同步和异步有何异同,在什么情况下分别使用他们 JSP中动态INCLUDE与静态INCLUDE的区别

    JAVA实现Modbus RTU或Modbus TCPIP数据采集.rar

    3.java同步的几种方式:synchronized,volatile,显示锁,原子变量,线程及对象的基础同步方法。 4.所谓线程安全就是当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在...

    java基础题 很全面

    Java基础 6 1. 面向对象的特征有哪些方面 6 2. String是最基本的数据类型吗? 7 3. int 和 Integer 有什么区别 7 4. String 和StringBuffer的区别 7 5. 运行时异常与一般异常有何异同? 7 6. 说出ArrayList,Vector, ...

    Java并发编程实战

    1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 性能问题 1.4 线程无处不在 第一部分 基础知识 第2章 线程安全性 2.1 什么是线程安全...

    java面试题大全(2012版)

    Java基础部分 7 1、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 7 2、Java有没有goto? 7 3、说说&和&&的区别。 8 4、在JAVA中如何跳出当前的多重嵌套循环? 8 5、switch语句能否作用在byte...

    java的入门教程

    对于刚开始接触java的同学来说是一门不错的教程,从基本的数据类型到 类的继承重载和复写都比较详细,还有java的常用类库的介绍,多线程的介绍,同步异步,还有异常的捕获讲解都叫基础详细,网络编程基础socket的...

    Java 7并发编程实战手册

    《Java 7并发编程实战手册》适合具有一定Java编程基础的读者阅读和学习。如果你是一名Java开发人员,并且想进一步掌握并发编程和多线程技术,并挖掘Java 7并发的新特性,那么本书是你的合适之选。 《Java 7并发编程...

    JavaNIO的通讯组件Gecko-Java.zip

    Gecko是一个Java NIO的通讯组件,它在一个轻量级的NIO框架的基础上提供了更高层次的封装和功能。支持的RPC调用方式包括RR(request-response)和pipeline 特性: 可插拔的协议设计 连接池 分组管理和负载均衡 ...

    leetcode题库-java-interview:Java研发基础相关

    Java-Interview 四大基本特性 重载与重写的区别 访问控制符 Object类方法 抽象类与接口 类初始化顺序 hashCode & equals == & equals this static 基本类型 & 包装类 String 泛型 内部类 集合类 ArrayList & ...

    java学习笔记包括基本语法和高级语法

    基于java的基础知识,进行归纳的文件,包括基本语法和高级语法,如:5 线程 同步异步 Lambda,7 函数式接口 stream流 反射等。以及部分案例

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

     本书是《轻量级java ee企业应用实战》的姊妹篇,《轻量级java ee企业应用实战》主要介绍以spring+hibernate为基础的java ee应用;本书则主要介绍以ejb 3+jpa为基础的java ee应用。ejb 3、jpa规范都属于sun公司所...

    Java面试宝典2020修订版V1.0.1.doc

    二、Java基础部分 13 1、java中有哪些基本类型? 13 2、java为什么能够跨平台运行? 13 3、String是基本数据类型吗?我可不可以写个类继承于String? 14 4、谈谈&和&&的区别? 14 5、Switch语句里面的条件可不可以是...

    Java面试宝典-经典

    Java基础部分 7 1、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 7 2、Java有没有goto? 7 3、说说&和&&的区别。 8 4、在JAVA中如何跳出当前的多重嵌套循环? 8 5、switch语句能否作用在byte...

Global site tag (gtag.js) - Google Analytics