【问题标题】:How does a synchronized method work in Java?Java 中的同步方法是如何工作的?
【发布时间】:2016-07-12 12:00:21
【问题描述】:

我需要一个无论有多少线程调用它都只会从后台运行一次的方法。 我找到了使用此代码的部分解决方案 我的代码:

public static void async(final int a){
    Thread th = new Thread(new Runnable() {
        @Override
        public void run() {
            meth(a);
        }
    });
    th.start();
}
public static synchronized void meth(final int a){
    try {
        Thread.sleep(1000);
        System.out.println(a);
    } catch (InterruptedException ex) {
        Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
    }
}

但是当我这样测试时:

System.out.println("start");
async(11);
async(12);
async(13);
async(14);
async(15);
async(16);
async(17);
async(18);
async(19);
System.out.println("end");

我得到了这些结果:

开始 结尾 11 19 18 17 15 16 14 13 12

我的代码有什么问题吗? 为什么结果和调用的顺序不一样?

已编辑 使用 Thread.join 后

public static Object obg = new Object();

public static void async(final int a){
    Thread th = new Thread(new Runnable() {
        @Override
        public void run() {
            meth(a);
        }
    });
    th.start();
    try {
        th.join();
    } catch (InterruptedException ex) {
        Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
    }
}

public static synchronized void meth(final int a){
    try {
        Thread.sleep(1000);
        System.out.println(a);
    } catch (InterruptedException ex) {
        Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
    }
}

我得到了取消后台工作的结果:

开始 11 12 13 14 15 16 17 18 19 结束

Thread.join 没有给我想要的结果

第三次编辑以提供其他语言的示例。

我在 c# 中尝试了相同的代码

 static void Main(string[] args)
    {
        Console.WriteLine("start");
        async(11);
        async(12);
        async(13);
        async(14);
        async(15);
        async(16);
        async(17);
        async(18);
        Console.WriteLine("end");
    }

    static Object o = new Object();
    public static void async(int a){
        new Thread(() =>
        {
            lock (o)
            {
                Thread.Sleep(1000);
                Console.WriteLine(a);
            }
        }).Start();
    }

结果顺序相同

swift语言的测试结果和c#一样

所以我的问题是:如何在 java 中实现这些结果

编辑: 在join中使用新创建线程的结果

代码:

public static void main(String[] args) throws Exception {
    System.out.println("start");
    async(19,async(18,async(17,async(16,async(15,async(14,async(13,async(12,async(11,null)))))))));
    System.out.println("end");
}

public static Object obg = new Object();

public static Thread async(final int a,final Thread other){
    Thread th = new Thread(new Runnable() {
        @Override
        public void run() {
            meth(a);
        }
    });
    th.start();
    try {
        if(other!=null){
            other.join();
        }
    } catch (InterruptedException ex) {
        Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
    }
    return th;
}

public static synchronized void meth(final int a){
    try {
        Thread.sleep(1000);
        System.out.println(a);
    } catch (InterruptedException ex) {
        Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
    }
}

结果:

开始 11 12 13 14 15 16 17 18 结尾 19

后台工作也被取消了。

【问题讨论】:

  • @Idos 我尝试了网站中提供的解决方案我得到了这个结果: start 11 12 13 14 15 end ,正如你所看到的,后台没有运行任何东西。
  • 使用 swift 语言测试相同的代码并给出了我的结果我希望我们能说这个问题来自 java 吗?
  • 说你在 Java 中发现了一个错误是非常冒昧的,可能不是真的......
  • 不是我找到的,而是结果说的

标签: java multithreading asynchronous synchronization


【解决方案1】:

您的主线程启动另外九个,给每个子线程一个不同的a,所有这九个子线程做的第一件事就是休眠一秒钟。

Thread.sleep() 调用中的时间分辨率未定义——这取决于底层操作系统——并且很可能所有线程都可以在系统时钟的同一滴答声中唤醒。

Java 不保证线程将按照它们的 Thread 对象是 start()ed 的顺序开始运行,也不保证它们会按照它们进入睡眠的顺序唤醒。

任何时候您希望事情以特定的顺序发生,最好的方法是在一个线程中完成所有事情。


我需要此部分按顺序运行...我不希望它同时运行两次或更多次...我仍然希望它在后台运行。

好的,我现在明白了。

您可能想使用java.util.concurrent.Executors.newSingleThreadExecutor()。该函数将返回一个带有单个工作线程的 线程池

工作线程将“在后台”运行您提交到池中的任务,并按照提交的顺序一次一个地运行它们。

static final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

public static void async(final int a) {
    singleThreadExecutor.submit(new Runnable() {
        @Override
        public void run() {
            meth(a);
        }
    });
}

【讨论】:

  • 我使用睡眠方法而不是长时间运行的操作来简化我的示例,我的问题是我的软件中有一个关键部分,无论哪个线程调用它,我都需要这个部分按顺序运行.为了简单起见,我不希望它同时运行两次或多次,但我仍然希望它在后台运行
  • 我很惊讶,您提供的解决方案完全符合我的要求,谢谢
【解决方案2】:

您在这里似乎有两个不同的问题。你首先说你“需要一个从后台只运行一次的方法,不管有多少线程调用它”,这是一个问题,然后你继续说不同线程的结果不会发生按照你想要的顺序。

问题 #1:让方法只运行一次

您没有具体说明“无论有多少线程调用它都只运行一次”的确切含义。当涉及到多线程编程时,这可能意味着一些不同的常见事物。由于您提供的示例代码,我假设您的意思是您一次只需要一个 em>,但您确实希望多次调用该方法。

