精华内容
下载资源
问答
  • 浅谈Java 多线程同步
    千次阅读
    2019-02-24 20:35:34

    最近在研究多线程同步的一些问题,整理了网上很多文档,在这里给大家分享下

    Java并发机制的底层实现原理

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。

    多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。为了解决此问题,就必须使用同步的方式保证程序不会产生设计之外的错误结果。

    下面简单说下Java同步机制的4种实现方式: 目的都是为了解决多线程中的对同一变量的访问冲突

    使用wait()和notify()

    wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。

    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉

    InterruptedException(中断异常)异常。

    notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒

    某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。

    notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是

    让它们竞争。

    synchronized实现同步的方式

    (1)同步方法:

    即用synchronized关键字修饰方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。

    线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。

    (2)同步代码块

    即用synchronized关键字修饰代码块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步,而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的Java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。

    如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。

    synchronized 关键字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,请看下面的例子:

    public class ThreadTest implements Runnable{
    
    public synchronized void run(){
    
    for(int i=0;i<10;i++) {
    
    System.out.print(" " + i);
    
    }
    
    }
    
    public static void main(String[] args) {
    
    Runnable r1 = new ThreadTest();
    
    Runnable r2 = new ThreadTest();
    
    Thread t1 = new Thread(r1);
    
    Thread t2 = new Thread(r2);
    
    t1.start();
    
    t2.start();
    
    }}

    在这个程序中,run()虽然被加上了synchronized 关键字,但保护的不是共享数据。因为这个程序中的t1,t2 是两个对象(r1,r2)的线程。而不同的对象的数据是不同的,r1,r2 有各自的run()方法,所以输出结果无法预知。

    synchronized的目的是使同一个对象的多个线程,在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。

    public class ThreadTest implements Runnable{
    
    public synchronized void run(){
    
        for(int i=0;i<10;i++){
    
            System.out.print(" " + i);
    
        }
    
    }
    
    public static void main(String[] args){
    
        Runnable r = new ThreadTest();
    
        Thread t1 = new Thread(r);
    
        Thread t2 = new Thread(r);
    
        t1.start();
    
        t2.start();
    
    }}

    如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()受synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。

    示例4:

    public class ThreadTest implements Runnable{
        public void run(){
            synchronized(this){
                for(int i=0;i<10;i++){
                    System.out.print(" " + i);
                }
            }
        }
    
    public static void main(String[] args){
        Runnable r = new ThreadTest();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
    }

    这个程序与示例3 的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4 的形式,this 代表“这个对象”。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。

    示例5:

    public class ThreadTest implements Runnable{
    
        public void run(){
            for(int k=0;k<5;k++){
                System.out.println(
                    Thread.currentThread().getName()+ " : for loop : " + k);
            }
            synchronized(this){
                for(int k=0;k<5;k++) {
                    System.out.println(
                        Thread.currentThread().getName()+ " : synchronized for loop : " + k);
                }
            }
         }
    
      public static void main(String[] args){
        Runnable r = new ThreadTest();
        Thread t1 = new Thread(r,"t1_name");
        Thread t2 = new Thread(r,"t2_name");
        t1.start();
        t2.start();
      } 
    }

    运行结果:

    t1_name : for loop : 0
    t1_name : for loop : 1
    t1_name : for loop : 2
    t2_name : for loop : 0
    t1_name : for loop : 3
    t2_name : for loop : 1
    t1_name : for loop : 4
    t2_name : for loop : 2
    t1_name : synchronized for loop : 0
    t2_name : for loop : 3
    t1_name : synchronized for loop : 1
    t2_name : for loop : 4
    t1_name : synchronized for loop : 2
    t1_name : synchronized for loop : 3
    t1_name : synchronized for loop : 4
    t2_name : synchronized for loop : 0
    t2_name : synchronized for loop : 1
    t2_name : synchronized for loop : 2
    t2_name : synchronized for loop : 3
    t2_name : synchronized for loop : 4

    第一个for 循环没有受synchronized 保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1 执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for 循环(t2 刚执行到k=2)。t1 开始执行第二个for 循环,当t1 的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2 想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for 循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for 循环后,它才可以执行第二个for 循环。

    使用特殊域变量(Volatile)实现线程同步

    volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。Java内存模型对volatile专门定义了一些特殊的访问规则:

    a.volatile关键字为域变量的访问提供了一种免锁机制

    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新

    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值

    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量  volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。 优势:这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 缘由:Java 语言规范中指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而 volatile 关键字就是提示 JVM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。 使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在synchronized 代码块中,或者为常量时,不必使用。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的 是B。只在某些动作时才进行A和B的同步,因此存在A和B不一致的情况。volatile就是用来避免这种 情况的。 volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到) Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。 您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件: 对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。

    释放同步监视器的锁定

    程序无法显式释放对同步监视器的锁定,线程会在如下几种情况下释放对同步监视器的锁定:

    • 当前线程的同步方法,同步代码块执行结束,当前线程即释放同步监视器;
    • 当线程在同步方法,同步代码块中遇到break、return终止了该代码块、该方法的继续运行,当前线程将会释放同步监视器;
    • 当线程在同步方法,同步代码块中出现了未处理的Error或Exception,导致了该方法、该代码块异常结束时将会释放同步监视器;
    • 当线程在同步方法,同步代码块时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

    在下面情况下,线程不会释放同步监视器:

    • 线程执行同步方法或同步代码块时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;
    • 线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。当然,我们应该避免使用suspend和resume方法来控制线程;

    总结的有点乱,等有时间再整理下,有不当之处还请指教

     

    参考:

    https://www.cnblogs.com/mengyuxin/p/5358364.html

    http://blog.csdn.net/ff55c/article/details/6748604

    http://developer.51cto.com/art/201509/490965.htm

    http://www.itzhai.com/java-based-notebook-thread-synchronization-problem-solving-synchronization-problems-synchronized-block-synchronized-methods.html#read-more

    更多相关内容
  • java多线程同步5种方法

    万次阅读 多人点赞 2018-05-28 22:11:07
    二、为什么要线程同步因为当我们有个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作...

    一、引言

    前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊。闲话不多说,进入正题。

    二、为什么要线程同步

    因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。

    三、不同步时的代码

    Bank.java

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3. /** 
    4.  * @author ww 
    5.  * 
    6.  */  
    7. public class Bank {  
    8.   
    9.     private int count =0;//账户余额  
    10.       
    11.     //存钱  
    12.     public  void addMoney(int money){  
    13.         count +=money;  
    14.         System.out.println(System.currentTimeMillis()+"存进:"+money);  
    15.     }  
    16.       
    17.     //取钱  
    18.     public  void subMoney(int money){  
    19.         if(count-money < 0){  
    20.             System.out.println("余额不足");  
    21.             return;  
    22.         }  
    23.         count -=money;  
    24.         System.out.println(+System.currentTimeMillis()+"取出:"+money);  
    25.     }  
    26.       
    27.     //查询  
    28.     public void lookMoney(){  
    29.         System.out.println("账户余额:"+count);  
    30.     }  
    31. }  


    SyncThreadTest.java

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3.   
    4. public class SyncThreadTest {  
    5.   
    6.     public static void main(String args[]){  
    7.         final Bank bank=new Bank();  
    8.           
    9.         Thread tadd=new Thread(new Runnable() {  
    10.               
    11.             @Override  
    12.             public void run() {  
    13.                 // TODO Auto-generated method stub  
    14.                 while(true){  
    15.                     try {  
    16.                         Thread.sleep(1000);  
    17.                     } catch (InterruptedException e) {  
    18.                         // TODO Auto-generated catch block  
    19.                         e.printStackTrace();  
    20.                     }  
    21.                     bank.addMoney(100);  
    22.                     bank.lookMoney();  
    23.                     System.out.println("\n");  
    24.                       
    25.                 }  
    26.             }  
    27.         });  
    28.           
    29.         Thread tsub = new Thread(new Runnable() {  
    30.               
    31.             @Override  
    32.             public void run() {  
    33.                 // TODO Auto-generated method stub  
    34.                 while(true){  
    35.                     bank.subMoney(100);  
    36.                     bank.lookMoney();  
    37.                     System.out.println("\n");  
    38.                     try {  
    39.                         Thread.sleep(1000);  
    40.                     } catch (InterruptedException e) {  
    41.                         // TODO Auto-generated catch block  
    42.                         e.printStackTrace();  
    43.                     }     
    44.                 }  
    45.             }  
    46.         });  
    47.         tsub.start();  
    48.           
    49.         tadd.start();  
    50.     }  
    51.       
    52.       
    53.   
    54. }  

    代码很简单,我就不解释了,看看运行结果怎样呢?截取了其中的一部分,是不是很乱,有写看不懂。

    [java]  view plain  copy
    1. 余额不足  
    2. 账户余额:0  
    3.   
    4.   
    5. 余额不足  
    6. 账户余额:100  
    7.   
    8.   
    9. 1441790503354存进:100  
    10. 账户余额:100  
    11.   
    12.   
    13. 1441790504354存进:100  
    14. 账户余额:100  
    15.   
    16.   
    17. 1441790504354取出:100  
    18. 账户余额:100  
    19.   
    20.   
    21. 1441790505355存进:100  
    22. 账户余额:100  
    23.   
    24.   
    25. 1441790505355取出:100  
    26. 账户余额:100  

    四、使用同步时的代码

    (1)同步方法:

    即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    修改后的Bank.java

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3. /** 
    4.  * @author ww 
    5.  * 
    6.  */  
    7. public class Bank {  
    8.   
    9.     private int count =0;//账户余额  
    10.       
    11.     //存钱  
    12.     public  synchronized void addMoney(int money){  
    13.         count +=money;  
    14.         System.out.println(System.currentTimeMillis()+"存进:"+money);  
    15.     }  
    16.       
    17.     //取钱  
    18.     public  synchronized void subMoney(int money){  
    19.         if(count-money < 0){  
    20.             System.out.println("余额不足");  
    21.             return;  
    22.         }  
    23.         count -=money;  
    24.         System.out.println(+System.currentTimeMillis()+"取出:"+money);  
    25.     }  
    26.       
    27.     //查询  
    28.     public void lookMoney(){  
    29.         System.out.println("账户余额:"+count);  
    30.     }  
    31. }  
    再看看运行结果:

    [html]  view plain  copy
    1. 余额不足  
    2. 账户余额:0  
    3.   
    4.   
    5. 余额不足  
    6. 账户余额:0  
    7.   
    8.   
    9. 1441790837380存进:100  
    10. 账户余额:100  
    11.   
    12.   
    13. 1441790838380取出:100  
    14. 账户余额:0  
    15. 1441790838380存进:100  
    16. 账户余额:100  
    17.   
    18.   
    19.   
    20.   
    21. 1441790839381取出:100  
    22. 账户余额:0  
    瞬间感觉可以理解了吧。

    注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    (2)同步代码块

    即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    Bank.java代码如下:

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3. /** 
    4.  * @author ww 
    5.  * 
    6.  */  
    7. public class Bank {  
    8.   
    9.     private int count =0;//账户余额  
    10.       
    11.     //存钱  
    12.     public   void addMoney(int money){  
    13.           
    14.         synchronized (this) {  
    15.             count +=money;  
    16.         }  
    17.         System.out.println(System.currentTimeMillis()+"存进:"+money);  
    18.     }  
    19.       
    20.     //取钱  
    21.     public   void subMoney(int money){  
    22.           
    23.         synchronized (this) {  
    24.             if(count-money < 0){  
    25.                 System.out.println("余额不足");  
    26.                 return;  
    27.             }  
    28.             count -=money;  
    29.         }  
    30.         System.out.println(+System.currentTimeMillis()+"取出:"+money);  
    31.     }  
    32.       
    33.     //查询  
    34.     public void lookMoney(){  
    35.         System.out.println("账户余额:"+count);  
    36.     }  
    37. }  

    运行结果如下:

    [html]  view plain  copy
    1. 余额不足  
    2. 账户余额:0  
    3.   
    4.   
    5. 1441791806699存进:100  
    6. 账户余额:100  
    7.   
    8.   
    9. 1441791806700取出:100  
    10. 账户余额:0  
    11.   
    12.   
    13. 1441791807699存进:100  
    14. 账户余额:100  

    效果和方法一差不多。

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

    (3)使用特殊域变量(volatile)实现线程同步

        a.volatile关键字为域变量的访问提供了一种免锁机制 
        b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新 
        c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
        d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

    Bank.java代码如下:

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3. /** 
    4.  * @author ww 
    5.  * 
    6.  */  
    7. public class Bank {  
    8.   
    9.     private volatile int count = 0;// 账户余额  
    10.   
    11.     // 存钱  
    12.     public void addMoney(int money) {  
    13.   
    14.         count += money;  
    15.         System.out.println(System.currentTimeMillis() + "存进:" + money);  
    16.     }  
    17.   
    18.     // 取钱  
    19.     public void subMoney(int money) {  
    20.   
    21.         if (count - money < 0) {  
    22.             System.out.println("余额不足");  
    23.             return;  
    24.         }  
    25.         count -= money;  
    26.         System.out.println(+System.currentTimeMillis() + "取出:" + money);  
    27.     }  
    28.   
    29.     // 查询  
    30.     public void lookMoney() {  
    31.         System.out.println("账户余额:" + count);  
    32.     }  
    33. }  
    运行效果怎样呢?

    [html]  view plain  copy
    1. 余额不足  
    2. 账户余额:0  
    3.   
    4.   
    5. 余额不足  
    6. 账户余额:100  
    7.   
    8.   
    9. 1441792010959存进:100  
    10. 账户余额:100  
    11.   
    12.   
    13. 1441792011960取出:100  
    14. 账户余额:0  
    15.   
    16.   
    17. 1441792011961存进:100  
    18. 账户余额:100  

    是不是又看不懂了,又乱了。这是为什么呢?就是因为volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。 它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

    (4)使用重入锁实现线程同步

        在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
         ReenreantLock类的常用方法有:
             ReentrantLock() : 创建一个ReentrantLock实例 
             lock() : 获得锁 
             unlock() : 释放锁 
        注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
    Bank.java代码修改如下:

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3. import java.util.concurrent.locks.Lock;  
    4. import java.util.concurrent.locks.ReentrantLock;  
    5.   
    6. /** 
    7.  * @author ww 
    8.  * 
    9.  */  
    10. public class Bank {  
    11.   
    12.     private  int count = 0;// 账户余额  
    13.       
    14.     //需要声明这个锁  
    15.     private Lock lock = new ReentrantLock();  
    16.   
    17.     // 存钱  
    18.     public void addMoney(int money) {  
    19.         lock.lock();//上锁  
    20.         try{  
    21.         count += money;  
    22.         System.out.println(System.currentTimeMillis() + "存进:" + money);  
    23.           
    24.         }finally{  
    25.             lock.unlock();//解锁  
    26.         }  
    27.     }  
    28.   
    29.     // 取钱  
    30.     public void subMoney(int money) {  
    31.         lock.lock();  
    32.         try{  
    33.               
    34.         if (count - money < 0) {  
    35.             System.out.println("余额不足");  
    36.             return;  
    37.         }  
    38.         count -= money;  
    39.         System.out.println(+System.currentTimeMillis() + "取出:" + money);  
    40.         }finally{  
    41.             lock.unlock();  
    42.         }  
    43.     }  
    44.   
    45.     // 查询  
    46.     public void lookMoney() {  
    47.         System.out.println("账户余额:" + count);  
    48.     }  
    49. }  
    运行效果怎么样呢?

    [html]  view plain  copy
    1. 余额不足  
    2. 账户余额:0  
    3.   
    4.   
    5. 余额不足  
    6. 账户余额:0  
    7.   
    8.   
    9. 1441792891934存进:100  
    10. 账户余额:100  
    11.   
    12.   
    13. 1441792892935存进:100  
    14. 账户余额:200  
    15.   
    16.   
    17. 1441792892954取出:100  
    18. 账户余额:100  
    效果和前两种方法差不多。

    如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

    (5)使用局部变量实现线程同步

    Bank.java代码如下:

    [java]  view plain  copy
    1. package threadTest;  
    2.   
    3.   
    4. /** 
    5.  * @author ww 
    6.  * 
    7.  */  
    8. public class Bank {  
    9.   
    10.     private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){  
    11.   
    12.         @Override  
    13.         protected Integer initialValue() {  
    14.             // TODO Auto-generated method stub  
    15.             return 0;  
    16.         }  
    17.           
    18.     };  
    19.       
    20.   
    21.     // 存钱  
    22.     public void addMoney(int money) {  
    23.         count.set(count.get()+money);  
    24.         System.out.println(System.currentTimeMillis() + "存进:" + money);  
    25.           
    26.     }  
    27.   
    28.     // 取钱  
    29.     public void subMoney(int money) {  
    30.         if (count.get() - money < 0) {  
    31.             System.out.println("余额不足");  
    32.             return;  
    33.         }  
    34.         count.set(count.get()- money);  
    35.         System.out.println(+System.currentTimeMillis() + "取出:" + money);  
    36.     }  
    37.   
    38.     // 查询  
    39.     public void lookMoney() {  
    40.         System.out.println("账户余额:" + count.get());  
    41.     }  
    42. }  

    运行效果:

    [html]  view plain  copy
    1. 余额不足  
    2. 账户余额:0  
    3.   
    4.   
    5. 余额不足  
    6. 账户余额:0  
    7.   
    8.   
    9. 1441794247939存进:100  
    10. 账户余额:100  
    11.   
    12.   
    13. 余额不足  
    14. 1441794248940存进:100  
    15. 账户余额:0  
    16.   
    17.   
    18. 账户余额:200  
    19.   
    20.   
    21. 余额不足  
    22. 账户余额:0  
    23.   
    24.   
    25. 1441794249941存进:100  
    26. 账户余额:300  

    看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。

    ThreadLocal与同步机制 
    a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题

    b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式 



    同步的方法:

    一、同步方法

      即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
    注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

     

    二、同步代码块

      即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
        代码如: 
    synchronized(object){ 
    }
        注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 
    复制代码
        package com.xhj.thread;
     
        /**
         * 线程同步的运用
         * 
         * @author XIEHEJUN
         * 
         */
        public class SynchronizedThread {
     
            class Bank {
                private int account = 100;
                public int getAccount() {
                    return account;
                }
     
                /**
                 * 用同步方法实现
                 * 
                 * @param money
                 */
                public synchronized void save(int money) {
                    account += money;
                }
     
                /**
                 * 用同步代码块实现
                 * 
                 * @param money
                 */
                public void save1(int money) {
                    synchronized (this) {
                        account += money;
                    }
                }
            }
    复制代码
    复制代码
    class NewThread implements Runnable {
                private Bank bank;
     
                public NewThread(Bank bank) {
                    this.bank = bank;
                }
     
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        // bank.save1(10);
                        bank.save(10);
                        System.out.println(i + "账户余额为:" + bank.getAccount());
                    }
                }
     
            }
     
            /**
             * 建立线程,调用内部类
             */
            public void useThread() {
                Bank bank = new Bank();
                NewThread new_thread = new NewThread(bank);
                System.out.println("线程1");
                Thread thread1 = new Thread(new_thread);
                thread1.start();
                System.out.println("线程2");
                Thread thread2 = new Thread(new_thread);
                thread2.start();
            }
     
            public static void main(String[] args) {
                SynchronizedThread st = new SynchronizedThread();
                st.useThread();
            }
     
        }
    复制代码

     

     
            
    =====================================

    示例加讲解

    同步是多线程中的重要概念。同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法同步块,这两种方式都要用到synchronized关键字。

    同步方法:给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。下面代码是一个同步方法的示例:

    复制代码
    public synchronized void aMethod() { 
        // do something 
    } 
    
    public static synchronized void anotherMethod() { 
        // do something 
    } 
    复制代码

    线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。

    同步块:同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步,而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。

    如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。

    synchronized 关键字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,请看下面的例子:

    复制代码
    public class ThreadTest implements Runnable{
    
    public synchronized void run(){
      for(int i=0;i<10;i++) {
        System.out.print(" " + i);
      }
    }
    
    public static void main(String[] args) {
      Runnable r1 = new ThreadTest(); //也可写成ThreadTest r1 = new ThreadTest();
      Runnable r2 = new ThreadTest();
      Thread t1 = new Thread(r1);
      Thread t2 = new Thread(r2);
      t1.start();
      t2.start();
    }}
    复制代码

     

    在这个程序中,run()虽然被加上了synchronized 关键字,但保护的不是共享数据。因为这个程序中的t1,t2 是两个对象(r1,r2)的线程。而不同的对象的数据是不同的,r1,r2 有各自的run()方法,所以输出结果无法预知。

    synchronized的目的是使同一个对象的多个线程,在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。

    示例3:

    复制代码
    public class ThreadTest implements Runnable{
    
    public synchronized void run(){
      for(int i=0;i<10;i++){
        System.out.print(" " + i);
      }
    }
    
    public static void main(String[] args){
      Runnable r = new ThreadTest();
      Thread t1 = new Thread(r);
      Thread t2 = new Thread(r);
      t1.start();
      t2.start();
    }}
    复制代码

     

    如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()受synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。

    示例4:

    复制代码
    public class ThreadTest implements Runnable{
    
    public void run(){
    
        synchronized(this){
        for(int i=0;i<10;i++){
            System.out.print(" " + i);
        }
    } 
    }
    
    public static void main(String[] args){
        Runnable r = new ThreadTest();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
    }    
    复制代码

     

    这个程序与示例3 的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4 的形式,this 代表“这个对象”。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。

    示例5:

    复制代码
    public class ThreadTest implements Runnable{
    
    public void run(){
      for(int k=0;k<5;k++){
        System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
      }
    
    synchronized(this){
      for(int k=0;k<5;k++) {
        System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
      }} }
    
    public static void main(String[] args){
      Runnable r = new ThreadTest();
      Thread t1 = new Thread(r,"t1_name");
      Thread t2 = new Thread(r,"t2_name");
      t1.start();
      t2.start();
    } }
    复制代码

     

    运行结果:

    t1_name : for loop : 0

    t1_name : for loop : 1

    t1_name : for loop : 2

    t2_name : for loop : 0

    t1_name : for loop : 3

    t2_name : for loop : 1

    t1_name : for loop : 4

    t2_name : for loop : 2

    t1_name : synchronized for loop : 0

    t2_name : for loop : 3

    t1_name : synchronized for loop : 1

    t2_name : for loop : 4

    t1_name : synchronized for loop : 2

    t1_name : synchronized for loop : 3

    t1_name : synchronized for loop : 4

    t2_name : synchronized for loop : 0

    t2_name : synchronized for loop : 1

    t2_name : synchronized for loop : 2

    t2_name : synchronized for loop : 3

    t2_name : synchronized for loop : 4

    第一个for 循环没有受synchronized 保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1 执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for 循环(t2 刚执行到k=2)。t1 开始执行第二个for 循环,当t1的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2 想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for 循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for 循环后,它才可以执行第二个for 循环。

    =====================================

    三、wait与notify

    wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
    notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
    Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

    详细见:wait、notify、notifyAll的使用方法

    四、使用特殊域变量(volatile)实现线程同步

        a.volatile关键字为域变量的访问提供了一种免锁机制
        b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
        c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
        d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 
        
        例如: 
            在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。 
        
        代码实例: 
     
    复制代码
            //只给出要修改的代码,其余代码与上同
            class Bank {
                //需要同步的变量加上volatile
                private volatile int account = 100;
     
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    account += money;
                }
            }
    复制代码
        注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 
        用final域,有锁保护的域和volatile域可以避免非同步的问题。 

    五、使用重入锁实现线程同步

        在 JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 
        ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
     ReenreantLock类的常用方法有:
    ReentrantLock() : 创建一个ReentrantLock实例 
    lock() : 获得锁 
    unlock() : 释放锁 
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
            
        例如: 
            在上面例子的基础上,改写后的代码为: 
    复制代码
           //只给出要修改的代码,其余代码与上同
            class Bank {
                
                private int account = 100;
                //需要声明这个锁
                private Lock lock = new ReentrantLock();
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    lock.lock();
                    try{
                        account += money;
                    }finally{
                        lock.unlock();
                    }
                    
                }
            }
    复制代码
         注:关于Lock对象和synchronized关键字的选择: 
            a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 
            b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
            c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 
     

    六、使用局部变量实现线程同步

        如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
         ThreadLocal 类的常用方法
    ThreadLocal() : 创建一个线程本地变量 
    get() : 返回此线程局部变量的当前线程副本中的值 
    initialValue() : 返回此线程局部变量的当前线程的"初始值" 
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
        例如: 
            在上面例子基础上,修改后的代码为: 
    复制代码
            //只改Bank类,其余代码与上同
            public class Bank{
                //使用ThreadLocal类管理共享变量account
                private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                    @Override
                    protected Integer initialValue(){
                        return 100;
                    }
                };
                public void save(int money){
                    account.set(account.get()+money);
                }
                public int getAccount(){
                    return account.get();
                }
            }
    复制代码
        注:ThreadLocal与同步机制 
            a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
            b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
     

    七、使用阻塞队列实现线程同步

    前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步 LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞代码实例: 实现商家生产商品和买卖商品的同步

     

    注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

      add()方法会抛出异常

      offer()方法返回false

      put()方法会阻塞

     

     

    7.使用原子变量实现线程同步

     

    需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

    那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

    AtomicInteger类常用方法:

    AtomicInteger(int initialValue) : 创建具有给定初始值的新的

    AtomicIntegeraddAddGet(int dalta) : 以原子方式将给定值与当前值相加

    get() : 获取当前值

    代码实例:

    只改Bank类,其余代码与上面第一个例子同

    复制代码
    class Bank {
        private AtomicInteger account = new AtomicInteger(100);
        public AtomicInteger getAccount() {
            return account; 
        } 
        public void save(int money) {
            account.addAndGet(money);
        }
    }
    复制代码

    补充--原子操作主要有:  

    对于引用变量和大多数原始变量(long和double除外)的读写操作;  

    对于所有使用volatile修饰的变量(包括long和double)的读写操作。


    展开全文
  • 主要介绍了Java利用ExecutorService实现同步执行大量线程,ExecutorService可以维护我们的大量线程在操作临界资源时的稳定性。
  • Java 多线程同步和异步详解

    千次阅读 2018-05-31 10:00:32
    转载自 https://www.cnblogs.com/mengyuxin/p/5358364.htmljava线程 同步与异步 线程池1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了...

    转载自 https://www.cnblogs.com/mengyuxin/p/5358364.html

    java线程 同步与异步 线程池
    1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线
    程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解
    决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲
    系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制
    同步:A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求
    不到,怎么办,A线程只能等待下去
    异步:A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程
    仍然请求的到,A线程无需等待
     
    显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个
    进程崩溃,但没有同步机制的存在,性能会有所提升
     
    java中实现多线程
    1)继承Thread,重写里面的run方法
    2)实现runnable接口
     
    Doug Lea比较推荐后者,第一,java没有单继承的限制第二,还可以隔离代码
     
    线程池
    要知道在计算机中任何资源的创建,包括线程,都需要消耗系统资源的。在WEB服务中,对于web服
    务器的响应速度必须要尽可能的快,这就容不得每次在用户提交请求按钮后,再创建线程提供服务
    。为了减少用户的等待时间,线程必须预先创建,放在线程池中,线程池可以用HashTable这种数
    据结构来实现,看了Apach HTTP服务器的线程池的源代码,用是就是HashTable,KEY用线程对象,
    value 用ControlRunnable,ControlRunnable是线程池中唯一能干活的线程,是它指派线程池中的
    线程对外提供服务。
    出于安全考虑,Apach HTTP服务器的线程池它是同步的。听说weblogic有异步的实现方式,没有研
    究过,不敢确定
    ---------------------------------------------------------------------------------------
    --------------------------------------------------------------------------------------
    一、关键字:
    thread(线程)、thread-safe(线程安全)、intercurrent(并发的)
    synchronized(同步的)、asynchronized(异步的)、
    volatile(易变的)、atomic(原子的)、share(共享)
    二、总结背景:
    一次读写共享文件编写,嚯,好家伙,竟然揪出这些零碎而又是一路的知识点。于是乎,Google和
    翻阅了《Java参考大全》、《Effective Java Second Edition》,特此总结一下供日后工作学习参
    考。
    三、概念:
    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 变量提供理
    想的线程安全,必须同时满足下面两个条件:
    对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。
     
    sleep() vs wait()
    sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监
    控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
    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();
    }
    }
    }
     
     
     
     
     
     
    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 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() {
    // TODO Auto-generated method stub
    //target.nonsynCall(msg);
    target.synCall(msg);
    target.test();
    }
     
     
    }

    展开全文
  • 一、为何写最为一个Android开发者,如果做得不够深入可能为不会去处理多线程同步的问题,稍微简单点可能使用一个线程池就可以搞定了,有关线程池的介绍可以参考我的另一篇文章:ExecutorService+LruCache+...

    一、为何写

    最为一个Android开发者,如果做得不够深入可能为不会去处理多线程同步的问题,稍微简单点可能使用一个线程池就可以搞定了,有关线程池的介绍可以参考我的另一篇文章:ExecutorService+LruCache+DiskLruCache用一个类打造简单的图片加载库
    只是前段时间研究Android音视频硬解码,看到开源项目中用到了线程同步,就是在视频的YUV数据的暂存,和解码为视频并展示,用到了两个线程去做,一个线程收集视频源数据,一个线程负责解码并播放视频,一个视频数据池是两个线程共享的,数据池满了或者空了的时候两个线程是要做出相应处理的,这就涉及到线程同步了。
    这里写图片描述
    学习、工作和生活的心态就要像向日葵,就算是太阳不在也要迎着月亮!

    二、名字讲解

    什么是线程同步?

    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。
    

    实现同步机制有两个方法:

    1、同步代码块:

    synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。

    2、同步方法:

    public synchronized 数据返回类型方法名(){}

    通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
    1,该类的对象可以被多个线程安全的访问。
    2,每个线程调用该对象的任意方法之后,都将得到正确的结果。
    3,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
    注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等

    ※不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。
    线程通讯:
    当使用synchronized 来修饰某个共享资源时(分同步代码块和同步方法两种情况),当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait() 和notify()/notifyAll()方法来进行线程通讯了。
    Java.lang.object 里的三个方法wait() notify() notifyAll()

    wait()
    导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。

    wait(mills)
    都是等待指定时间后自动苏醒,调用wait方法的当前线程会释放该同步监视器的锁定,可以不用notify或notifyAll方法把它唤醒。

    notify()
    唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

    notifyAll()
    唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

    注意,notify方法一定要在synchronized同步里面调用,还有做异常捕捉。


    原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。

    自增操作(++)不是原子操作,因为它涉及到一次读和一次写。

    原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

    同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。

    同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。

    当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。
    1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
    2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)

    线程同步的特征:
    1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
    2、 每个对象都有唯一的同步锁
    3、 在静态方法前面可以使用synchronized修饰符。
    4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
    5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。

    释放对象的锁:
    1、 执行完同步代码块就会释放对象的锁
    2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
    3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。

    死锁:
    线程1独占(锁定)资源A,等待获得资源B后,才能继续执行
    同时
    线程2独占(锁定)资源B,等待获得资源A后,才能继续执行
    这样就会发生死锁,程序无法正常执行

    如何避免死锁
    一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。


    注意:
    1、线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。
    2、只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
    3、只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
    4、多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

    5、我们要尽量避免这种直接把synchronized加在函数定义上的偷懒做法。因为我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

    同步锁:
    我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。
    同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。
    访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
    这就是说,同步锁本身也一定是多个线程之间的共享对象。

    三、生产者消费者代码示例

    产品仓库

    package com.danxx.javalib2;
    
    import java.util.LinkedList;
    import java.util.Queue;
    
    /**
     * 数据存储仓库和操作
     * 一个缓冲区,缓冲区有最大限制,当缓冲区满
     * 的时候,生产者是不能将产品放入到缓冲区里面的,
     * 当然,当缓冲区是空的时候,消费者也不能从中拿出来产品,
     * 这就涉及到了在多线程中的条件判断
     * Created by dawish on 2017/7/13.
     */
    public class Storage {
    
        private static volatile int goodNumber = 1;
    
        private final static int MAX_SIZE = 20;
        /**
         *  Queue操作解析:
         *  add       增加一个元索                 如果队列已满, 则抛出一个IIIegaISlabEepeplian异常
         *  remove    移除并返回队列头部的元素     如果队列为空, 则抛出一个NoSuchElementException异常
         *  element   返回队列头部的元素           如果队列为空, 则抛出一个NoSuchElementException异常
         *  offer     添加一个元素并返回true       如果队列已满, 则返回false
         *  poll      移除并返问队列头部的元素     如果队列为空, 则返回null
         *  peek      返回队列头部的元素           如果队列为空, 则返回null
         *  put       添加一个元素                 如果队列满,   则阻塞
         *  take      移除并返回队列头部的元素     如果队列为空, 则阻塞
         *
         */
        Queue<String> storage;
        public Storage() {
            storage = new LinkedList<String>();
        }
    
        /**
         *
         * @param dataValue
         */
        public synchronized void put(String dataValue, String threadName){
            if(storage.size() >= MAX_SIZE){
                try {
                    goodNumber = 1;
                    super.wait();  //当生产满了后让生产线程等待
                    return;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            storage.add(dataValue + goodNumber++);
            System.out.println(threadName + dataValue + goodNumber);
            super.notify();  //每次添加一个数据就唤醒一个消费等待的线程来消费
        }
    
        /**
         *
         * @return
         * @throws InterruptedException
         */
        public synchronized String get(String threadName) {
            if(storage.size() == 0){
                try {
                    super.wait();  //当产品仓库为空的时候让消费线程等待
                    System.out.println(threadName + "wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
            super.notify();  //当数据不为空的时候就唤醒一个生产线程来生产
            String value = storage.remove();
            return value;
        }
    
    }
    

    生产者

    package com.danxx.javalib2;
    
    import java.util.UUID;
    
    /**
     * 生产者
     * Created by dawish on 2017/7/13.
     */
    public class Producer extends Thread{
    
        private Storage storage;//生产者仓库
        private String name="";
    
        public Producer(Storage storage, String name) {
            this.storage = storage;
            this.name = name;
        }
        public void run(){
            //生产者每隔1s生产1~100消息
            long oldTime = System.currentTimeMillis();
            while(true){
                synchronized(storage){
                    if (System.currentTimeMillis() - oldTime >= 1000) {
                        oldTime = System.currentTimeMillis();
                        String msg = UUID.randomUUID().toString();
                        storage.put("-ID:" ,name);
                    }
                }
            }
        }
    }

    消费者

    package com.danxx.javalib2;
    
    /**
     * 消费者
     * Created by dawish on 2017/7/13.
     */
    
    public class Consumer extends Thread{
    
        private Storage storage;//仓库
    
        private String name="";
    
        public Consumer(Storage storage, String name) {
            this.storage = storage;
            this.name = name;
        }
        public void run(){
            while(true){
                synchronized(storage){
                    //消费者去仓库拿消息的时候,如果发现仓库数据为空,则等待
                    String data = storage.get(name);
                    if(data != null){
    
                        System.out.println(name +"-------------"+ data);
    
                    }
                }
            }
        }
    }

    main方法

    package com.danxx.javalib2;
    
    /**
     *  Java中的多线程会涉及到线程间通信,常见的线程通信方式,例如共享变量、管道流等,
     *  这里我们要实现生产者消费者模式,也需要涉及到线程通信,不过这里我们用到了java中的
     *  wait()、notify()方法:
     *  wait():进入临界区的线程在运行到一部分后,发现进行后面的任务所需的资源还没有准备充分,
     *  所以调用wait()方法,让线程阻塞,等待资源,同时释放临界区的锁,此时线程的状态也从RUNNABLE状态变为WAITING状态;
     *  notify():准备资源的线程在准备好资源后,调用notify()方法通知需要使用资源的线程,
     *  同时释放临界区的锁,将临界区的锁交给使用资源的线程。
     *  wait()、notify()这两个方法,都必须要在临界区中调用,即是在synchronized同步块中调用,
     *  不然会抛出IllegalMonitorStateException的异常。
     *  Created by dawish on 2017/7/14.
     */
    
    public class MainApp {
        public static void main(String[] args) {
            Storage storage = new Storage();
    
            Producer producer1 = new Producer(storage, "Producer-1");
            Producer producer2 = new Producer(storage, "Producer-2");
            Producer producer3 = new Producer(storage, "Producer-3");
            Producer producer4 = new Producer(storage, "Producer-4");
    
            Consumer consumer1 = new Consumer(storage, "Consumer-1");
            Consumer consumer2 = new Consumer(storage, "Consumer-2");
    
            producer1.start();
            producer2.start();
            producer3.start();
            producer4.start();
    
            consumer1.start();
            consumer2.start();
        }
    }
    

    运行结果(4个生产者2个消费者)

    这里写图片描述

    四、Github地址

    https://github.com/Dawish/CustomViews/tree/master/JavaLib

    展开全文
  • java多线程 —— 多线程同步

    千次阅读 2020-05-30 20:42:33
    1、什么是多线程同步器? 可以理解为,用于控制多线程之前同步动作的工具。 2、为什么使用多线程同步器? 在实际应用中,我们希望多线程根据某些一些特定的规则执行。因此有了多线程同步器,通过不同的多线程同步...
  • java多线程同步例子

    热门讨论 2010-03-24 10:11:10
    java多线程同步互斥访问实例,对于初学者或是温故而知新的同道中人都是一个很好的学习资料
  • JAVA多线程——实现同步

    万次阅读 多人点赞 2018-07-26 17:20:33
    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了...
  • java中实现多线程的两种方法

    千次阅读 2021-03-09 07:44:52
    java多线程有几种实现方法,都是什么?同步有几种实java中多线程的实现方法有两种:1.直接继承thread类;2.实现runnable接口;同步的实现方法有五种:1.同步方法;2.同步代码块;3.使用特殊域变量(volatile)实现线程...
  • 一、多线程同步关键字-synchronized1.概念 synchronized保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。当多个并发线程访问同一个对象object中的同步...
  • java线程同步的实现方式

    万次阅读 多人点赞 2019-03-08 01:47:21
    线程同时操作一个可共享的资源时会出现线程安全问题,将会导致数据不一致,因此使用同步锁来防止该操作执行完之前不许被其他线程执行,从而保证了该变量的唯一性和准确性。下面总结一些java线程实现同步方式,...
  • java多线程同步的五种方法

    千次阅读 2017-09-12 10:15:13
    因为当我们有线程要同时访问同一个变量或对象时,如果这些线程中午既有读又有写操作时,就会导致变量值或者对象的状态出现混乱,从而导致程序异常,举个例子:如果同一个银行账户被连个线程操作,一个存钱1000....
  • Java线程同步和锁定

    千次阅读 2022-03-23 09:08:29
    同步和锁定 Java中每个对象都有一个内置锁。 当程序运行到synchronized同步方法或代码... 释放锁是指持锁线程退出了synchronized同步方法或代码块。 关于锁和同步的几个要点 1)只能同步方法,而不能同步变量和类;
  • java 多线程同步方法的实例 java 多线程同步方法的实例 java 多线程同步方法的实例
  • Java 多线程同步-模拟窗口售票

    千次阅读 2018-11-03 17:23:11
    Java 多线程同步-模拟窗口售票 java开发者 2018-11-02 20:52:45 Java 内容目录 实现Runnable接口 使用 同步代码块 1、实现的方式 2、继承的方式 使用 同步方法 1、实现的方式 本文例子:利用多线程模拟 ...
  • JAVA 多线程同步工具类总结

    千次阅读 2012-12-10 14:23:07
    这两天看了《JAVA 并发编程实战》一书,内容很不错,关于同步常用的几个工具类总结如下: CountDownLatch 闭锁:可以延迟线程的进度,直到锁到达终止状态。闭锁的作用相当于一扇门,在锁到达终止状态之前这扇门...
  • JAVA多线程之间实现同步+多线程并发同步解决方案

    万次阅读 多人点赞 2018-03-04 14:09:15
    一、什么是线程安全问题 为什么...案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。/** * 需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。 * Crea...
  • Java多线程】synchronized同步方法

    万次阅读 2021-10-10 23:54:05
    “非线程安全”其实会在线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取得的数据其实是被更改过的。...两个线程访问同一个对象中的同步方法时一定是线程安全的
  • Java多线程-6】synchronized同步

    千次阅读 2020-03-31 15:35:49
    前文描述了Java多线程编程,多线程的方式提高了系统资源利用和程序效率,但多个线程同时处理共享的数据时,就将面临线程安全的问题。 例如,下面模拟这样一个场景:一个售票处有3个售票员,出售20张票。 public ...
  • Java多线程多线程的实现方式、同步代码块的方式
  • java多线程同步分析

    2009-08-26 12:31:01
    java多线程同步分析java多线程同步java多线程同步分析析java多线程同步分析java多线程同步分析
  • Java多线程——交替输出

    千次阅读 2021-01-17 22:12:58
    设置两个线程交替输出"1 2 1 2 1 2…" 1. 定义一个全局锁对象 为了保证两个线程使用的一定是同一个锁,我们创建一个对象作为静态属性放到一个类中, 这个对象就用来充当锁 2. 定义一个线程输出1,另外一个线程输出2 ...
  • 首先存钱取钱的这个操作,应该是线程操作的,可以有很多的顾客,这意思就是得有...因为有个老铁问这个多线程的代码。 首先是银行,这个对象model的创建。 package com.lxk.threadTest.bank; /** * 银行model,...
  • Java多线程同步和异步详解

    千次阅读 2016-11-12 22:26:50
    1. 多线程并发时,多个线程同时请求同一资源,必然导致此资源的数据不安全。 2. 线程池 在WEB服务中,对于web服务器的响应速度必须尽可能的快,这就容不得在用户提交请求按钮后,再创建线程提供服务。为了减少用户...
  • Java多线程同步Boolean问题

    千次阅读 2015-12-27 15:06:01
    这需要有一个Flag,一开始我使用Boolean, 变量名receivedPeerList,以构造函数参数的形式传递进channelHandler中,然后在主线程和netty的channelRead事件中同步它。 用法如下: channelRead中修改它:
  • Java多线程|同步与锁机制

    千次阅读 2016-06-03 20:06:43
    总结 Java「锁与同步」机制。
  • Java学习笔记---多线程同步的五种方法

    万次阅读 多人点赞 2015-09-09 18:33:49
    二、为什么要线程同步 因为当我们有个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程...
  • java多线程同步编程

    千次阅读 2016-09-08 18:25:21
    学习java有一段时间了,一直对java多线程同步理解的不够深刻,今天将我学习的过程记录下来帮助大家一起来学习深刻理解java多线程同步策略 现实生活中多线程同步场景很多,比如说我的银行卡里面的money数是100,...
  • 在我们的业务中很可能会碰到需要执行一段时间的任务,并且如果同步的话就会造成一些无谓的等待。因此可以使用异步调用的方法,不阻塞当前其他任务的执行。 小栗子 首先我们先要创建一个线程池,可以根据自己的需求...
  • Java多线程编程总结

    2018-01-14 21:05:07
    Java线程线程同步-同步方法 Java线程线程同步-同步Java线程:并发协作-生产者消费者模型 Java线程:并发协作-死锁 Java线程:volatile关键字 Java线程:新特征-线程池 Java线程:新特征-有返回值的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 525,164
精华内容 210,065
关键字:

java多线程同步

java 订阅
友情链接: Audio.rar