Java,多线程,线程的同步机制

线程的安全问题与线程的同步机制

以火车站买票的问题来举例。假设火车站有100张票,分三个窗口售卖这一百张票。

分别用继承Thread类和实现Runnable接口的方式:

实现Runnable接口:

public class WindowTest
{
    public static void main(String[] args)
    {
        //线程的安全问题
        SaleTicket ss = new SaleTicket();
        Thread t1 = new Thread(ss,"窗口一");
        Thread t2 = new Thread(ss,"窗口二");
        Thread t3 = new Thread(ss,"窗口三");
        t1.start();
        t2.start();
        t3.start();
        //这种情况下,会出现重票错票的问题
    }
}
class SaleTicket implements Runnable
{
     private int ticket = 100;


    public int getTicket()
    {
        return ticket;
    }


    @Override
    public void run()
    {
        while(true)
        {
            if(ticket > 0)
            {
                try
                {
                    Thread.sleep(10);
                } catch (InterruptedException e)
                {
                    e.getStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
                ticket--;
            }
            else
            {
                break;
            }
        }
    }
}

继承Thread类:

public class WindowTest2
{
    public static void main(String[] args)
    {
        //线程的安全问题
        Window w1 = new Window("窗口一");
        Window w2 = new Window("窗口二");
        Window w3 = new Window("窗口三");
        w1.start();
        w2.start();
        w3.start();
        //同样会出现重票错票的问题
    }
}


class Window extends Thread
{
    public Window()
    {


    }
    public Window(String name)
    {
        super(name);
    }


    private static int ticket = 100;
    @Override
    public void run()
    {
        while(true)
        {
            if(ticket > 0)
            {
                try
                {
                    Thread.sleep(10);
                } catch (InterruptedException e)
                {
                    e.getStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
                ticket--;
            }
            else
            {
                break;
            }
        }
    }
}

分别运行可以发现:两中方式卖票时都会出现重票错票的问题。

出现这种现象的原因:

例如,线程一(窗口一)操作ticket的过程中,尚未结束的情况下,其他的线程也参与进来对ticket进行操作。

要解决这种问题,必须保证一个线程(假设是线程一)在操作ticket的过程中,其他线程必须等待,直到线程一操作ticket结束以后,其他线程才可以进来继续操作ticket。

要解决线程的安全问题,就要使用线程的同步机制。

方式一:同步代码块

格式:

synchronized(同步监视器){

//需要被同步的代码

//……

}

说明:

·需要被同步的代码,即为操作共享数据的代码。

·共享数据:即多个线程都需要操作的数据。

·需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待。

·同步监视器,俗称锁,哪个线程获取了锁,哪个线程就能执行需要被同步的代码。

·同步监视器,可以使用任何一个类的对象充当。多个线程(这些线程要执行同一段需要被同步的代码)必须共用同一个同步监视器。

(同步监视器类似于交通信号灯,多个线程共用同一个同步监视器即多个线程看同一个交通信号灯,多个线程都是只有获取到这个锁之后才能执行相应需要被执行的代码)

·实现Runnable的方式创建线程的方式同步监视器使用this最方便,继承Thread类的方式创建线程的方式使用  当前类名.class  最方便。

使用同步代码块后的卖票的代码如下:

实现Runnable接口:

public class WindowTest
{
    public static void main(String[] args)
    {
        //用同步代码块线程的安全问题
        SaleTicket ss = new SaleTicket();
        Thread t1 = new Thread(ss,"窗口一");
        Thread t2 = new Thread(ss,"窗口二");
        Thread t3 = new Thread(ss,"窗口三");
        t1.start();
        t2.start();
        t3.start();
        //这种情况下,会出现重票错票的问题
    }
}
class SaleTicket implements Runnable
{
     private int ticket = 100;
    //Object o = new Object();
    public int getTicket()
    {
        return ticket;
    }


