卖票程序、同步(锁)、同步函数、死锁

卖票示例
class Ticket implements Runnabe
{
private int num=100;//票数
public void run()
{
while(num>0)
{
System.out.println(Thread.currentThread().getName()+”…sale…”+num–);
}
}
}

class TicketDemo
{
public static void main(String [ ] args)
{
Ticket t=new Ticket();
Thread t1=new Thread(t1);
Thread t2=new Thread(t1);
Thread t3=new Thread(t1);
Thread t4=new Thread(t1);
//四个线程并发的卖一个Ticket对象t所持有的100张票
t1.start();
t2.start();
t3.start();
t4.start();
}
}
另一种方法是:继承Thread类,将num声明为static,创建四个Tichet对象。

但是上述的买票程序存在安全问题,例如num=1时,cpu切换到线程t1执行,此时num=1>0;进入循环(还没进行System.out.println()操作);然后cpu切换到线程t2执行,此时的num=1;进入循环;cpu再切换到t1线程执行,不再判断,直接进行System.out.println()操作,完成后num=0;cpu切换到t2线程,打印的num值为0,且完成后num=-1,没有0号票,却卖出了0号票则是有问题的。

出现上述安全隐患的原因:
多个线程在操作共享的数据。当一个线程在执行操作共享数据的多条代码过程中暂停(失去cpu的执行权),其他线程参与了运算,就会导致线程安全问题的产生。
解决办法:将操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以进入参与操作,必须要当前线程把这些代码都执行完毕后,其他线程才可以进入封装块对该共享数据参与运算。
在java中用同步代码块就可以解决这个问题:
同步代码块的格式:
Synchronized(对象)
{
需要被同步的代码;
}
对象是任意类型的对象,如Object类型
Object obj=new Object();
synchronized(obj)
{
需要被同步的代码;
}
class Ticket implements Runnabe
{
private int num=100;
Object obj=new Object();
public void run()
{

   Synchronized(obj)
    {
     while(num>0)
       {
        System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
        }
 }

}
}

Synchronized后面的对象相当于标志位(如0和1,1表示可以进入,0表示不能进入)或者同步锁,当线程t1执行时检测到标志位为1,则进入同步体,然后将标记位置为0;此时cpu切换到线程t2执行,检测发现标志位为0,不能进入同步体;等到cpu再次切换到线程t1,执行完成同步体的代码后,将标志位置为1,cpu切换到其他线程,发现标志位为1时,可以进入。这样就保证了只能有一个线程在同步体中执行。
后面会具体解释为何synchronized后面要接对象?

同步的好处:解决了线程的安全问题
同步的弊端:降低了效率。当有一个线程进入同步体后,即使释放了cpu的执行权,其他线程也无法进入执行,cpu没有执行任何有效代码,一直处于等待状态,直到再次切换到再同步体中的线程。

同步的前提:
必须有多个线程,并使用同一个锁(标志位)

示例
需求:有两个储户到银行存钱,每次存100,共存三次。
下面是一个简单的实现程序,只是为了说明问题,并不是一个好程序。
class Bank
{
private int moneySum;
private Object obj=new Object();
Public void add(int num)
{
synchronized(obj)//或synchronized(Bank.class)
{
moneySum = moneySum +num;
System.out.println(“moneySum =”+ moneySum);
}
}
}

class User implement 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)
{
User c=new User();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
在上述程序中,moneySum为共享数据,如果不加同步:储户1存进100,执行完moneySum = moneySum +num后;cpu切换到储户2,也存进100,执行完moneySum = moneySum +num后;cpu再切换到储户1的线程,执行System.out.println()操作,则moneySum变化了,出现问题。
在程序中,add函数体内只有一个代码块,且是同步代码块,则该函数可以改为同步函数,如下:
public synchronized void add(int num)//同步函数
{
Sum=sum+num;
System.out.println(“sum=”+sum);
}
同步函数的锁是this,即调用该函数的对象,例如上例中的b。
同步函数和同步代码块的区别是:同步函数的锁是固定的this,同步代码块的锁是任意指定的对象。
建议使用同步代码块的形式。

静态同步函数:
public static synchronized void add(int num)//静态同步函数
{
Sum=sum+num;
System.out.println(“sum=”+sum);
}
静态的同步函数使用的锁是:该函数所属的类的Class对象,可以使用:对象 .getClass(),或者:类名.class等方式获取该Class对象。

死锁
死锁的体现形式之一:同步嵌套
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()...if...locka...");
            synchronized(MyLock.locka)
            {
                 System.out.println("Thread.currentThread().getName()...if...lockb...");
            }
        }   
    }
}

}

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();
}

}
上面的程序可能引起死锁:
t1启动后,t1线程执行run方法中的if语句,获得locka锁,进入同步体,然后cpu切换到t2线程;t2线程获得lockb锁后,进入同步体,cpu切换到t1执行;此时,t1线程需要获得lockb锁才能进入嵌套的同步体中执行,而线程t2持有lockb锁,线程t2的同步体未执行完,不会释放lockb锁;线程t1什么也做不了,等待cpu切换到线程t2;cpu切换到线程t2后,t2需要locka锁进入其嵌套的同步体,而线程t1的同步体没有执行完,也不会释放locka锁;两个线程一直处于相持阶段,形成死锁。

因此,编写多线程程序时,要避免死锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值