对于这个问题,你是在正确的轨道上。如果您希望一次只能由一个线程访问某些数据或操作,那么正确的方法是使用锁,也称为互斥锁。在 Java 中,这是通过创建一个对象来充当锁来完成的。对象可以是任何类类型——那部分并不重要。重要的部分是所有需要互斥访问某些数据或操作的线程都使用锁定对象的相同实例。在访问数据或操作之前必须在对象上获得锁,然后必须在之后释放它。

现在,有了这个理论部分,您应用到您的方法的“同步”关键字就更容易解释了。当您将 synchronized 应用于 Java 方法时,会发生“this”对象,即“拥有”该方法的对象(在您的情况下,无论哪个对象实例拥有对 meth(int) 的调用),就是该对象用作锁。方法体执行前在this上获得锁,方法体执行完毕后释放锁。这一次只允许一个线程访问该代码。

但请记住,不同的线程需要具有相同的锁对象实例。如果您有多个类型的对象,那么对象的不同实例可以使它们的同步方法彼此同时运行,因为它们具有单独的锁

考虑到您已经在使用synchronized,这可能足以回答这部分问题。如需进一步阅读,请参阅我将在底部提供的链接。

问题 #2:为什么这些线程会出现乱序?

这个问题有一个快速而简单的答案:如果你没有专门做某事导致它不是这样,那么就不能保证线程会以任何特定的顺序发生。就这么简单。

在您的情况下,每个线程都会在它执行的第一件事时休眠一秒钟,但这会作为该线程操作的一部分发生。基本上,你所有的线程基本上都在同一时间休眠一秒钟,然后基本上同时为所有线程休眠一秒钟。并且不保证它们将按什么顺序执行。

如果您让它在新线程的实例化之间休眠,那么您可能会导致执行按您预期的顺序执行。也就是说,async(1); sleep; async(2); sleep; async(3); etc.. 可能会给您带来更像您所期望的结果,尽管这会在每次调用 async 之间休眠,因此需要很长时间才能完成所有事情。但是,请注意,这不是你应该完成你正在寻找的事情的方式。虽然你会可能如果你在调用之间睡眠时按照你想要的顺序获得输出async,即使在那时,您也无法保证获得该订单。例如,如果您的系统因其他活动而陷入困境,它可能会导致对meth 的一次调用需要超过两秒的时间,因此您有多个线程在等待锁定,并且您再次获得结果乱七八糟的。

如果没有某种类型的直接干预,您永远无法保证线程执行顺序。如果 T1 和 T2 都被执行,或者都在等待加入,或者出于任何原因想要运行,你永远不知道哪个会先发生。通常不需要知道。

如果您需要强制线程以特定顺序发生 - 为了最大限度地提高线程的活跃度,您通常希望避免这种情况,但有时这是必要的 - 然后尝试查看一些帮助获得您可能想要的时间的特殊 Java 并发对象。您可以在我将在下面提供的链接中找到其中一些。

在您提供给我们的非常具体的情况下,如果您只想要一个线性顺序(甚至是树状顺序),其中您有一组线程 {T1, T2, ..., Tn} 您可以使用join,就像你开始做的那样(你又接近了!),但是让每个线程加入它之前的线程,而不是全部加入一个线程。也就是说,让 T2 加入 T1,T3 加入 T2,以此类推。您可以通过这种方式获得线性执行,还可以获得树状模式:如果您只关心 T3 和 T4 发生在 T2 之后,但您不关心 T3 和 T4 之间哪个先发生,那么它们都可以加入 T2 -尽可能增加活力。

如果您的最终目标与您在这里的问题完全一样,它们都是线性执行的,那么您最好在一个线程中执行它但拥有一个线程需要执行的操作列表,那么一个线程可以按顺序执行这些事情,每次它执行所有操作并且没有任何操作时,它会等待更多操作。本段没有回答您的问题,但它是关于另一种方法的建议,根据您的用例可能会或可能不会更好。

还有我提到的链接...

Java Concurrency Trail (编辑:对不起,我第一次搞砸了链接。已修复。)

彻底检查该资源。请注意那里的目录,以帮助您浏览 Java 并发的子主题。起初可能难以掌握其中的一些示例,但您似乎正朝着正确的方向前进。坚持下去。

【讨论】:

  • 我的问题出现在第二部分,当你回答 如果你没有特别做某事导致它不是这样,那么不能保证线程会在任何情况下发生具体顺序。就这么简单。 是不是说没有办法按顺序进行线程调用?
  • @Mouhammad 默认情况下不保证订单。如果您希望它们按特定顺序排列,则需要做一些事情将它们按顺序排列。我试图在我的回答中做到彻底,但对于您的具体情况,最简单的方法可能是使用您开始使用的join,除非您需要将每个线程 join 设为它之前的线程而不是制作它们都加入同一个线程。也许给 async 一个 Thread 参数并使其也像Thread async(final int a, Thread t) 一样返回一个 Thread。
  • @Mouhammad 然后,在async 内部,您可以在t 上拥有新创建的线程join,并且您可以返回新创建的线程以传递给下一个async 调用.第一个将有t = null,因此您需要检查它,如果t == null,则 join。这只是一种方法。
  • 我通过添加您提到的方法的结果更新了我的问题,它仍然阻塞了我当前正在运行的线程,因为您看到“start”没有直接由“end”流动
猜你喜欢
  • 1970-01-01
  • 2010-10-19
  • 1970-01-01
  • 2013-11-08
  • 1970-01-01
  • 2014-05-06
  • 1970-01-01
  • 2015-07-02
相关资源
最近更新 更多