线程同步问题

来源:互联网 发布:哪里有学淘宝拍摄的 编辑:程序博客网 时间:2024/06/11 03:45

线程之间的关系

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

线程协作在研究