JAVA 线程安全

线程同步

当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。在恢复执行之后,面临共享变量被其他线程所改变的情况,将会导致数据不一致。因此多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待。程序上称为同步。

public class Counter {
    private int num;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public void add() {
        this.num++;
    }
}
public class Async {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.setNum(0);

        Thread[] threds = new Thread[2];
        for (int i = 0; i < 2; i++) {
            threds[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 2000; j++) {
                        counter.add();
                    }
                }
            });
        }

        threds[0].start();
        threds[1].start();

        try {
            threds[0].join();
            threds[1].join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("num:" + counter.getNum());

    }
}

执行结果:
num:2938 #随机出现数值,并非想要的结果4000

Counter 中的 num 为共享变量,被两个线程同时操作,便出现线程安全问题。对于共享变量不安全问题可以采用同步解决。最常用的方式是对共享变量操作指令加锁,保证一组指令以原子方式执行。即在同一时间点,只能有一个线程拿到锁,操作共享变量,然后释放锁,其他线程再争抢锁。

synchronized

在 Java 语言中,每一个对象都会内置一把锁(监视器锁),这是一种互斥锁,每一时间片只能有一个线程获取该锁,其他线程进入等待,直到原先线程释放锁。 线程可以使用 synchronized 关键字来获取对象上的锁。synchronized 关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。

synchronized 方法

当用 synchronized 修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

public class Counter {
    private int num;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public synchronized void add() {
        this.num++;
    }
}
public class Sync {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.setNum(0);

        Thread[] threds = new Thread[2];
        for (int i = 0; i < 2; i++) {
            threds[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 2000; j++) {
                        counter.add();
                    }
                }
            });
        }

        threds[0].start();
        threds[1].start();

        try {
            threds[0].join();
            threds[1].join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("num:" + counter.getNum());

    }
}

执行结果:
num:4000

把 Counter 计数程序中 add 方法添加 synchronized 关健字,便完成共有变量 num 的读写同步。synchronized 修饰普通类方法,那么锁住的是 this,即谁调用该方法便锁住谁。以上例子锁住的是 Counter 实例。 需要注意的是:synchronized 锁住的必须是同一个对象。

synchronized 代码块

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

public class Counter {
    private int num;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public void add() {
    	synchronized (this){
    		this.num++;
		}
    }
}
public class Sync {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.setNum(0);

        Thread[] threds = new Thread[2];
        for (int i = 0; i < 2; i++) {
            threds[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 2000; j++) {
                        counter.add();
                    }
                }
            });
        }

        threds[0].start();
        threds[1].start();

        try {
            threds[0].join();
            threds[1].join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("num:" + counter.getNum());

    }
}

执行结果:
num:4000

在代码段 num++ 中添加 synchronized 修饰,便完成共有变量 num 的读写同步。synchronized 修饰代码块,那么锁住的是 synchronized 修饰的对象,即在括号中的值。

synchronized 静态方法

public class Counter {
    private static int num;

    public static int getNum() {
        return num;
    }

    public synchronized static void add() {
        num++;
    }
}
public class Async {
    public static void main(String[] args) {
        Thread[] threds = new Thread[2];
        for (int i = 0; i < 2; i++) {
            threds[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 2000; j++) {
                        Counter.add();
                    }
                }
            });
        }

        threds[0].start();
        threds[1].start();

        try {
            threds[0].join();
            threds[1].join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("num:" + Counter.getNum());

    }
}
执行结果:
num:4000

当 synchronized 修饰 static 方法,它锁住的是该类的 Class 对象,而不是某一个具体对象,在程序执行过程中,类的Class对象只有一份,同样可以达到同步效果。

synchronized 何时释放锁

synchronized 内部机制并不需要使用者关心何时获取锁何时释放锁,但是释放锁还是值得一提,总共有三种情况:

  1. 占有锁的线程执行完了该代码块,然后释放对锁的占有;
  2. 占有锁线程执行发生异常,此时JVM会让线程自动释放锁;
  3. 占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等。

明白释放锁,那么便可以知道 synchronized 的使用局限性:

  1. 占有锁的线程如遇IO读写阻塞,便无法释放锁 ,其他线程都在等待;
  2. 不能自定义某些线程需要拿锁操作共享变量,某些线程不需要锁操作变量;如某共享变量写操作需要同步,读不需要同步;
  3. 使用者不能感知是否获取锁,是否释放锁。

ReentrantLock

在 JavaSE5.0 中新增了一个 java.util.concurrent 包来支持同步。ReentrantLock 类是可重入、互斥、实现了Lock接口的锁, 它与使用 synchronized 方法和快具有相同的基本行为和语义,并且扩展了其能力。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ATM {
    private int money;

    Lock lock = new ReentrantLock();

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public void add() {
        try {
            lock.lock();
            this.money ++;
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


}

public class TestThread {

    static ATM atm = new ATM();

    public static void main(String[] args) throws InterruptedException {

        atm.setMoney(0);

        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 2000000; i++) {
                    atm.add();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 2000000; i++) {
                    atm.add();
                }
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("atm现在的金额是:" + atm.getMoney());
    }
}

执行结果:
atm现在的金额是:4000000
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值