多线程的同步与不同步

线程的同步

为什么要线程同步

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),
将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,
从而保证了该变量的唯一性和准确性。

以售卖火车票为例

未实现同步,

出现一票多卖的情况.开三个线程模拟三个窗口卖票.

class TicketWindows implements Runnable {
    private int tickets = 20;// 总票数

    public void run() {
        while (true) {
            // if else 要保证其原子行 同步代码块
            if (tickets > 0) {
                // 显示售票信息
                System.out.println(Thread.currentThread().getName() + "正在售票中   ....第 " + tickets + "张票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tickets--;
                System.out.println("票还余: " + tickets + "张 ...");
            } else {
                // 退出售票窗口
                break;
            }
            // 判断是否还有票
        }
    }

    public static class TestTickets {

        public static void main(String[] args) {

            TicketWindows tWindows1 = new TicketWindows();
            Thread thread1 = new Thread(tWindows1);
            Thread thread2 = new Thread(tWindows1);
            Thread thread3 = new Thread(tWindows1);
            thread1.start();
            thread2.start();
            thread3.start();

        }

    }
}

输出结果为:

Thread-0正在售票中   ....第 20张票
Thread-2正在售票中   ....第 20张票
Thread-1正在售票中   ....第 20张票
票还余: 19...
Thread-0正在售票中   ....第 19张票
票还余: 17...
Thread-1正在售票中   ....第 17张票
票还余: 17...
Thread-2正在售票中   ....第 17张票
票还余: 16...
Thread-0正在售票中   ....第 16张票
票还余: 15...
Thread-1正在售票中   ....第 15张票
票还余: 14...
Thread-2正在售票中   ....第 14张票
票还余: 13...
Thread-0正在售票中   ....第 13张票
票还余: 12...
Thread-1正在售票中   ....第 12张票
票还余: 11...
Thread-2正在售票中   ....第 11张票
票还余: 10...
Thread-0正在售票中   ....第 10张票
票还余: 9...
Thread-1正在售票中   ....第 9张票
票还余: 8...
Thread-2正在售票中   ....第 8张票
票还余: 7...
Thread-0正在售票中   ....第 7张票
票还余: 6...
Thread-1正在售票中   ....第 6张票
票还余: 5...
Thread-2正在售票中   ....第 5张票
票还余: 4...
Thread-0正在售票中   ....第 4张票
票还余: 3...
Thread-1正在售票中   ....第 3张票
票还余: 2...
Thread-2正在售票中   ....第 2张票
票还余: 1...
Thread-0正在售票中   ....第 1张票
票还余: 0...
票还余: -1...
票还余: -2...

可以看出一票被多个窗口售卖,并且还出先卖负数票的情况.是因为:前一个线程进入run()后开始执行售卖操作,票数减一,操作还没结束,另一个线程也进来了,此时的票数其实已经减过了(负数),后来的线程之后执行到输出语句的时候才知道是负数.
所以,就需要在一个线程进入之后锁住这一方法,等操作结束另一线程才能进入.这一线程操作的时候,其他线程只能等待.

实现同步

只粘出修改的代码,其实就是加上一个同步代码块(同步锁的一种,还有同步代码块方法)
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    public void run() {
        while (true) {
            synchronized (this) {
                // if else 要保证其原子行 同步代码块
                if (tickets > 0) {

                    // 显示售票信息
                    System.out.println(Thread.currentThread().getName() + "正在售票中   ....第 " + tickets + "张票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    tickets--;
                    System.out.println("票还余: " + tickets + "张 ...");
                } else {
                    // 退出售票窗口
                    break;
                }
            }
        }
    }

此时的输出结果为:

Thread-0正在售票中   ....第 20张票
票还余: 19...
Thread-0正在售票中   ....第 19张票
票还余: 18...
Thread-2正在售票中   ....第 18张票
票还余: 17...
Thread-2正在售票中   ....第 17张票
票还余: 16...
Thread-2正在售票中   ....第 16张票
票还余: 15...
Thread-2正在售票中   ....第 15张票
票还余: 14...
Thread-2正在售票中   ....第 14张票
票还余: 13...
Thread-2正在售票中   ....第 13张票
票还余: 12...
Thread-1正在售票中   ....第 12张票
票还余: 11...
Thread-1正在售票中   ....第 11张票
票还余: 10...
Thread-2正在售票中   ....第 10张票
票还余: 9...
Thread-2正在售票中   ....第 9张票
票还余: 8...
Thread-2正在售票中   ....第 8张票
票还余: 7...
Thread-0正在售票中   ....第 7张票
票还余: 6...
Thread-0正在售票中   ....第 6张票
票还余: 5...
Thread-0正在售票中   ....第 5张票
票还余: 4...
Thread-2正在售票中   ....第 4张票
票还余: 3...
Thread-2正在售票中   ....第 3张票
票还余: 2...
Thread-1正在售票中   ....第 2张票
票还余: 1...
Thread-1正在售票中   ....第 1张票
票还余: 0...

此时的结果才是正常的情况!

线程同步的方法

  • 同步方法
public  synchronized void addMoney(int money){ }
  • 同步代码块
synchronized (this) { }
  • 使用特殊域变量(volatile)实现线程同步(不常用)
  • 使用重入锁实现线程同步
    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
    ReenreantLock类的常用方法有:
    ReentrantLock(): 创建一个ReentrantLock实例
    lock() : 获得锁
    unlock() : 释放锁
    伪代码如下:
public void addMoney(int money) {  
        lock.lock();//上锁  
        try{  
        count += money;  
        System.out.println(System.currentTimeMillis() + "存进:" + money);  

        }finally{  
            lock.unlock();//解锁  
        }  
    }
  • 局部变量实现同步(不常用)

你可能感兴趣的:(学习总结)