Java多线程3.1:synchronized同步方法

本文主要介绍Java多线程中的同步,也就是如何在Java中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题。

非线程安全

什么是非线程安全?非线程安全是指多个线程对同一个对象中的实例变量进行并发访问时发生,发生的结果就是脏读,也就是取到的结果其实是被更改过的,而线程安全就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

方法内的变量为线程安全

非线程安全问题存在于”实例变量”中,如果是方法内部的私有变量,则不存在”非线程安全”的问题。

Example:

HasSelfPrivateNum.java

public class HasSelfPrivateNum {
    public void add(String name){
        int num=0;
        if(name.equals("a")){
            num=100;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            num=200;
        }
        System.out.println("num的值为:"+num);
    }
}

ThreadA.java

public class ThreadA  extends Thread{
    HasSelfPrivateNum hasSelfPrivateNum;
    ThreadA(HasSelfPrivateNum hasSelfPrivateNum){
        this.hasSelfPrivateNum=hasSelfPrivateNum;
    }
    @Override
    public void run() {
        super.run();
        hasSelfPrivateNum.add("a");
    }
}

ThreadB.java与ThreadA.java相类似

运行代码Run.java

public class Run {
    public static void main(String[] args) {
        HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(hasSelfPrivateNum);
        ThreadB threadB = new ThreadB(hasSelfPrivateNum);
        threadA.start();
        threadB.start();
    }
}

Result:

num的值为:200
num的值为:100

可见,方法中的变量不存在非线程安全的问题,永远是线程安全的。这是方法内部的变量是私有的特性所造成的。

实例变量的非线程安全

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

Example:
只需要将HasSelfPrivateNum.java中的int num=0;提到类里面。那么此时Result:

num的值为:200
num的值为:200

加Synchronized关键字解决问题

我们现在只需要在HasSelfPrivateNum.java里的add(String name)前加synchronized关键字即可。

public class HasSelfPrivateNum {
    int num=0;
    public synchronized  void add(String name){
        if(name.equals("a")){
            num=100;
            System.out.println("a set over");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            num=200;
            System.out.println("b set over");
        }
        System.out.println("num的值为:"+num);
    }
}

Result:

a set over
num的值为:100
b set over
num的值为:200

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

多个对象多个锁

修改一下Run.java代码

public class Run {
    public static void main(String[] args) {
        HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
        HasSelfPrivateNum hasSelfPrivateNum2 = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(hasSelfPrivateNum1);
        ThreadB threadB = new ThreadB(hasSelfPrivateNum2);
        threadA.start();
        threadB.start();
    }
}

这里生成了二个对象,Result:

a set over
b set over
num的值为:200
num的值为:100

二个线程分别访问同一个类中的二个不同实例的相同名称的同步方法,效果却是以异步的方式运行。说明了由于创建了2个对象,就会产生了2把锁

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。

synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象锁的。这也证明了一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

Example:

public class Service {
    synchronized  public void f1(){
        System.out.println("f1");
        f2();
    }
    synchronized  public void f2(){
        System.out.println("f2");
        f3();
    }
    synchronized  public void f3(){
        System.out.println("f3");
    }
}

通过继承一个Thread并在run方法中执行f1();打印结果:

f1
f2
f3

可重入锁也可以支持父子类继承的环境中

public class Fu{
    synchronized  public void fu(){
        System.out.println("fu");
    }
}

Zi类继承Fu

public class Zi extends  Fu{
    public synchronized void zi() {
        System.out.println("zi");
        this.fu();
    }
}

运行代码:

public class Run2 {
    public static void main(String[] args) {
      new Thread(new Runnable() {
          @Override
          public void run() {
              Zi zi = new Zi();
              zi.zi();
          }
      }).start();
    }
}

Result:

zi
fu

这个例子说明,当存在父子类继承关系时,子类是完全可以通过可重入锁调用父类的同步方法的。

出现异常,锁自动释放

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

修改HasSelfPrivateNum.java

public class HasSelfPrivateNum {
    int num=0;
    public synchronized  void add(String name){
        if(name.equals("a")){
            num=100;
            System.out.println("a set over");
            Integer.parseInt("a");//多加了这一行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            num=200;
            System.out.println("b set over");
        }
        System.out.println("num的值为:"+num);
    }
}

Result:

线程ThreadA出现异常并释放锁,线程ThreadB进入方法正常打印,实验结论是出现异常的锁被自动释放了。

同步不具有继承性

同步不可以被继承

public class Main {
    synchronized  public void serviceMethod(){
        System.out.println("int main 下一步 sleep begin threadName="+Thread.currentThread().getName()+
        "time"+System.currentTimeMillis());
        try {
            Thread.sleep(5000);
            System.out.println("int main 下一步 sleep end threadName="+Thread.currentThread().getName()+ "time"+System.currentTimeMillis());
        } catch (InterruptedException e) {
        }
    }
}
public class Sub extends  Main{
      public void serviceMethod(){
        System.out.println("int Sub 下一步 sleep begin threadName="+Thread.currentThread().getName()+
                "time"+System.currentTimeMillis());
        try {
            Thread.sleep(5000);
            System.out.println("int Sub 下一步 sleep end threadName="+Thread.currentThread().getName()+ "time"+System.currentTimeMillis());
        } catch (InterruptedException e) {
        }
        super.serviceMethod();
      }
}
public class Run2 {
    public static void main(String[] args) {
        final Sub sub = new Sub();
      new Thread(new Runnable() {
          @Override
          public void run() {
            sub.serviceMethod();
          }
      }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                sub.serviceMethod();
            }
        }).start();
    }
}

Result:

输出的前二行说明了非同步,也就是没有继承同步性。
可以在子类的方法中加上synchronized此时输出结果

下一篇是介绍synchronized同步语句块

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值