synchronized关键字

“线程安全”是一个老生常谈的话题,但是对线程安全比较规范的定义却不是一件容易的事。
Brian Goetz是这样描述线程安全的:

当多个线程同时访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者在调用方法进行其它的协调操作,调用这个对象的行为都可以获得正确的结果,那么就称这个对象是安全的。

简单说:但多个线程同时访问同一个类(对象、方法)的时候,可以得到预期的结果,那么这就是线程安全的。

看一段代码:

public class Xiaoping {
     //定义一个共享的变量
    public static  int num = 0;
  
    public static void main(String[] args) {
        //创建三个线程,来进行变量的累加
        T1 t1 = new T1();
        T1 t2 = new T1();
        T1 t3 = new T1();
        //启动线程
        t1.start();
        t2.start();
        t3.start();
        try {
        //三个线程结束后,打印结果
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印结果
        System.out.println(Xiaoping.num);
    }
}
//自定义一个线程类
class T1 extends  Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++){
            Xiaoping.num ++;
        }
    }
}

//分析
按照我们的预期,每个线程进行10000的累加,那么三个线程执行结束后就是30000
但是:实际结果却有可能不是30000,这就表明该线程是不安全的!!!

引发线程安全问题的主要原因:
1.存在共享数据
2.多线程同时操作共享数据

线程安全解决思路

我们既然知道线程安全问题的诱因是1.存在共享数据2.多个线程同时访问共享数据。那么我们就可以从这个思路出发

保证同一时刻只有一个线程去操作共享变量:当一个线程去操作共享数据时,其它的线程必须等待至该线程执行结束后才能执行。这就是我们常说的互斥锁!互斥锁简单说就是互斥访问的锁,它可以做到互斥访问,即一个线程操作共享数据时,其它线程是不能操作共享数据的,只有该线程释放锁之后,其它线程才可以访问共享数据。

Synchronized关键字


Synchronized关键字可以用于解决线程安全。它可以保证同一时刻只有一个线程去执行方法或者代码块(主要是线程中共享变量的操作)。还有一个作用就是可以保证线程变化对其它线程的可见性(共享变量的可见性)完全可以替代volatile关键字

问题?
上述存在线程安全问题的代码怎么修改

public static volatile int num = 0;

千万注意,上述的修改是不能保证线程安全的,volatile关键字不能保证线程安全的!!!

我们可以使用synchronized关键字来进行线程安全问题的解决

 @Override
    public synchronized void run() { //使用synchronized关键字来避免线程安全问题
        for (int i = 0; i < 10000; i++){
            Xiaoping.num ++;
        }
    }

synchronized关键字的使用

1.修饰实例方法,作用于当前实例,调用实例方法时首先获取该实例的锁

2.修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁

3.修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁

代码分别对synchronized关键字使用的演示
1.synchronized关键字修饰实例方法,这里的实例方法是实例对象的方法,而不是静态方法

public class SynchronizedTest {
    public static void main(String[] args) {
       Person person = new Person();
       Demo d1 = new Demo(person);
       Demo d2 = new Demo(person);
       d1.start();
       d2.start();
        try {
            d1.join();
            d2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(person.num);
    }
}
class Demo extends Thread{

    private Person person;

    public Demo(){}

    public Demo(Person person){
        this.person = person;
    }

    @Override
    public void run() {
        for(int i = 0; i < 10000; i++){
            this.person.add();
        }
    }
}
class Person {
    int num = 0;
    //synchronized修饰的实例方法
    public synchronized void add(){
        num ++;
    }
}

synchronized修饰实例方法注意事项:

1.多个线程操作的是同一个实例
2.同一个实例的多个实例方法用synchronized关键字修饰

2.synchronized关键字静态方法,这里的锁对象就是类的Class对象

public class SynchronizedTest2 {
    public static void main(String[] args) {
        T t1 = new T();
        T t2 = new T();
        T t3 = new T();
        t1.start();
        t2.start();
        t3.start();
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Student.num);
    }
}
class Student{
    static int num = 0;
    //使用synchronized关键字修饰静态方法
    public  static synchronized void add(){
        num ++;
    }
}
class T extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Student.add();
        }
    }
}

修饰静态方法时,此时的锁对象是Class对象
上述可以这样转换:

class Student{
    static int num = 0;
    public  static void add(){
        synchronized (Student.class){
            num ++;
        }
    }
}

3.synchronized关键字修饰同步代码块

我们知道synchronized关键字可以修饰静态方法和实例方法。但是存在一个问题就是他么都是在执行方法前获取指定对象的锁,那么存在一个问题就是有的方法方法体很大,内容比较多,而需要保证线程安全的代码又比较少,这样是比较浪费性能的,因此我们可以使用同步代码块,来对同步代码快中的内容进行加锁。

在演示代码1的基础上使用同步代码块保证线程安全

public class SynchronizedTest {
    public static void main(String[] args) {
       Person person = new Person();
       Demo d1 = new Demo(person);
       Demo d2 = new Demo(person);
       d1.start();
       d2.start();
        try {
            d1.join();
            d2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(person.num);
    }
}
class Demo extends Thread{
    private Person person;
    public Demo(){}
    public Demo(Person person){
        this.person = person;
    }
    @Override
    public void run() {
        for(int i = 0; i < 10000; i++){
            this.person.add();
        }
    }
}
class Person {
    int  num = 0;
    public void add(){
        synchronized (this){ //这里使用的是同步代码快
            num ++;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值