黑马程序员_多线程(2)

本文介绍了如何使用多线程实现一个卖票系统,并详细阐述了在多线程环境下可能出现的安全问题及其解决方案。通过实例演示了同步代码块的使用,以确保共享数据的一致性和正确性。

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

 -------  android培训 java培训 、期待与您交流! ----------

用多线程做一个多窗口卖票系统,并为多线程的安全问题进行解决。

/*
    需求:
        简单的卖票程序,多个窗口卖票。
    思路:
        票是一个共享数据被多个窗口所操作。
        窗口是一个线程,多个窗口应该是一个多线程。
    步骤:
        票,是共享数据,应该被static修饰起来。
        多窗口,是多线程,应该创建多个线程来操作票。
    */
class Ticket implements Runnable{
    //private Object obj = new Object();
    //定义票数据,因为创建的是一个对象资源,所以保证了数据的共享,不用static也可以
    private int tick =10;  
    public void run(){
        while(tick > 0){
                try{
                    Thread.sleep(10); //经过此处线程休眠10秒
                } catch(Exception e){
                }
                System.out.println(Thread.currentThread().getName()+"卖第"+tick--+"票");
        }
    }
}
class Test{
    public static void main(String[] args){
    /* 这时要设置线程的名字的话,就要用Thread类的方法了,因为Runnable是
    父接口,而设置线程的方法是在Thread类中的,所以可以直接用Thread对象调用。
    */
    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.setName("1号窗口");
    t2.setName("2号窗口");
    t3.setName("3号窗口");
    t4.setName("4号窗口");
    t1.start();
    t2.start();
    t3.start();
    t4.start();
    //创建线程对象,把资源当成参数进行传递(保证了资源的唯一),调用Thread类的start()方法。
    }
}
其运行结果为:
                
    由结果可知最后几行卖出的票是0或者-1,这种现象是不应该出现的。运行界过之所以出现上述问题,是因为多线程在售票时出现了安全问题。
    问题的原因:
                当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分(CPU就切换到另外的线程去),还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

       解决办法:   

               对多条操作共享数据的语句,让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。必须保证同步中只能有一个线程在运行。

      为了实现这种情况,java提供了同步机制。当多个线程使用同一个共享资源时,可以讲处理共享资源的代码放置在一个代码块中,使用synchronized关键字来修饰,被称之为同步代码块,其语法格式如下:

       synchronized(唯一对象){

             操作共享的资源代码块

      }

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

     好处:解决了多线程的安全问题。
     弊端:多个线程需要判断锁,较为消耗资源,

    同步的前提:

         1,必须要有两个或者两个以上的线程。

         2,必须是多个线程使用同一个锁。

   同步函数

         所谓的同步函数就是在函数的返回值前面加一个synchronized关键字就是同步函数了。

   使用同步函数注意事项:

        一定要明确哪个代码是需要进行同步,如果同步函数中的代码都是需要同步的,就可以使用同步函数。

    疑问:

       同步代码块的锁死自己定义的任意类型的对象,那么同步方法是否也存在锁?如果有,它的锁是什么?答案是肯定的,同步方法也有锁,它的锁就是当前调用这个方法的对象,也就是this指向的说对象。这样做的好处是,同步方法被所有线程所共享,方法所在的对象相对于所有的线程来说是唯一的,从而保证了锁的唯一性,当一个线程执行该方法时,其他的线程就不能进入该方法中,直到这性格线程执行完该方法为止,从而达到了线程同步的效果。

     有时候需要同步的方法是静态方法,静态方法不需要创建对象就可以直接用”类名.方法名()“的方式调用。这时,如果不创建对象,那么这个静态同步方法的锁是什么?java中静态方法的锁是该方法的所在类的class对象,该对象可以直接用”类名.class“的方式获取。

死锁问题:

        由于线程同步代码中可能嵌套同步,最容易导致的问题就是死锁。程序就停在那里不动了。我们作为程序员,应该尽量避免死锁的出现。

   死锁代码:

      class MyLock {
          public static Object locka = new Object();
           public static Object lockb = new Object();
      }
      class DeadLockTest implements Runnable {
          private boolean flag;
          DeadLockTest(boolean flag) {
              this.flag = flag;
          }
          public void run() {
              if (flag) {
                  while(true) {
                      synchronized (MyLock.locka) {
                          System.out.println(Thread.currentThread().getName() +"...if locka ");
                          synchronized (MyLock.lockb) {
                              System.out.println(Thread.currentThread().getName() +"..if lockb");
                          }
                       }
                  }
              }  else {
                  while (true) {
                      synchronized (MyLock.lockb) {
                          System.out.println(Thread.currentThread().getName() +"..else lockb");
                          synchronized(MyLock.locka) {
                              System.out.println(Thread.currentThread().getName() +".....else locka");
                          }
                      }
                  }
              }
          }
     }
      class Test {
          public static void main(String[] args) {
              Thread t1 = new Thread(new DeadLockTest(true));
              Thread t2 = new Thread(new DeadLockTest(false));
              t1.start();
              t2.start();
         }
     }

    其结果是:

        
    该例子中,两个线程都需要对方所占用的锁,但是都无法释放自己所拥有的锁,于是这两个线程都处于了挂起状态,从而造成了这种运行结果。

多线程通信:

      线程间通信-等待唤醒机制:

             wait(),notify(),notifyAll(),都使用在同步中,因为要对持有监视器(锁)的线程操作。

             wait(), notify() ,notifyAll()用来操作线程的方法定义在了Object类中,因为这些方法在操作同步中,线程同步,都必须要表示      它们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁notify唤醒,不可以对不同锁中的线程进行等待唤醒。

     也就是说,等待和唤醒必须是同一个锁。所以要使用在同步中,因为只有同步才具有锁。

            wait()和sleep() 的区别:wait()   :释放资源,释放锁 。sleep()   :释放资源,不释放锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值