最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

三:线程的同步与死锁

互联网 admin 0浏览 0评论

三:线程的同步与死锁

          引言:在多线程的处理之中,可以利用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、线程死锁

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

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

 

三:线程的同步与死锁

          引言:在多线程的处理之中,可以利用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、线程死锁

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

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

 

与本文相关的文章

发布评论

评论列表 (0)

  1. 暂无评论