记录自己多线程遇到的一个有趣的问题

在复习多线程这一章节时写了下面这一串代码

package CallableTest;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * @author haustsusu
 * @create 2023-03-31-11:37
 */
public class CallableTest implements Callable {
    private int i=0;
    @Override
    public Object call() throws Exception {
        synchronized (this){
            for (; i < 1000; i++) {
                notifyAll();
                System.out.println(Thread.currentThread().getName()+"第"+i+"个数");
                wait();
            }
            return null;
        }
    }
}


class mainTest{
    public static void main(String[] args) {
        //newcallable的实现类
        CallableTest callableTest = new CallableTest();

        //new futureTask对象
        FutureTask futureTask = new FutureTask(callableTest);
        FutureTask futureTask1 = new FutureTask(callableTest);
        FutureTask futureTask2 = new FutureTask(callableTest);
        FutureTask futureTask3 = new FutureTask(callableTest);

        //将futureTask放入Thread类中
        new Thread(futureTask, "线程1").start();
        new Thread(futureTask1, "线程2").start();
        new Thread(futureTask2, "线程3").start();
        new Thread(futureTask3, "线程4").start();
    }
}

自己想要的结果其实是这四个线程都不重复的按照顺序输出0,1,2,3,4......

结果是:

线程1第0个数
线程4第0个数
线程2第0个数
线程3第0个数
线程2第1个数
线程4第2个数
线程1第3个数
线程4第4个数
线程2第5个数
线程3第6个数
线程2第7个数
线程4第8个数
线程1第9个数
线程4第10个数
线程2第11个数
线程3第12个数
线程2第13个数
线程4第14个数
线程1第15个数
线程4第16个数
线程2第17个数
线程3第18个数
线程2第19个数
线程4第20个数.......

 很有趣的是这四个线程在起初都输出了第一个数,这肯定不是我们想看到的,于是把代码进行了修改,改成下面这样

package CallableTest;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * @author haustsusu
 * @create 2023-03-31-11:37
 */
public class CallableTest implements Callable {
    private int i=0;
    @Override
    public Object call() throws Exception {
        synchronized (this){
            for (; i < 1000;) {
                notifyAll();
                System.out.println(Thread.currentThread().getName()+"第"+i+"个数");
                i++;
                wait();
            }
            return null;
        }
    }
}


class mainTest{
    public static void main(String[] args) {
        //newcallable的实现类
        CallableTest callableTest = new CallableTest();

        //new futureTask对象
        FutureTask futureTask = new FutureTask(callableTest);
        FutureTask futureTask1 = new FutureTask(callableTest);
        FutureTask futureTask2 = new FutureTask(callableTest);
        FutureTask futureTask3 = new FutureTask(callableTest);

        //将futureTask放入Thread类中
        new Thread(futureTask, "线程1").start();
        new Thread(futureTask1, "线程2").start();
        new Thread(futureTask2, "线程3").start();
        new Thread(futureTask3, "线程4").start();
    }
}

改动的部分就是将i++放在wait()方法之前,这样代码就按照预想的结果输出了

改动前:

            for (; i < 1000; i++) {
                notifyAll();
                System.out.println(Thread.currentThread().getName()+"第"+i+"个数");
                wait();
            }

改动后:

            for (; i < 1000;) {
                notifyAll();
                System.out.println(Thread.currentThread().getName()+"第"+i+"个数");
                i++;
                wait();
            }

这里就不得不提一下wait()方法了,首先wait()方法只能在同步代码块与同步方法中使用

notify()方法:

Object notify() 方法用于唤醒一个在此对象监视器上等待的线程。

如果所有的线程都在此对象上等待,那么只会选择一个线程,选择是任意性的,并在对实现做出决定时发生。

一个线程在对象监视器上等待可以调用 wait() 方法。

notify() 方法只能被作为此对象监视器的所有者的线程来调用。

一个线程要想成为对象监视器的所有者,可以使用以下 3 种方法:

  • 执行对象的同步实例方法
  • 使用 synchronized 内置锁
  • 对于 Class 类型的对象,执行同步静态方法

wait()方法 

Object wait() 方法让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法

当前线程必须是此对象的监视器所有者,否则还是会发生 IllegalMonitorStateException 异常。

如果当前线程在等待之前或在等待时被任何线程中断,则会抛出 InterruptedException 异常。

注意:

CPU的操作具有原子性,单独执行一条指令或者语句,在执行完毕前不会被中断。 

 个人理解:

代码之所以出现所有线程输出0,是因为当线程一抢到资源时,输出i=0,之后进入等待状态释放锁,随后线程二也进入,执行notifyAll()方法唤醒了所有等待的线程,但此时线程二手里握着锁,导致其他线程无法进入代码块,此时线程二依旧会输出0,之后也进入等待状态,假如这时线程三也获得了锁,同理也是会输出0,线程三释放锁后线程四又抢到锁,同样输出0,此后剩下三个线程中有一个获得了锁,此时才会进行i++操作...

 4/4号

今天看到一篇文章说i++的操作不是原子性的,而是分了三步,取值,加一,赋值,这三个操作都是原子的,不过合在一起就不行了。

 加入有下列代码:
 

public class ThreadDemo implements Runnable{
	 int no = 0;
	 @Override
	 public void run() {
		 no++;
		 System.out.println(no);
	 }
	 public static void main(String[] args)
	 {
		 ThreadDemo demo = new ThreadDemo();
		 for(int i=0;i<1000;i++)
		 {
			 Thread task = new Thread(demo);
			 task.start();
		 }
	 }
}

 1000个线程执行i++操作

按照我原来的想法是一千个线程执行1000次i++,虽然线程是不安全的,但我心里默认i++是原子性操作.但实际上是i++不是原子性操作,其实包括了取值,加一,赋值这三步,

假如两个线程A、B一起来操作no,no初始值是1,线程A读取no值是1,然后做no+1,这时线程B对 no取值还是1,然后A将2赋值给no,B操作no+1结果是2,也将2赋值给no。对1做了两次+1操作,最后结果是2。这个过程可以参考下图

 

如果把同步代码块中改成这样

public class CallableTest implements Callable {
    private int i=0;
    @Override
    public Object call() throws Exception {
        synchronized (this){
            for (; i < 3; i++) {
                notifyAll();
                System.out.println(Thread.currentThread().getName()+"第"+i+"个数");
                wait();
                System.out.println(Thread.currentThread().getName()+"线程醒了");
            }
            return null;
        }
    }
}

 这里我创建了十二个线程结果如下:

线程1第0个数
线程4第0个数
线程3第0个数
线程2第0个数
线程7第0个数
线程8第0个数
线程10第0个数
线程3线程醒了
线程3第1个数
线程6第1个数
线程4线程醒了
线程4第2个数
线程5第2个数
线程1线程醒了
线程4线程醒了
线程6线程醒了
线程3线程醒了
线程10线程醒了
线程8线程醒了
线程7线程醒了
线程2线程醒了

 结果也很有意思,先到这里吧

为了加深对部分知识的理解,发现了几篇优质好文,看完会有收货

(35条消息) 深入理解Java中的wait() 方法_小楼夜听雨QAQ的博客-CSDN博客

关于synchronized 锁的是什么?

synchronized 锁的是什么?(二)-阿里云开发者社区 (aliyun.com)

 多线程篇-线程安全-原子性、可见性、有序性解析

 多线程篇-线程安全-原子性、可见性、有序性解析 - 知乎 (zhihu.com)

Java 中的 i++ 是原子操作吗?

(38条消息) Java 中的 i++ 是原子操作吗?_i++是原子操作吗_ツぃ☆ve芜情的博客-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值