Java多线程(四)

目录

四、线程的同步

4.1 线程同步介绍

4.2 线程同步实现

四、线程的同步

4.1 线程同步介绍

  • 场景案例

    例如电影院有三台自动售票机售卖100张电影票,不考虑实际情况,票号从100号依次减少,售票过程包括出票和自减两个步骤,先自减再出票,当其中一台机器A自减结束未完成出票的操作,另一台机器B执行了自减操作,这样导致两台机器均售出了相同的票(重票、漏票),再者,剩余最后一张票时,三台机器同时执行自减操作,这样三台售卖机可能卖出0号票、-1号票的情况(错票)。所以,当一台售票机执行售票操作(自减和出票),不允许其他机器同时执行售票操作(即线程同步),三台售票机相当于三条线程,而100张电影票则是共享资源,且实际情况下,出票和自减均是一瞬间的事情,所以从感官上是三台机器同时操作。

  • 原因分析

    当多条语句在操作线程共享数据时,一个线程对多条语句只执行其中一部分,还没有执行完成,另一个线程参与进来,导致共享数据错误,即发生线程安全问题。

  • 解决方案

    对于多条操作共享数据的语句,只允许其中一个线程都执行完,在执行过程中,不允许其他的线程参与执行,哪怕正在执行中的线程处于堵塞状态,也不允许其他线程去执行。

  • 线程同步

    当线程共享了相同的资源,在某些时刻同时读写该资源时,可能会产生冲突,从而导致线程的安全问题,因此一次只能允许其中一条线程来访问共享资源,即线程同步。

4.2 线程同步实现

4.2.1 场景介绍

  • 需求描述

准备100张票作为共享资源,三台自动售票机进行售票,即启动三个线程去消费100张电影票。

  • 继承Thread方式

class Window1 extends Thread {
    private static Integer ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}
Window1 w1 = new Window1();
Window1 w2 = new Window1();
Window1 w3 = new Window1();
​
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
​
w1.start();
w2.start();
w3.start();
  • 实现Runnable接口

class Window2 implements Runnable {
    private Integer ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
​
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
​
t1.start();
t2.start();
t3.start();
  • 错误现象

image-20210121231501254

4.2.2 Synchronized使用

  • 同步代码块

// 格式
synchronized(同步监视器){
    // 需要被同步的业务代码程序
}

1.操作共享数据的程序代码,即是需要被同步的代码,不能包含代码多了,也不能包含代码少了;

2.共享数据,即多个线程可以共同操作的变量,例如100张电影票;

3.同步监视器,俗称锁,任何一个对象都可以充当锁,潜在的要求就是多个线程对象必须共用一把锁,所以推荐继承Thread类的的子类,使用子类的对象(类名.class),实现Runnable接口的子类,使用当前子类对象本身(this)。

  • 继承Thread方式

private static Integer ticket = 100;
while (true) {
    synchronized (Window3.class) {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        } else {
            break;
        }
    }
}
}
  • 实现Runnable接口

private Integer ticket = 100;
​
@Override
public void run() {
​
    while (true) {
        synchronized (this) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket--;
            } else {
                break;
            }
​
        }
    }
}
  • 同步方法

// synchronized还可以放在方法声明中,表示整个方法为同步方法。 
private static synchronized void show();
private synchronized void show();
// 在方法的修饰词中加上synchronized关键字

1.静态方法,同步方法的同步监视器是类名.class;

2.非静态方法,同步方法的同步监视器是this

  • 继承Thread方式

private static synchronized void show() {
    if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
        ticket--;
    }
}
  • 实现Runnable接口

private synchronized void show() {
    if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
        ticket--;
    }
}

注意,这里一个使用的是静态的同步方法,一个使用的非静态的同步方法。

总结

优点:同步的方式,解决了线程的安全问题 缺点:操作同步代码,只能一个线程参与,其他线程等待,相当于是单线程操作,效率低

同步锁机制: 在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防 止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法 就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须 锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁 之时,另一个任务就可以锁定并使用它了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值