栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 线程安全(重点)0627

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 线程安全(重点)0627

1.什么情况下数据在多线程并发下会存在安全问题?

三个条件:

1)多线程并发 

2)  有共享数据

3) 共享数据有修改的行为

满足以上三个条件后,就会存在线程安全问题

例如,多线程账户对同一个账户取款

 

2. 怎么解决线程安全问题?

线程排队执行。(不能并发)

用排队执行就解决线程安全问题。

这种机制被称为:线程同步机制。

这就是并发带来的问题,有可能会导致数据不安全。

线程同步就是线程排队,线程排队会牺牲一部分效率。

3.     同步编程模型 :线程排队执行。     同步就是排队

        异步编程模型:多线程并发(效率较高)。    异步就是并发

4. 举例 银行取款

1)定义账户类   Account

package leetcode0606;

public class Account {
    //账户
    private String actno;
    //余额
    private double balance;

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款的方法
    // 多线程并发这个方法,t1和t2 是两个栈,两个栈操作堆中的同一个对象
    public void withdraw(double money){
        //取款之前的余额
        double before = this.getBalance();
        //取款之后的余额
        double after = before - money;
        //更新余额
        // 若来不及更新,那么就会产生问题
        // 利用sleep模拟网络延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }

}

2)”取款的线程” 类  AccountThread

package leetcode0606;

public class AccountThread extends Thread{
    //如何做到多线程共享同一个账户
    private Account act;
    public AccountThread(Account act){
        this.act = act;
    }
    @Override
    public void run() {
        // run方法的执行表示取款操作
        //设置取款的金额
        double money = 5000;
        // 进行取款
        // 多线程并发这个方法
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName()+"账户"+act.getActno()+"取款"+money+",余额"+act.getBalance());
    }
}

3)测试类  Test

public class Test {
    public static void main(String[] args) {
        // 创建账户对象
        Account act = new Account("act-001",10000);
        // 创建两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        t2.start();
    }
}

5.利用线程同步机制解决线程安全问题

synchronized(){

                //线程同步代码块

}

synchronized 后面的参数很重要,必须是多线程的共享数据,才能达到排队的效果。

()中写什么?那要看你想让谁排队

如果有t1 t2 t3 t4 t5 五个线程,如果你想让前三个排队,那么共享的对象一定是前三个线程共享的,后两个不共享的。

此问题 中的共享对象是账户对象,所以写this

    public void withdraw(double money){
        //以下的代码必须是线程排队的,不能并发
        synchronized(this){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    }

t1账户act-001取款5000.0,余额5000.0
t2账户act-001取款5000.0,余额0.0

6. 对synchronized的理解

1)争抢对象锁

这个线程能否运行代码块,就看能不能抢到锁(每个对象只有一把锁)

一种锁机制(排他锁)。一个线程遇到synchronized就会去找这个共享对象的锁(每一个java对象都有一把锁),一个线程会占有这把锁,直到执行完同步代码块(执行完后归还锁)。另外一个线程也会执行这段代码,也会去找锁,但是没有找到,所有只能等待。类似于茅坑拉屎关门上锁的例子。

 

另一种写法:

package leetcode0606.ThreadSafe1;

public class Account {
    //账户
    private String actno;
    //余额
    private double balance;

    Object obj = new Object();   // 实例变量. 由于Account是共享的,所以对象中的obj也是共享的,也是只有一份

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public void withdraw(double money){
        //以下的代码必须是线程排队的,不能并发
//        synchronized(this){
        synchronized(obj){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    }
}

 

挖坑:

由于obj2是局部变量,不是共享变量,所以还是会有安全问题

public void withdraw(double money){
        Object obj2 = new Object();
        //以下的代码必须是线程排队的,不能并发
//        synchronized(this){
        synchronized(obj2){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }

使用字符串常量:

synchronized("abc"){}

字符串常量池则存在于方法区,故是共享数据

参考:彻底搞懂String:字符串常量池_weixin_42073629的博客-CSDN博客_java string 常量池

但是这样所有的线程都要排队。

2)争抢类锁

7.java 的三大变量

实例变量: 堆

静态变量 : 方法区

局部变量: 在栈中

以上三大变量中,局部变量没有线程安全问题,不会被共享。

常量没有线程安全问题,因为不可修改。

8. 同步代码块的范围:

同步代码块越小,效率越高。

9.作用在实例方法上

    public synchronized void withdraw(double money){
        //取款之前的余额
        double before = this.getBalance();
        //取款之后的余额
        double after = before - money;
        //更新余额
        // 若来不及更新,那么就会产生问题
        // 利用sleep模拟网络延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }

出现在实例方法上,锁的是this,这种方式不灵活。

整个方法都需要同步,效率较低。

但是代码少。。。。

================================================================

如果使用局部变量的话,建议使用StringBuilder. 

StringBuffer 底层的实例方法都是加了synchronized的。

ArrayList 非线程安全

Vector 线程安全

HashMap HashSet 非线程安全

Hashtable 线程安全

加了synchronized关键字,就算不需要等待,也要去锁池里看一下,很麻烦的,所以拖慢了效率。

10 . 三种写法

代码块

实例方法

在静态方法上:找类锁,类锁只有一把,与对象数量无关。类锁保证了静态变量的安全,前两类保证了实例变量的安全。

11.面试题1 : doOther方法需要等待doSome结束在执行吗?

不需要,因为doOther方法没有被synchronized修饰。

package leetcode0606.exam;

public class Exam1 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        MyThread myThread1 = new MyThread(myClass);
        MyThread myThread2 = new MyThread(myClass);
        myThread1.setName("t1");
        myThread2.setName("t2");
        myThread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}
class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}


doSome begin
doOther begin
doOther over

11.面试题1 : doOther方法需要等待doSome结束在执行吗?doOther加了synchronized

需要。

package leetcode0606.exam2;

public class Exam2 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        MyThread myThread1 = new MyThread(myClass);
        MyThread myThread2 = new MyThread(myClass);
        myThread1.setName("t1");
        myThread2.setName("t2");
        myThread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread2.start();
    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}
class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
doSome begin
doSome over
doOther begin
doOther over

13.面试题3

        MyClass myClass = new MyClass();
        MyClass myClass1 = new MyClass();
        MyThread myThread1 = new MyThread(myClass);
        MyThread myThread2 = new MyThread(myClass1);

并发了。

14.面试题4: 静态方法加synchronized

需要等

class MyClass{
    public synchronized static void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized static void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");

doSome begin
doSome over
doOther begin
doOther over

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/996185.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号