Synchronized同步方法

“非线程安全”其实会在多个线程同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。

1**、方法内的变量为线程安全的**
方法内部的私有变量,则不存在“非线程安全”的问题,所得结果也就是“线程安全”的。

2、实例变量非线程安全
如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。

用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况。
如果对象仅有一个实例变量,则有可能出现覆盖的情况。

1、对象实例

public class HasSelfPrivateNum {
    //实例变量
    private int num=0;

    public void addI(String userName){
        try{
            if(userName.equals("a")){
                num=100;
                System.out.println("a set over");
                Thread.sleep(2000);
            }else{
                num=200;
                System.out.println("b set over");
            }
            System.out.println(userName+"  num="+num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

为了避免“非线程安全”问题,在方法前加关键字 synchronized.

synchronized public  void addI(String userName)
或者
public synchronized void addI(String userName)

多个线程访问同一个对象中的同步方法时一定是线程安全的。

同步是synchronized, 异步是 asynchronized。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁。哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁***Lock,那么其他线程只能是等待状态,前提是多个线程访问的是同一个对象*。

但是如果多个线程访问多个对象(不同的对象),则JVM会创建多个锁。

public class Run {
    public static void main(String[] args) {
        //两个不同的对象
        HasSelfPrivateNum has1=new HasSelfPrivateNum();
        HasSelfPrivateNum has2=new HasSelfPrivateNum();
        //两个线程
        CountOperate c1=new CountOperate(has1);
        CountOperate c2=new CountOperate(has2);
        //启动之后,结果是异步执行,与启动顺序无关
        c1.start();
        c2.start();
        try{
             Thread.sleep(5000);
        }catch (InterruptedException e){
        }
    }
}

调用关键字synchronized声明的方法一定是排队运行的(同步的)。只有共享资源的读写访问才需要同步化,如果不是共享资源,根本没有同步的必要。

1、(两个线程分别访问同一个对象的 同步方法和 非同步方法)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用同一个object对象中的 非synchronized类型的方法。(非线程方法的调用不受影响)

2、(两个线程分别访问同一个对象的 两个不同的 同步方法)A线程先持有object对象的Lock锁,B线程如果调用同一个对象的另一个同步方法则需要等待,也就是同步。

三、脏读—一个线程在读取数据,另一个线程在设置数据(同一个对象中,一个是同步方法,一个是非同步)。
发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。(对于修改和读取的方法都设置同步synchronized)

四、synchronized锁重入—自己可以再次获得自己的内部锁
在使用synchronized时,当一个线程得到一个对象锁后,(只要该线程还没有释放这个对象锁,)再次请求此对象锁时是可以再次得到该对象的锁的。(也就是同步方法可以调用同一个对象的另一个同步方法)

public class HasSelfPrivateNum {

     public synchronized void addI(String userName){
         //同一个线程中,同一个对象的同步方法
         //可以获得该对象锁Lock
         addA(userName);
    }
     public synchronized void addA(String userName){

    }
}

可重入锁也支持在父子类继承的环境中。当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。

public class SubHasSelfPrivateNum extends HasSelfPrivateNum {

    public synchronized void addC(String userName){
        //调用父类的同步方法,也可以获得对象锁
        this.addI(userName);
    }
}

当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

五、同步不具有继承性 —同步不可以继承

//父类
public class HasSelfPrivateNum {
    //父类的同步方法
     public synchronized void addI(String userName){
    }
}
//子类
public class SubHasSelfPrivateNum extends HasSelfPrivateNum {

    //同名方法,但是不是同步的
    public  void addI(String userName){

        //调用父类的同步方法
        super.addI(userName);
    }

}
多个线程调用子类的方法是异步调用的。但是调用父类的方法,则是同步的

思考:如果一开始就调用同步的方法,那么在该同步方法中存在调用其他方法(无论是否同步),都应该是同步的。

//非同步的对象
public class NoSysnTest {
    public  void play(){
         System.out.println("sleep begin threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
          try{
              Thread.sleep(5000);
              System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());

          }catch (InterruptedException e){

          }

    }
}

//线程
public class CountOperate extends Thread {
   private HasSelfPrivateNum numRef;
    private NoSysnTest noSysnTest;
    public CountOperate(HasSelfPrivateNum numRef,NoSysnTest noSysnTest){
        this.numRef=numRef;
        this.noSysnTest=noSysnTest;
    }
    public void run(){
    //调用对象的同步方法
       numRef.addI("a",noSysnTest);
    }
}

//同步的对象
public class HasSelfPrivateNum {

     public synchronized void addI(String userName,NoSysnTest noSysnTest){
     //调用非同步的方法
         noSysnTest.play();
    }

}
//执行
public class Run {
    public static void main(String[] args) {
        //两个不同的对象
        HasSelfPrivateNum has1=new HasSelfPrivateNum();
        NoSysnTest noSysnTest=new NoSysnTest();
        //两个线程
        CountOperate c1=new CountOperate(has1,noSysnTest);
        c1.setName("A");
        CountOperate c2=new CountOperate(has1,noSysnTest);
        c2.setName("B");
        //启动之后,结果是异步执行,与启动顺序无关
        c1.start();
        c2.start();
        noSysnTest.play();

    }
}

//结果
//main线程并不是同步的,而是异步的
//A、B线程是同步的,
sleep begin threadName=main time=1520412948959
sleep begin threadName=B time=1520412948969
sleep end threadName=main time=1520412953960
sleep end threadName=B time=1520412953969
sleep begin threadName=A time=1520412953969
sleep end threadName=A time=1520412958969
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值