上文创建多线程买票的例子中注释会出现错票、重票的问题,本文来讲讲如何解决此问题。本文例子:利用多线程模拟 3 个窗口卖票

实现Runnable接口

public class TestThread2 {
public static void main(String [] args){
Window window=new Window();
Thread thread1=new Thread(window,"窗口一");
Thread thread2=new Thread(window,"窗口二");
Thread thread3=new Thread(window,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
} class Window implements Runnable{
int ticket=50;
@Override
public void run(){
while (true){
if(ticket > 0){
try {
Thread.currentThread().sleep(100);//模拟卖票需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}else {
break;
}
}
}
}

运行结果:

窗口二售票,票号为:13
窗口三售票,票号为:12
窗口一售票,票号为:11
窗口二售票,票号为:10
窗口一售票,票号为:10
窗口三售票,票号为:10
窗口三售票,票号为:9
窗口一售票,票号为:8
窗口二售票,票号为:7
窗口三售票,票号为:6
窗口一售票,票号为:5
窗口二售票,票号为:4
窗口三售票,票号为:3
窗口一售票,票号为:2
窗口二售票,票号为:1
窗口三售票,票号为:0
窗口一售票,票号为:-1

结果分析:这里出现了票数为0和负数还有重票的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

  当票号为10时:A线程、B线程、C线程同时进入到if(ticket > 0)的代码块中,A线程已经执行了打印输出语句,但是还没有做ticket--操作;
  这时B线程就开始执行了打印操作,那么就会出现两个线程打印票数一样,即卖的是同一张票
  当票号为1时:A线程、B线程,C线程同时进入到if(ticket > 0)的代码块中,A线程执行了打印语句,并且已经做完了ticket--操作,则此时ticket=0;
  B线程再打印时就出现了0的情况,同理C线程打印就会出现-1的情况。

解决办法:即我们不能同时让超过两个以上的线程进入到 if(ticket > 0)的代码块中,不然就会出现上述的错误。必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。我们可以通过以下两个办法来解决:

  1、使用 同步代码块

  2、使用 同步方法

使用 同步代码块

synchronized(同步监视器){
//需要被同步的代码块(即为操作共享数据的代码)
}

  同步监视器:由任意一个类的对象来充当,哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁

  要求:1、所有的线程必须公用同一把锁!不能相对于线程是变化的对象;

        2、并且只需锁住操作共享数据的代码,锁多了或少了都不行;

实例:

1、实现的方式

public class TestWindow {
public static void main(String [] args){
Window1 window=new Window1();
Thread thread1=new Thread(window,"窗口一");
Thread thread2=new Thread(window,"窗口二");
Thread thread3=new Thread(window,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
} class Window1 implements Runnable{
int ticket=100;//共享数据
@Override
public void run(){
while (true){
synchronized (this){//this表示当前对象,此时表示创建的 window
if(ticket > 0){
try {
//模拟卖票需要一定的时间
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}
}
}
}
}

注意:在实现的方式中,考虑同步的话,可以使用this充当锁,但在继承的方式中,会创建多个对象,慎用this

2、继承的方式

public class TestWindow1 {
public static void main(String [] args){
Window2 window1=new Window2();
Window2 window2=new Window2();
window1.start();
window2.start();
}
} class Window2 extends Thread{
static int ticket=100;//共享数据;注意声明为 static,表示几个窗口共享
static Object object=new Object();//用static 可以表示唯一
@Override
public void run(){
while (true){
//synchronized (this){//this表示当前对象,此时表示创建的 window1和window2
synchronized (object){//锁必须是唯一,不能每个线程都使用自己的一把锁
if(ticket > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}
}
}
}
}

注意:1、继承的方式会创建多个实例,所以共享资源需要用static来修饰,表示共享

      2、继承的方式会创建多个实例,所以 this 表示不同的实例对象,这里表示widow1和window2,所以不能使用this当锁,此时可以定义一个 static 修饰的对象当锁

使用 同步方法

语法:即用  synchronized  关键字修饰方法

将操作共享数据的方法声明为synchronized。即此方法为同步方法,能够保证当其中一个线程执行此方法时,其他线程再外等待直至此线程执行完此方法。
注意:同步方法的锁:this

实例:

1、实现的方式

public class TestWindow2 {
public static void main(String [] args){
Window3 window=new Window3();
Thread thread1=new Thread(window,"窗口一");
Thread thread2=new Thread(window,"窗口二");
Thread thread3=new Thread(window,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
} class Window3 implements Runnable{
int ticket=100;//共享数据
@Override
public void run(){
while (true){
show();
}
} public synchronized void show(){//this充当锁,此时表示创建的 window;
// 如果用继承的方式,使用同步方法,这里表示创建的 window1和window2,继承的方式不要使用同步方法
if(ticket > 0){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}
}
}

注意:1、synchronized 的锁为this,这里表示创建的对象实例window;

     2、继承的时候t this 表示创建的window1和window2,继承的方式不要使用同步方法。

Java 多线程(二)—— 线程的同步的更多相关文章

  1. Java多线程之线程的同步

    Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...

  2. java多线程之线程的同步与锁定(转)

    一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. publicc ...

  3. (原创)JAVA多线程二线程池

    一,线程池的介绍 线程池包括一下三种: 线程池名称 创建方法 特点 其他 固定大小线程池 ExecutorService threadpool = Executors.newFixedThreadPo ...

  4. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  5. Java多线程与线程同步

    六.多线程,线程,同步 ①概念: 并行:指两个或多个在时间同一时刻发生(同时发生) 并发:指两个或多个事件在同一时间段内发生 具体概念: 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多 ...

  6. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  7. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

  8. Java多线程之线程的生命周期

    Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...

  9. Java多线程之线程其他类

    Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...

  10. Java多线程之线程的通信

    Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...

随机推荐

  1. MySQL 如何使用左链接代替 NOT IN

    核心思想 通过左链接 查询出要排除的数据 然后和主表进行匹配 拿去未匹配到的数据 可以使用 IS NULL 来过滤掉 案例稍后 更新 select * from a left join on a.id ...

  2. js/jQuery使用过程中常见问题/已踩过的坑大杂烩

    目录 一.jQuery选择器选择选中的或者disabled的选择框时attr函数无效 二.jQuery each函数的break/continue 三.jQuery 获取元素的left会值/left数 ...

  3. mysql 8.0 密码加密方式的坑

    问题:新安装好MySQL 8.0和Navicat之后,连接时总是报: 1251 Client does not support authentication protocol requested by ...

  4. 创建线程的一般方式和匿名内部类方式对比——实现runnable接口,重新run方法

    启动:使用静态代理设计模式 优点:可同时实现继承,避免单继承局限性 一般方式: Programer.java /** * 真实角色 * * @author :liuqi * @date :2018-0 ...

  5. 我的 FPGA 学习历程(07)—— BCD 编码:移位加 3 算法

    2-10 进制码,也称为 BCD 码,它的编码方式则是通过一个 4 位二进制来表示一个 10 进制数,部分十进制对应的 BCD 码如下 十进制数 | BCD 码 13 --> 0001_0011 ...

  6. ABAQUS/CAE——Context

    Part(部分) 用户在Part单元内生成单个部件,可以直接在ABAQUS/CAE环境下用图形工具生成部件的几何形状,也可以从其他的图形软件输入部件.详细可参考ABAQUS/CAE用户手册第15章. ...

  7. DataTable的Merge\COPY\AcceptChange使用说明

    在C#内使用DataTable的Merge().Copy().AcceptChange().Clone()方法的用途如下: 1.Merge()可将两个不同的表结构的表进行合并,合并后新表的列为之前两表 ...

  8. webpack-dev-server和webpack-dev-middleware的区别

    webpack-dev-server webpack-dev-server实际上相当于启用了一个express的Http服务器+调用webpack-dev-middleware.它的作用主要是用来伺服 ...

  9. [LeetCode] Flipping an Image 翻转图像

    Given a binary matrix A, we want to flip the image horizontally, then invert it, and return the resu ...

  10. [error] - Build path is incomplete. Cannot find class file for org/aspectj/weaver/refl

    将本地仓库中mybatis 的jar 包删除,然后在eclipse 中右键工程选中 Maven->upgrade ..