如何在Java中使用同步方法?

时间:2016-07-12 12:00:21

标签: java multithreading asynchronous synchronization

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

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中实现这些结果

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

代码:

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

背景工作也被取消了。

2 个答案:

答案 0 :(得分:2)

你的主线程启动了另外九个,给每个子线程一个不同的a,并且所有九个子线程做的第一件事就是睡一秒钟。

Thread.sleep()调用中的时序分辨率是未定义的---它取决于底层操作系统---并且很可能所有线程都可以在系统的相同时钟唤醒时钟。

Java不保证线程将按照Thread对象被start()编辑的顺序开始运行,并且无法保证它们将按照它们进入的顺序唤醒睡眠。

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

  

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

好的,我现在明白了。

您可能想要使用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);
        }
    });
}

答案 1 :(得分:1)

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

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

你没有具体说明你的意思是什么"无论有多少线程都叫它,它只运行一次。"当谈到多线程编程时,可能会有一些不同的常见问题。因为您提供的示例代码,我将假设您的意思是您一次只需要一个 ,但您确实需要多次调用该方法。

对于这个问题,你走在正确的轨道上。如果您希望一次只能访问一个线程的某些数据或操作,那么正确的方法是使用锁,也称为互斥锁。在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并发的子主题。有些例子一开始可能很难掌握,但你似乎正朝着正确的方向前进。坚持下去。