多线程(二)--线程同步

本文探讨了线程安全问题的产生原因及解决方案,并通过售票系统等实例详细讲解了同步代码块与同步方法的应用。此外,还介绍了如何避免多线程环境下的死锁现象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程安全问题

线程安全问题产生的原因:
1,多个线程在操作共享的数据;
2,操作共享数据的线程代码有多条。
即当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
例一:实际开发中,以火车票售票为例,假设共有10张票,有四个窗口(四个线程)同时在卖票:

class Ticket implements Runnable//extends Thread
{
    private int num=10;
    Object obj=new Object();    
    public void run()
    {
        while(true)
        {
                if(num>0)
                {
                    try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
                    {
                        Thread.sleep(10);
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"...tickets ..."+num--);
                }               
            }       
    }
}

class ThreadDemo
{
    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();

    }
}

输出结果

Thread-2...tickets ...10
Thread-1...tickets ...9
Thread-0...tickets ...8
Thread-3...tickets ...7
Thread-2...tickets ...6
Thread-1...tickets ...5
Thread-0...tickets ...4
Thread-3...tickets ...3
Thread-2...tickets ...2
Thread-1...tickets ...1
Thread-0...tickets ...0
Thread-3...tickets ...-1
Thread-2...tickets ...-2

出现了负的票数,显然这是不被允许的。
线程安全问题来源于两个线程同时存取单一对象的数据。
解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。

同步代码块的格式:
synchronized(对象)//对象可以简单理解为标志位,是对象锁,同步锁
{
需要被同步的代码;
}

同步的好处:解决了线程的安全问题;
同步的弊端:相对地降低了效率,因为同步外的线程都会判断同步锁;
同步的前提:同步中必须有多个线程并使用同一个锁。
例二:加同步代码后:

class Ticket implements Runnable//extends Thread
{
    private int num=10;
    Object obj=new Object();

    public void run()
    {
        while(true)
        {
            synchronized(obj)
            {
                if(num>0)
                {
                    try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
                    {
                        Thread.sleep(20);
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"...sale ..."+num--);
                }

            }

        }
    }
}

class ThreadDemo
{
    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();

    }
}

这样的结果就不会出现负数的票数了。

同步方法:

例三:假设有两个储户(两个线程)到银行去存钱,一次存100,各存三次。

class Bank
{
    private int sum;
    //private Object obj=new Object();
    public synchronized void add(int num)//同步函数
    {
    //  synchronized(obj)
    //  {
            sum=sum+num;
            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 ThreadDemo
{
    public static void main(String[] args)
    {
        Cus c=new Cus();
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.start();
        t2.start();
    }
}

输出结果:

sum=100
sum=200
sum=300
sum=400
sum=500
sum=600

同步函数使用的锁是this。
同步函数和同步代码块的区别:同步函数的锁是固定的this,同步代码块的锁是任意的对象。
建议使用同步代码块。
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前 类名.class 表示。
例四:静态的同步函数


class Ticket implements Runnable//extends Thread
{
    private static int num=100;
    //Object obj=new Object();
    boolean flag=true;

    public void run()
    {
    //  System.out.println("this:"+this);
        if(flag)
            while(true)
            {
                synchronized(this.getClass())//(Ticket.class  -》类名.class这个是静态方法里的属性)
                {
                    if(num>0)
                    {
                        try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
                        {
                            Thread.sleep(10);
                        }
                        catch(InterruptedException e)
                        {
                            //应该有处理方法
                        }
                        System.out.println(Thread.currentThread().getName()+"...obj ..."+num--);
                    }
                }
            }
            else
                while(true)
                    this.show();
    }
    public static synchronized void show()//静态方法当中没有this对象,静态同步方法的对象锁是字节码文件对象(this.getClass())
    {
        if(num>0)
        {
            try//run方法是接口的方法,没有抛出异常,所以覆盖的时候也不能抛出异常,只能catch
            {
                Thread.sleep(10);
            }
            catch(InterruptedException e)
            {
                //应该有处理方法
            }
            System.out.println(Thread.currentThread().getName()+"...function ..."+num--);
        }
    }   
}

class ThreadDemo
{
    public static void main(String[] args)
    {
        Ticket t=new Ticket();//创建一个线程对象

        //class clazz=t.getClass();
        //class clazz=Ticket.class();

        //System.out.println("this:"+this);
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);

        t1.start();
        try{Thread.sleep(10);}catch(InterruptedException e){}
        t.flag=false;
        t2.start();
    }
}

单例模式中的多线程问题

先来复习一下单例模式,单例模式有两种形式,一种是饿汉模式:

class Single
{
    private static final Single s=new Single();
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }
}

