线程同步问题

线程之间的关系

  1. 线程间同步:相互竞争 相互排斥
  2. 线程间协作:线程间相互协作

线程同步

问题引出

一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,由此引出资源的同步问题,即当多个线程要操作同一资源时,有可能出现错误。

练习: 分别使用两种编程方法实现买票的程序
1. 继承Thread方法

public class TestTicketThread{

    public static void main(String[] a){
        TicketThread tThread1 = new TicketThread();
        TicketThread tThread2 = new TicketThread();
        TicketThread tThread3 = new TicketThread();

        tThread1.start();
        tThread2.start();
        tThread3.start();
    }
}

class TicketThread extends Thread {
    private int ticket = 5;

    public void run(){
        for (int i = 0; i < 5; i++){
            if (ticket > 0){
                System.out.println(this.getName() + "卖票:ticket = " + ticket--);
            }
        }
    }
}

运行结果:每个线程单独买票
这里写图片描述

  1. 实现Runnable接口
package zhi;

/**
 * Created by admin on 2017/5/17.
 */
public class TestTicketRunnable{

    public static void main(String[] a){
        TicketRunnable Thread = new TicketRunnable();

        new Thread(Thread).start();
        new Thread(Thread).start();
        new Thread(Thread).start();
    }
}

class TicketRunnable implements Runnable {
    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
            }
        }
    }
}

运行结果:
Thread-0卖票:ticket = 5
Thread-0卖票:ticket = 2
Thread-2卖票:ticket = 3
Thread-1卖票:ticket = 4
Thread-0卖票:ticket = 1

  1. 结果分析
    第二种方法,虽然启动了3个线程,但是3个线程一共卖了5张票,即ticket属性被所有的线程对象共享。而第一种方法,3个线程各卖了5张票,没有实现属性的共享。

结论:实现Runnable接口的方法相对于继承Thread类来说,适合多个相同程序代码的线程去处理统一资源的情况。

练习: 在上述卖票事例的基础上,做如下修改

public class TestTicketRunnable{

    public static void main(String[] a){
        TicketThread tThread = new TicketThread();

        new Thread(tThread).start();
        new Thread(tThread).start();
        new Thread(tThread).start();
    }
}

class TicketThread implements Runnable {
    private int ticket = 5;

    public void run(){
        for (int i = 0; i < 5; i++){
            if (ticket > 0){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
            }
        }
    }
}

运行结果:
Thread-0卖票:ticket = 5
Thread-2卖票:ticket = 5
Thread-1卖票:ticket = 4
Thread-1卖票:ticket = 2
Thread-2卖票:ticket = 3
Thread-0卖票:ticket = 3
Thread-0卖票:ticket = -1
Thread-1卖票:ticket = 1
Thread-2卖票:ticket = 0

分析:卖票的业务步骤如下:
1. 判断票数是否大于0,大于0则表示还有票可以卖;
2. 如果票数大于0,则将票卖出。

在上面的代码中,在步骤(1)和(2)之间加入了延迟操作,那么一个线程就有可能在判断完之后,还没有对票数进行减操作之前,其他线程就已经将票数减少了,这样一来就会出现票数为负的情况。

要解决这个问题,必须使用同步。同步是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

使用synchronized同步代码块——隐式加锁

为避免竞争状态,应该防止多个线程同时进入程序的某一个特定的部分,程序中的这部分称为临界区。
第一种方法可以在代码块上加上synchronized关键字,则此代码块就称为同步代码块。
同步代码块同一时刻只能有一个线程访问

练习: 修改上面的代码,用同步机制解决资源共享问题

public class TestTicketRunnable{

    public static void main(String[] a){
        TicketThread tThread = new TicketThread();

        new Thread(tThread).start();
        new Thread(tThread).start();
        new Thread(tThread).start();
    }
}

class TicketThread implements Runnable {
    private int ticket = 5;

    public void run(){
        for (int i = 0; i < 5; i++){
            synchronized(this){
                    if (ticket > 0){
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
                    }
            }

        }
    }
}

运行结果:

Thread-0卖票:ticket = 5
Thread-0卖票:ticket = 4
Thread-2卖票:ticket = 3
Thread-2卖票:ticket = 2
Thread-2卖票:ticket = 1

分析:使用同步代码块之后,没有出现重复票和票为负数的情况。

在方法前加入synchronized关键字,则该方法为同步方法。

public class TestSynchronizedMethod {
    public static void main(String[] a){
        TicketThreadMethod tThread = new TicketThreadMethod();

        new Thread(tThread).start();
        new Thread(tThread).start();
        new Thread(tThread).start();
    }
}

class TicketThreadMethod implements Runnable {
    private int ticket = 5;

    public void run(){
        for (int i = 0; i < 5; i++){
            this.sale();
        }
    }

    public synchronized void sale(){
            if (ticket > 0){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
            }
    }
}

利用加锁同步——显式加锁lock.lock()

线程协作在研究

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值