力扣1115 循环交替打印

力扣1115题目:给定类,两个不同的线程分别执行调用同一个FooBar实例,修改程序实现”foobar“被交替打印n次。


题目

给定类如下:

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

两个不同的线程共用一个FooBar实例:

  • 线程A调用foo()方法;
  • 线程B调用bar()方法。

设计修改程序,实现foobar被循环输出n次。


方法① 利用synchronized同步锁

  1. wait、notify/notifyAll方法都是Object的本地final方法,无法被重写
  2. wait让当前线程阻塞等待,前提是首先获得锁,配合synchronized关键字使用;在synchronized同步代码中使用wait、notify/notifyAll方法。
  3. 由于wait、notify/notifyAll在synchronized代码中执行,则说明当前线程必定有锁
  • 若线程执行wait方法,释放当前锁,给出CPU资源,进入等待状态。
  • 只有当notify/notifyAll方法执行,才会唤醒一个或者多个处于等待状态的线程,从而继续往下执行,一直到执行完synchronized代码块中的代码或者中间遇到了wait,再次释放锁lock。
  • 也就是说,notify/nofityAll执行只是唤醒沉睡的线程,不会立即释放锁,锁的释放需要查看代码块的具体执行情况。在具体业务处理中,尽量在使用了notify/notifyAll后立即退出临界区,唤醒其他等待的线程。

临界区:一个访问公用资源,例如公用设备或者公用存储器,的程序片段;这些公用资源无法同时被多个线程访问的特性;当有线程进入临界区段时,其他线程必须wait等待(例如:bounded waiting 等待算法),有一些同步的机制会在临界区的进入点或者离开点实现,保证公用资源是互斥获得使用权,例如:信号量semaphore机制;生活中的例子有,打印机资源的互斥使用。

  1. wait方法需要被try–catch异常机制包裹,即使发生异常也能让wait等待的线程唤醒。
  2. notify和wait的执行顺序不可以出错,若A线程优先执行了notify方法,B线程执行了wait方法,则B线程是无法唤醒的。
  3. notify和notifyAll方法的差异:
  • 首先给出如下两个概念:等待池,设定线程A调用了对象的wait方法,则线程A释放该对象锁之后,进入了该对象的等待池,等待池的线程不会去竞争该对象的锁;锁池,只有获取了对象的锁,县城才可以执行对象的synchronized代码块,对象的锁每次只有一个线程可以获取,其他线程只能够等待。
  • notify方法随机唤醒对象的等待池的一个线程,进入锁池;
  • nofiyAll方法则唤醒对象的等待池中的所有线程,进入锁池。

给出测试代码如下:

package java_thread_run;

public class TestNotifyNotifyAll {
    private static Object obj = new Object();

    public static void main(String[] args) {
        //测试RunnableImplA wait方法
        Thread t1 = new Thread(new RunnableImplA(obj));
        Thread t2 = new Thread(new RunnableImplA(obj));
        t1.start();
        t2.start();

//        //RunnableImplB notify()
//        Thread t3 = new Thread(new RunnableImplB(obj));
//        t3.start();

        //RunnableImplC notifyAll()
        Thread t4 = new Thread(new RunnableImplC(obj));
        t4.start();
    }
}

class RunnableImplA implements Runnable {
    private Object obj;

    public RunnableImplA(Object obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        System.out.println("run on RunnableImplA");
        synchronized (obj) {
            System.out.println("obj to wait on RunnableImplA");
            try {
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("obj continue to run on RunnableImplA");
        }
    }
}

class RunnableImplB implements Runnable {

    private Object obj;

    public RunnableImplB(Object obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        System.out.println("run on RunnableImplB");
        System.out.println("睡眠3秒...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (obj) {
            System.out.println("notify obj on RunnableImplB");
            obj.notify();
        }
    }
}

class RunnableImplC implements Runnable {

    private Object obj;

