三:线程的同步与死锁

    科技2026-09-30  12

              引言:在多线程的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,所以当多个线程访问同一资源时如果处理不当会产生数据的错误操作。

    1、同步问题引出

             通过一个简单的卖票的程序,观察若干个线程对象实现卖票的处理。

             范例:实现卖票操作

     代码结果问题没有模拟网络延迟的情况下

    package cn.demos;

    class MyThread implements Runnable {

        // 设置总票数     private int ticket = 10;

        @Override     public void run() {         // 定义循环         while (true) {             // 当票数大于0时,实现卖票操作             if (this.ticket > 0) {                 System.out.println(Thread.currentThread().getName() + "卖票,剩余票数:" + this.ticket--);             } else {                 System.out.println("票已经卖光了");                 break;             }         }     }

    }

    public class Demo1 {     public static void main(String[] arge) {         // 实例化对象         MyThread mt = new MyThread();         new Thread(mt, "票贩子A").start();         new Thread(mt, "票贩子B").start();         new Thread(mt, "票贩子C").start();     } }

    票贩子B卖票,剩余票数:9 票贩子B卖票,剩余票数:7 票贩子B卖票,剩余票数:6 票贩子B卖票,剩余票数:5 票贩子B卖票,剩余票数:4 票贩子B卖票,剩余票数:3 票贩子B卖票,剩余票数:2 票贩子B卖票,剩余票数:1 票已经卖光了 票贩子C卖票,剩余票数:8 票已经卖光了 票贩子A卖票,剩余票数:10 票已经卖光了   模拟网络延迟

    package cn.demos;

    class MyThread implements Runnable {

        // 设置总票数     private int ticket = 10;

        @Override     public void run() {         // 定义循环         while (true) {             // 当票数大于0时,实现卖票操作             if (this.ticket > 0) {

                    try {                     // 模拟网络延迟                     Thread.sleep(1000);                } catch (InterruptedException e) {                     // TODO Auto-generated catch block                     e.printStackTrace();                 }                 System.out.println(Thread.currentThread().getName() + "卖票,剩余票数:" + this.ticket--);             } else {                 System.out.println("票已经卖光了");                 break;             }         }     }

    }

    public class Demo1 {     public static void main(String[] arge) {         // 实例化对象         MyThread mt = new MyThread();         new Thread(mt, "票贩子A").start();         new Thread(mt, "票贩子B").start();         new Thread(mt, "票贩子C").start();     } }

    票贩子B卖票,剩余票数:10 票贩子A卖票,剩余票数:8 票贩子C卖票,剩余票数:9 票贩子A卖票,剩余票数:7 票贩子B卖票,剩余票数:7 票贩子C卖票,剩余票数:6 票贩子A卖票,剩余票数:5 票贩子B卖票,剩余票数:4 票贩子C卖票,剩余票数:3 票贩子A卖票,剩余票数:2 票贩子B卖票,剩余票数:2 票贩子C卖票,剩余票数:1 票已经卖光了 票贩子A卖票,剩余票数:0 票已经卖光了票贩子B卖票,剩余票数:-1 票已经卖光了会出现负数

             此时程序实现的过程是,三个线程卖10张票,所以线程对象在进行票数递减时,会产生不同步的现象,在使用了休眠对程序进行网络延迟模拟处理,可以发现票数会出现负数,而这种现象是当只剩最后一张票时,被一个线程对象抢先领取却进入了休眠阶段,而票数的递减是在休眠之后进行的,所以,会给其他线程对象一种还剩最后一张票的假象,所以当这个线程休眠结束成功获取到最后一张票,那么此时的票剩余为零,而其他线程在结束休眠之后进行票数递减,则会形成票数为负数的情况。这就是线程不同所导致的。而要做一个完整的需要痛同步处理来解决。

     

    2、线程同步处理

             锁:指当某一个线程执行操作时,其他线程外面等待。

             而解决同步问题的关键是锁,所谓的同步就是指多个操作在同一时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

             而在程序中要想实现锁的功能,需要使用synchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块,即在同步代码块的操作里面的代码只允许一个线程执行。

    同步代码块操作

    synchronized(同步对象){

              同步代码操作;

    }

    同步方法操作在方法上追加synchronized关键字即可

             注意:操作里的同步对象表示当前对象,用this定义。

             1)范例:利用同步代码块解决数据访问问题

    代码结果(若全出现一个线程对象,是因为执行结果与电脑速度有关,可以将票数设置多一点试试)

    package cn.demos;

    class MyThread implements Runnable {     // 设置总票数     private int ticket = 10;

        @Override     public void run() {         // 定义循环         while (true) {             // 同步代码块             synchronized (this) {                 // 当票数大于0时,实现卖票操作                 if (this.ticket > 0) {                     try {                         // 模拟网络延迟                         Thread.sleep(100);                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                     System.out.println(Thread.currentThread().getName() + "卖票,剩余票数:" + this.ticket--);

                    } else {                     System.out.println("票已经卖光了");                     break;                 }             }         }     } }

    public class Demo1 {     public static void main(String[] arge) {         // 实例化对象         MyThread mt = new MyThread();         new Thread(mt, "票贩子A").start();         new Thread(mt, "票贩子B").start();         new Thread(mt, "票贩子C").start();     } }

    票贩子A卖票,剩余票数:8421 票贩子A卖票,剩余票数:8420 票贩子A卖票,剩余票数:8419 票贩子A卖票,剩余票数:8418 票贩子A卖票,剩余票数:8417 票贩子A卖票,剩余票数:8416 票贩子A卖票,剩余票数:8415 票贩子C卖票,剩余票数:8414 票贩子C卖票,剩余票数:8413 票贩子C卖票,剩余票数:8412 票贩子C卖票,剩余票数:8411 票贩子C卖票,剩余票数:8410 票贩子C卖票,剩余票数:8409 票贩子C卖票,剩余票数:8408 票贩子C卖票,剩余票数:8407 票贩子C卖票,剩余票数:8406 票贩子C卖票,剩余票数:8405 票贩子C卖票,剩余票数:8404 票贩子C卖票,剩余票数:8403

             从上面的程序中可以发现,加入同步处理之后,程序的整体的性能下降,即同步会造成性能的降低,而异步可以达到性能的提升。

             2)范例:利用同步方法

    代码结果(可以设置大量票(10000)

    package cn.demos;

    class MyThread implements Runnable {     // 设置总票数     private int ticket = 10;

        // 设置一个销售方法     public synchronized boolean sale() {         // 当票数大于0时,实现卖票操作         if (this.ticket > 0) {             try {                 // 模拟网络延迟                 Thread.sleep(10);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println(Thread.currentThread().getName() + "卖票,剩余票数:" + this.ticket--);             return true;         } else {             System.out.println("票已经卖光了");             return false;         }     }

        @Override     public void run() {         // 定义循环         // 调用销售方法         while (this.sale()) {

            }     } }

    public class Demo1 {     public static void main(String[] arge) {         // 实例化对象         MyThread mt = new MyThread();         new Thread(mt, "票贩子A").start();         new Thread(mt, "票贩子B").start();         new Thread(mt, "票贩子C").start();     } }

    票贩子A卖票,剩余票数:10 票贩子A卖票,剩余票数:9 票贩子A卖票,剩余票数:8 票贩子A卖票,剩余票数:7 票贩子A卖票,剩余票数:6 票贩子A卖票,剩余票数:5 票贩子A卖票,剩余票数:4 票贩子A卖票,剩余票数:3 票贩子A卖票,剩余票数:2 票贩子A卖票,剩余票数:1 票已经卖光了 票已经卖光了 票已经卖光了

     

    3、线程死锁

             死锁是在进行多线程同步的处理时可能产生的一种问题,而所谓的死锁指的是若干个线程互相等待的状态。

             死锁的造成原因是若干个线程访问统一资源时一定要进行同步处理,而过多的同步会造成死锁。

     

    Processed: 0.008, SQL: 9