zoukankan      html  css  js  c++  java
  • Java线程:同步

    一 同步的概念

      线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

      例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。

      MyRunnable.java

     1 package Thread;
     2 public class MyRunnable implements Runnable{
     3     private Foo foo=new Foo();
     4     public static void main(String[] args){
     5         MyRunnable r=new MyRunnable();
     6         Thread ta=new Thread(r,"Thread-A");
     7         Thread tb=new Thread(r,"Thread-B");
     8         ta.start();
     9         tb.start();
    10     }
    11     public void run(){
    12         for(int i=0;i<3;i++){
    13             this.fix(30);
    14             try{
    15                 Thread.sleep(1);
    16             }
    17             catch(InterruptedException e){
    18                 e.printStackTrace();
    19             }
    20             System.out.println(Thread.currentThread().getName()+":当前foo对象的值="+foo.getX());
    21         }
    22     }
    23     public int fix(int y){
    24         return foo.fix(y);
    25     }
    26 }
    View Code

      Foo.java

     1 package Thread;
     2 public class Foo {
     3     private int x=100;
     4     public int getX(){
     5         return x;
     6     }
     7     public int fix(int y){
     8         x=x-y;
     9         return x;
    10     }
    11 }
    View Code

      运行结果:

    1 Thread-A:当前foo对象的值=40
    2 Thread-B:当前foo对象的值=40
    3 Thread-A:当前foo对象的值=-20
    4 Thread-B:当前foo对象的值=-50
    5 Thread-A:当前foo对象的值=-80
    6 Thread-B:当前foo对象的值=-80
    View Code

    从结果看出,这样的输出值明显不合理。原因是两个线程不加控制的访问Foo对象并修改器数据所致。因此,应该对Foo的访问加以限制,每次只能有一个线程在访问。

     1 、同步方法

      线程的同步是保证多线程安全访问竞争资源的一种手段。 对于同步具体的Java代码中需要完成两个操作:

      1、把竞争访问的资源标识为private;

      2、同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。(对于synchronized而言,只能标记非抽象方法,不能标识成员变量。)

      为了演示同步方法的使用,构建了一个信用卡账户,起初信用额度为100w,然后模拟透支、存款等操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户余额设为私有变量,禁止直接访问。

      BankTest.java

     1 package Thread;
     2 
     3 public class BankTest {
     4     public static void main(String[] args){
     5         User u=new User("小二",100);
     6         MyThread t1=new MyThread("线程1",u,20);
     7         MyThread t2=new MyThread("线程2",u,-60);
     8         MyThread t3=new MyThread("线程3",u,-80);
     9         MyThread t4=new MyThread("线程4",u,-30);
    10         MyThread t5=new MyThread("线程5",u,32);
    11         MyThread t6=new MyThread("线程6",u,21);
    12         t1.start();
    13         t2.start();
    14         t3.start();
    15         t4.start();
    16         t5.start();
    17         t6.start();
    18     }
    19 }
    20 class MyThread extends Thread{
    21     private User u;
    22     private int y=0;
    23     MyThread(String name,User u,int y){
    24         super(name);
    25         this.u=u;
    26         this.y=y;
    27     }
    28     public void run(){
    29         u.oper(y);
    30     }
    31 }
    32 class User{
    33     private String code;
    34     private int cash;
    35     User(String code,int cash){
    36         this.code=code;
    37         this.cash=cash;
    38     }
    39     public String getCode(){
    40         return code;
    41     }
    42     /**
    43      * 业务方法
    44      * @param x 添加x万元
    45      * */
    46      public synchronized void oper(int x){
    47          try{
    48              Thread.sleep(10L);
    49              this.cash+=x;
    50              System.out.println(Thread.currentThread().getName()+"运行结果,增加"+x+",当前余额为:"+cash);
    51              Thread.sleep(10L);
    52          }
    53          catch(InterruptedException e){
    54              e.printStackTrace();
    55          }
    56      }
    57      public String toString(){
    58          return "User{" + "code=" + code + ",cash=" + cash +'}';
    59      }
    60 }
    View Code

    结果为:

    1 线程1运行结果,增加20,当前余额为:120
    2 线程2运行结果,增加-60,当前余额为:60
    3 线程6运行结果,增加21,当前余额为:81
    4 线程5运行结果,增加32,当前余额为:113
    5 线程4运行结果,增加-30,当前余额为:83
    6 线程3运行结果,增加-80,当前余额为:3

    但是如果把同步关键字synchronized去掉,结果为:

    1 线程2运行结果,增加-60,当前余额为:60
    2 线程6运行结果,增加21,当前余额为:1
    3 线程4运行结果,增加-30,当前余额为:3
    4 线程5运行结果,增加32,当前余额为:3
    5 线程1运行结果,增加20,当前余额为:60
    6 线程3运行结果,增加-80,当前余额为:-20

    显然是错误的,多个线程并发访问了竞争资源u,并对u的属性做了修改。可见同步的重要性。

    2、同步块

      有时候,同步块比同步方法有更好的效果。在上个例子的基础上对oper方法做了改变,由同步方法改为同步块模式。

       BankTest.java

     1 public void oper(int x){
     2          try{
     3              Thread.sleep(10L);
     4              synchronized(this){
     5                  this.cash+=x;
     6                  System.out.println(Thread.currentThread().getName()+"运行结果,增加"+x+",当前余额为:"+cash);             
     7              }
     8              Thread.sleep(10L);
     9          }
    10          catch(InterruptedException e){
    11              e.printStackTrace();
    12          }
    13      }

    结果和上例是一样的。具体的变化思想为:

    1 public syncronized int getX(){
    2    return x++;  //同步方法
    3 }
    45 public int getX(){
    6   synchronized(this){     
    7         return x;//非同步方法
    8     }
    9 }

    注意:

      在使用synchronized时,应该避免在synchronized方法或synchronized块中使用sleep或yield方法。因为synchronized程序块占用着对象锁,你休息那么其他线程只能等待。这样效率不高。同样,在同步程序块内调用yield方法让出CPU资源也没有意义,因为你占用着锁,其他资源无法访问。

    二、静态方法同步

      要同步静态方法,需要一个用整个类对象的锁,这个对象就是这个类(xxx.class),如:

    1 public static synchronized int setName(String name){
    2   xxx.name=name;  
    3 }
    4 等价于
    5 public static int setName(){
    6   synchronized(xxx.class){
    7       xxx.name=name;                
    8   }  
    9 }

    三、何时需要同步

      1、多个线程同时访问互斥(可交换)数据时,应该同步保护数据,确保两个线程不会同时更改它。

      2、非静态字段中可更改的数据,通常用非静态方法访问。

      3、静态字段中可更改的数据,用静态方法访问。

     四、总结

      1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

      2、线程同步方法是通过锁来实现,每个对象都仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程无法再访问该对象的其他同步方法。

      3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁不干预。一个线程获得锁,当在一个同步方法中访问另外对象的同步方法时,会获取两个对象的锁。

      4、对于同步,要时刻清醒哪个对象上同步,这是关键。

    当神已无能为力,那便是魔渡众生
  • 相关阅读:
    angualrjs2教程
    需要关注的技术
    webstorm 2016
    Java内存泄露分析和解决方案及Windows自带查看工具
    2018-6-21-随笔-WEB应用程序
    2018-6-20-随笔-SQL Server中乱码
    2018年6月15日随笔--统计图
    2018-6-12随笔-类库
    2018-6-11随笔-返回值-密钥
    vs各种版本
  • 原文地址:https://www.cnblogs.com/liuzhongfeng/p/5065013.html
Copyright © 2011-2022 走看看