    public RunnableImplC(Object obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        System.out.println("run on RunnableImplC");
        System.out.println("睡眠3秒...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (obj) {
            System.out.println("notifyAll obj on RunnableImplC");
            obj.notifyAll();
        }
    }
}

调用notify方法,则始终有一个线程等待被唤醒,程序不会终止;调用notifyAll方法,则线程t1、t2都会执行完毕。
7. 多线程测试某个条件变化,使用if还是while
注意,notify唤醒沉睡的县城之后,线程会继续执行上次的步骤,进行条件判定,可以把wait语句忽略不计;但是显然,需要确保程序一定会执行,并且保证程序直至满足一定的条件在执行,则需要while等待,直至满足条件后继续往下执行。


方法① 代码

class FooBar {
    private int n;
    private Object obj = new Object();
    private volatile boolean fooExec = true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            synchronized (obj) {
                if (!fooExec) {
                    //若fooExec为false,则线程等待,true执行下面步骤
                    obj.wait();
                }
                printFoo.run();
                fooExec = false;
                //唤醒其他线程
                obj.notifyAll();
            }
        	// printFoo.run() outputs "foo". Do not change or remove this line.
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            
            // printBar.run() outputs "bar". Do not change or remove this line.
        	synchronized (obj) {
                if (fooExec) {
                    obj.wait();
                }
                printBar.run();
                fooExec = true;
                obj.notifyAll();
            }
        }
    }
}

关键点:volatile

Java语言提供了一种稍弱的同步机制,也就是volatile 变量,保证变量的更新操作会告知其他线程。

若把某个变量声明为volatile,编译器在运行时都会注意到该变量存在共享行为,不会把该变量的操作和内存其他操作一起重排序。volatile 变量不会被缓存在寄存器或者其他处理器不可见的地方,在读取volatile 类型的变量时总是会返回最新写入的数值。

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

在这里插入图片描述

对于非volatile 变量读写时,每一个线程会把内存拷贝变量到CPU cache缓存中,若计算机存在多个CPU,每个线程可能会在不同的CPU处理,意味每一个线程都可以拷贝到不同的cache中

声明为volatile 之后,JVM保证每次变量读取都从内存中,跳过CPU cache这一步。

  • 当一个变量为volatile 之后,存在两种特点:
  1. 保证变量对所有线程可见,若某个线程修改了该变量,会保证新数值立刻更新同步到主内存中;同时每次读取也从主内存读取。
  2. 禁止指令重排优化,被volatile 修饰的变量,赋值之后会执行load addl $0x0, (%esp) 操作,类似内存屏障。(指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
力扣一个在线的编程题库,在其中有各种算法和数据结构的题目,供程序员进行练习。力扣题库支持多种编程语言,包括Python。 力扣Python格式是指在力扣平台上使用Python语言解答问题时需要注意的一些细节和规范。以下是一些力扣Python格式的要点: 1. 导入模块:根据题目需要,导入相应的Python模块。常见的模块如:math、collections等。 2. 主函数:在解题时,将代码写在一个主函数中。通常命名为def main()。 3. 输出:遵循力扣输出格式。使用input函数获取入数据,使用print函数输出结果。 4. 命名规范:遵循Python的命名规范。变量和函数名采用小写字母与下划线的组合,以便于代码的可读性。 5. 注释:在关键代码处添加注释,描述代码功能和思路。这不仅方便自己理解和维护代码,也方便他人阅读。 6. 缩进:使用统一的缩进风格,通常为4个空格或者1个制表符。 7. 算法实现:根据题目要求,选择合适的算法进行实现。可以使用循环、条件判断、递归等常见的编程结构。 8. 异常处理:对于可能出现异常的地方,使用try-except语句进行异常处理。 9. 提交代码:在完成代码编写后,将代码复制到力扣平台的代码编辑器中,然后点击提交按钮进行代码评测。 总之,力扣Python格式主要是指在力扣平台上使用Python语言解题时需要遵守的编码规范和格式要求。遵循这些规范可以提高代码的可读性和可维护性,从而更好地解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值