JavaSE笔记7.3-多线程-多线程的安全问题

一. 引入

class Ticket implements Runnable
{
    private int num=100;
    public void run()
    {
        while(true)
        {
            if(num>0)
            {
                try{
                    Thread.sleep(10);
                }
                catch (Exception e){}
                System.out.println(Thread.currentThread().getName()+"...sale:"+num--);
            }
        }
    }
}
class TicketDemo
{
    public static void main(String[] args)
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

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

运行结果是:
在这里插入图片描述
发现:多线程的运行出现了安全问题,打印出0、-1、-2等错票。

二. 问题总结

多线程的运行出现了安全问题

1. 问题的原因

当多条语句在操作同一个线程共享数据时(写操作),一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致了共享数据的错误。

2. 解决方法

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中其它线程不可以参与执行。
(即使其它线程拿到了执行权,也不能进行操作)

3. Java的解决方法

同步代码块

synchronized(对象)
{
    需要被同步的代码//与操作共享数据相关的代码
}
class Ticket implements Runnable
{
    private int num=100;
    Object obj=new Object();
    public void run()
    {
        while(true)
        {
            synchronized(obj)
            {
                if(num>0)
                {
                    try{Thread.sleep(10);}
                    catch(Exception e){}
                    System.out.println(Thread.currentThread().getName()+"...sale:"+num--);
                }
            }
        }
    }
}
class TicketDemo2
{
    public static void main(String[] args)
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

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

运行结果是:
在这里插入图片描述

三. 同步代码块

对象如同锁
持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取了CPU的执行权也进不去,因为没有锁。

1. 同步的前提

(1)必须要有2个或者2个以上的线程
(2)必须是多个线程使用同一个锁
(3)必须保证同步中只能有一个线程在运行

2. 好处

解决了多线程的安全问题

3. 弊端

多个线程需要判断锁,较为消耗资源

4. 练习

需求:银行有一个金库,有两个储户分别存300元,每次存100元,存3次
目的:该程序是否有安全问题,如果有如何解决
如何找问题
(1)明确哪些代码是多线程运行的代码
(2)明确共享数据
(3)明确多线程运行代码中哪些语句是操作共享数据的
问题

class Bank{
    private int sum;
    public void add(int n){
        sum=sum+n;
        try{Thread.sleep(10);}
        catch(Exception e){}
        System.out.println("sum= "+sum);
    }
}
class Cus implements Runnable{
    private Bank b=new Bank();
    public void run(){
        for(int x=0;x<3;x++){
            b.add(100);
        }
    }
}
class BankDemo{
    public static void main(String[] args){
        Cus c=new Cus();
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();
    }
}

运行结果是:
在这里插入图片描述
解决问题

class Bank{
    private int sum;
    Object obj=new Object();
    public void add(int n){
        //同步代码块
        synchronized (obj){
            sum=sum+n;
            try{Thread.sleep(10);}
            catch(Exception e){}
            System.out.println("sum= "+sum);
        }
    }
}
class Cus implements Runnable{
    private Bank b=new Bank();
    public void run(){
        for(int x=0;x<3;x++){
            b.add(100);
        }
    }
}
class BankDemo{
    public static void main(String[] args){
        Cus c=new Cus();
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();
    }
}

运行结果是:
在这里插入图片描述

四. 同步函数

1. 例子
class Bank{
    private int sum;
 