    @Override
    public void run()
    {
        while(true)
        {
            try
            {
                Thread.sleep(10);
            } catch (InterruptedException e)
            {
                e.getStackTrace();
            }
            //synchronized(o){
            //如果多个线程要使用同一段同步的代码,同步监视器中的对象要保证这些线程使用同一个锁。
            synchronized(this)//同步监视器中可以放任何一个唯一对象,只要保证唯一。一般情况下放this最方便。
            {
                if(ticket > 0)
                {
                    try
                    {
                        Thread.sleep(10);//调用sleep代码只是为了更好的显示同步代码块时轮流买票的情况。
                    } catch (InterruptedException e)
                    {
                        e.getStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
                    ticket--;
                }
                else
                {
                    break;
                }
            }
        }
    }
}

继承Thread类:

public class WindowTest2
{
    public static void main(String[] args)
    {
        //线程的安全问题
        Window w1 = new Window("窗口一");
        Window w2 = new Window("窗口二");
        Window w3 = new Window("窗口三");
        w1.start();
        w2.start();
        w3.start();
        //同样会出现重票错票的问题
    }
}

class Window extends Thread
{
    public Window()
    {

    }
    public Window(String name)
    {
        super(name);
    }

    private static int ticket = 100;
    //static Object oo = new Object();
    @Override
    public void run()
    {
        while(true)
        {
            //synchronized (this)//w1,w2,w3使用的并非同一个同步监视器,相当于三个线程看的“交通信号灯”不是同一个。
            //synchronized (oo)
            synchronized (Window.class)//继承Thread类的方式,同步监视器建议使用  当前类名.class
            {
                if(ticket > 0)
                {
                    try
                    {
                        Thread.sleep(100);
                    } catch (InterruptedException e)
                    {
                        e.getStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
                    ticket--;
                }
                else
                {
                    break;
                }
            }
        }
    }
}

方式二:同步方法

如果操作共享数据的代码完整地声明在了一个方法中,那么我们就可以将此方法声明为同步方法。(在方法声明中加上synchronized)

·非静态的同步方法,默认同步监视器为this

·静态的同步方法,默认同步监视器是  当前类名.class  。

具体代码如下:

实现Runnable接口:

public class WindowTest
{
    public static void main(String[] args)
    {
        //线程的安全问题
        //使用同步方法解决实现Runnable接口的方式的线程安全问题
        SaleTicket ss = new SaleTicket();
        Thread t1 = new Thread(ss,"窗口一");
        Thread t2 = new Thread(ss,"窗口二");
        Thread t3 = new Thread(ss,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}
class SaleTicket implements Runnable
{
     private int ticket = 100;


    public int getTicket()
    {
        return ticket;
    }


    @Override
    public void run()
    {
        boolean b = true;//判断ticket是否大于0
        while(b)
        {
            show(b);
        }
    }


    public synchronized void show(boolean b)//此时的同步监视器是this
    {
        if(ticket > 0)
        {
            try
            {
                Thread.sleep(10);
            } catch (InterruptedException e)
            {
                e.getStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
            ticket--;
        }
        else
        {
            b = false;
        }
    }
}

继承Thread类:

public class WindowTest2
{
    public static void main(String[] args)
    {
        //线程的安全问题
        Window w1 = new Window("窗口一");
        Window w2 = new Window("窗口二");
        Window w3 = new Window("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }
}


class Window extends Thread
{
    public Window()
    {


    }
    public Window(String name)
    {
        super(name);
    }


    private static int ticket = 100;
    @Override
    public void run()
    {
        boolean b = true;
        while(b)
        {
            show(b);
        }
    }
    //public synchronized void show(boolean b)//同步监视器还是默认为this,而this在这里三个不同的线程过来时分别指三个对象,不能解决问题
    public static synchronized void show(boolean b)//此时同步监视器:当前类名.class//注意:不是使用静态的情况不要使用此方式,应该继续使用同步代码块的方式
    {
        if(ticket > 0)
        {
            try
            {
                Thread.sleep(10);
            } catch (InterruptedException e)
            {
                e.getStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
            ticket--;
        }
        else
        {
            b = false;
        }
    }
}

synchronized关键字

好处:解决了线程安全问题。

弊端:在操作共享数据指多线程其实是串行执行的,意味着性能低。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二狗mao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值