这种方式的getInstance()方法中由于函数中只有一句语句,所以在多线程使用时不会出现线程安全问题。
懒汉模式:

class Single
{
    private static Single s=null;
    private Single(){}
    public static Single getInstance()
    {
            if(s==null)
                s=new Single();
            return s;
    }
}

这种方式的getInstance()方法中由于函数中有多句语句,所以在多线程使用时会出现线程安全问题。必须加入同步。

class Single
{
    private static Single s=null;
    private Single(){}
    public static Single getInstance()//如果用同步函数的话每一次进程进来都要判断同步锁,效率降低
    {
        if(s==null)//多加一次判断就可以解决效率问题
        {
            synchronized(Single.class)
            {
                if(s==null)
                    s=new Single();
                return s;
            }
        }
    }
}

死锁

死锁的常见形式之一就是同步的嵌套。
还有一种情况发生在wait()的使用时将所有的线程都冻结了,而没有线程来唤醒。
例五:死锁

class Ticket implements Runnable
{
    private int num=100;
    Object obj=new Object();
    boolean flag=true;
    public void run()
    {
        if(flag)
            while(true)
            {
                synchronized(obj)
                {
                    show();
                }
            }
            else
                while(true)
                    this.show();
    }
    public synchronized void show()
    {
        synchronized(obj)
        {
            if(num>0)
            {
                try
                {
                    Thread.sleep(10);
                }
                catch(InterruptedException e)
                {
                    //应该有处理方法
                }
                System.out.println(Thread.currentThread().getName()+"...function ..."+num--);
            }
        }
    }

}

class DeadLockDemo
{
    public static void main(String[] args)
    {
        Ticket t=new Ticket();//创建一个线程对象

        Thread t1=new Thread(t);
        Thread t2=new Thread(t);

        t1.start();
        try{Thread.sleep(10);}catch(InterruptedException e){}
        t.flag=false;
        t2.start();
    }
}

在这个例子中,在同步代码块中,外层的锁是obj,进入同步代码块后执行的show()方法是同步方法,持有锁this。而在同步方法show()中,外层持有this锁,里面的同步代码块持有obj锁。这两个锁互相嵌套。当同步代码块中持有obj锁,进入代码想拿到this锁,而此时show方法刚好持有this锁,进入代码想拿到obj锁时,两方会相持不下,就会发生死锁。
例六:死锁二

class Test implements Runnable
{
    private boolean flag;
    Test(boolean flag)
    {
        this.flag=flag;
    }
    public void run()
    {
        if(flag)
        {
            synchronized(MyLock.locka)
            {
                System.out.println(Thread.currentThread().getName()+"...if locka");
                synchronized(MyLock.lockb)
                {
                    System.out.println(Thread.currentThread().getName()+"...if lockb");

                }
            }

        }
        else
        {
            synchronized(MyLock.lockb)
            {
                System.out.println(Thread.currentThread().getName()+"...else lockb");
                synchronized(MyLock.locka)
                {
                    System.out.println(Thread.currentThread().getName()+"...else locka");
                }
            }

        }
    }
}

class MyLock
{
    public static final Object locka=new Object();
    public static final Object lockb=new Object();
}
class DeadLockTest
{
    public static void main(String[] args)
    {
        Test a=new Test(true);
        Test b=new Test(false);

        Thread t1=new Thread(a);
        Thread t2=new Thread(b);

        t1.start();
        t2.start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值