    //同步函数
    public synchronized void add(int n){
            sum=sum+n;
            try{Thread.sleep(10);}
            catch(Exception e){}
            System.out.println("sum= "+sum);
    }
}
class Cus implements Runnable{
    private Bank b=new Bank();
    public void run(){
        for(int x=0;x<3;x++){
            b.add(100);
        }
    }
}
class BankDemo{
    public static void main(String[] args){
        Cus c=new Cus();
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();
    }
}

运行结果是:
在这里插入图片描述

class Ticket implements Runnable
{
    private int num=100;
    Object obj=new Object();
    public void run()
    {
        while(true)
        {
               show();
        }
    }
    public synchronized void show(){
        if(num>0)
        {
            try{Thread.sleep(10);}
            catch(Exception e){}
            System.out.println(Thread.currentThread().getName()+"...sale:"+num--);
        }
    }
}
class TicketDemo2
{
    public static void main(String[] args)
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

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

运行结果是:
在这里插入图片描述

2. 同步函数的锁是this

函数需要被对象调用,那么函数都有一个所属对象的引用,就是this
所以同步函数使用的锁是this

(1)同步函数的锁不是Object类对象

使用2个线程来卖票
一个线程在同步代码块中,一个线程在同步函数中
都在执行卖票动作

class Ticket implements Runnable{
    private int num=500;
    Object obj=new Object();
    boolean flag=true;
    public void run(){
        if(flag){
            while(true){
                //同步代码块
                synchronized (obj){
                    if(num>0){
                        try{Thread.sleep(10);}
                        catch (Exception e){}
                        System.out.println(Thread.currentThread().getName()+"...code: "+num--);
                    }
                }
            }
        }
        else{
            while (true){
                //同步函数
                show();
            }
        }
    }
    public synchronized void show() {
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + "...show: " + num--);
        }
    }
}
class ThisLockDemo{
    public static void main(String[] args){
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        t1.start();
        //在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
        try{Thread.sleep(10);}
        catch(Exception e){}

        t.flag=false;
        t2.start();
    }
}

运行结果是:出现了错票0
在这里插入图片描述
原因
同步的前提出问题了;
两个线程不是使用同一个锁,因此同步函数的锁不是Object类对象

(2)同步函数的锁是this

将同步代码块的对象换成this进行验证

class Ticket implements Runnable{
    private int num=500;
    Object obj=new Object();
    boolean flag=true;
    public void run(){
        if(flag){
            while(true){
                //同步代码块
                //将同步代码块的对象换成this
                synchronized (this){
                    if(num>0){
                        try{Thread.sleep(10);}
                        catch (Exception e){}
                        System.out.println(Thread.currentThread().getName()+"...code: "+num--);
                    }
                }
            }
        }
        else{
            while (true){
                //同步函数
                show();
            }
        }
    }
    public synchronized void show() {
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + "...show: " + num--);
        }
    }
}
class ThisLockDemo{
    public static void main(String[] args){
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        t1.start();
        //在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
        try{Thread.sleep(10);}
        catch(Exception e){}

        t.flag=false;
        t2.start();
    }
}

运行结果是:没有出现错票
在这里插入图片描述
结论:同步函数的锁是this

五. 静态同步函数的锁是class对象

1. 静态同步函数的锁不是this
class Ticket implements Runnable{
    private static int num=500;
    Object obj=new Object();
    boolean flag=true;
    public void run(){
        if(flag){
            while(true){
                //同步代码块
                //将同步代码块的对象换成this
                synchronized (this){
                    if(num>0){
                        try{Thread.sleep(10);}
                        catch (Exception e){}
                        System.out.println(Thread.currentThread().getName()+"...code: "+num--);
                    }
                }
            }
        }
        else{
            while (true){
                //静态同步函数
                show();
            }
        }
    }

    //静态同步函数
    public static synchronized void show() {
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + "...show: " + num--);
        }
    }
}
class StaticMethodDemo{
    public static void main(String[] args){
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        t1.start();
        //在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
        try{Thread.sleep(10);}
        catch(Exception e){}

        t.flag=false;
        t2.start();
    }
}

运行结果是:出现了错票0
在这里插入图片描述
原因
同步的前提出问题了;两个线程不是使用同一个锁,因此静态同步函数的锁不是this
因为静态方法中不可以定义this

2. 静态同步函数的锁是class对象

猜想
静态方法进内存的时候没有对象,是由类调用的。
类进内存有对象,类要先封装成class类对象(字节码类对象);
验证:将同步代码块的对象换成Ticket.class

class Ticket implements Runnable{
    private static int num=500;
    Object obj=new Object();
    boolean flag=true;
    public void run(){
        if(flag){
            while(true){
                //同步代码块
                //将同步代码块的对象换成Ticket.class
                synchronized (Ticket.class){
                    if(num>0){
                        try{Thread.sleep(10);}
                        catch (Exception e){}
                        System.out.println(Thread.currentThread().getName()+"...code: "+num--);
                    }
                }
            }
        }
        else{
            while (true){
                //静态同步函数
                show();
            }
        }
    }

    //静态同步函数
    public static synchronized void show() {
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + "...show: " + num--);
        }
    }
}
class StaticMethodDemo{
    public static void main(String[] args){
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        t1.start();
        //在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
        try{Thread.sleep(10);}
        catch(Exception e){}

        t.flag=false;
        t2.start();
    }
}

运行结果是:没有出现错票
在这里插入图片描述
结论
静态同步函数使用的锁是该方法所在类的字节码文件对象,类名.class

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值