如果一个同步方法调用另一个非同步方法,非同步方法是不是有锁

Posted

技术标签:

【中文标题】如果一个同步方法调用另一个非同步方法,非同步方法是不是有锁【英文标题】:If a synchronized method calls another non-synchronized method, is there a lock on the non-synchronized method如果一个同步方法调用另一个非同步方法,非同步方法是否有锁 【发布时间】:2012-03-20 12:26:46 【问题描述】:

在Java中,如果一个同步方法包含对一个非同步方法的调用,那么另一个方法还能同时访问非同步方法吗?基本上我要问的是同步方法中的所有内容都有锁定(包括对其他同步方法的调用)?

【问题讨论】:

【参考方案1】:

如果一个同步方法调用另一个非同步方法,非同步方法是否有锁

答案取决于上下文。

如果您在一个对象的synchronized 方法中,则其他线程对同样为synchronized 的同一对象实例的其他方法的调用将被锁定。然而,其他线程对非同步方法的调用是锁定的——任何人都可以同时调用它们。

public synchronized void someSynchronizedMethod() 
    ...
    someNonSynchronizedMethod();
    ...


// anyone can call this method even if the someSynchronizedMethod() method has
// been called and the lock has been locked
public void someNonSynchronizedMethod() 
   ...

此外,如果您调用 someSynchronizedMethod() 但恰好在 someNonSynchronizedMethod() 方法内,您仍然持有锁。进入同步方法(或块)时启用锁,退出该方法时禁用。你可以调用其他各种不同步的方法,它们仍然会被锁定。

但是您在问题中提出了两个不同的问题:

在Java中,如果一个同步方法包含对一个非同步方法的调用,那么另一个方法还能同时访问非同步方法吗?

是的。其他方法可以访问非同步方法。

基本上我要问的是同步方法中的所有内容都有锁定(包括对其他同步方法的调用)?

嗯,是的。对同步方法的其他调用被锁定。但非同步方法不会被锁定。

另外,请记住,如果方法是 static,则锁定在 ClassLoader 中的 Class 对象上。

// this locks on the Class object in the ClassLoader
public static synchronized void someStaticMethod() 

如果方法是实例方法,那么锁就在类的实例上。

// this locks on the instance object that contains the method
public synchronized void someInstanceMethod() 

这两种情况下有两种不同的锁。

最后,当您处理synchronized 实例方法时,该类的每个instance 都是被锁定的。这意味着两个线程可以同时在同一个synchronized 方法中使用不同的instances。但是如果 2 个线程尝试对同一实例上的 synchronized 方法进行操作,则其中一个线程将阻塞,直到另一个线程退出该方法。

【讨论】:

所以我想在同步方法中调用非同步方法不是一个好主意……因为它们可以同时被其他方法调用。 如果这导致2个线程访问需要同步的数据,那么是的,这不是一个好主意。但是,toString() 和其他方法总是会发生这种情况。这仅取决于您的对象和用例。 我还想在这里补充一点,只要在不同的对象上调用同步方法,就可以同时调用任意次数。锁适用于特定对象——类的实例——除非方法是静态的。我认为很容易忘记这一点,因为很多人(包括所有当前的发帖人)都说方法上的锁定,就好像不管对象实例如何都应用它一样。【参考方案2】:

如果线程 A 调用同步方法 M1,而后者又调用非同步方法 M2,那么线程 B 仍然可以调用 M2 而不会阻塞。

同步方法在调用它的对象上获取和释放内在锁。这就是它可能会阻塞的原因。非同步方法不会尝试获取任何锁(除非在代码中明确完成)。

因此,如果您还需要确保 M2 的互斥,则无论其调用方(如 M1)是否同步,都应使其同步。

【讨论】:

【参考方案3】:

锁属于线程,而不属于方法(或更准确地说,它的堆栈帧)。碰巧的是,如果你有一个同步方法,你可以保证线程在方法体开始之前拥有锁,然后释放它。

另一个线程仍然可以调用第二个非同步方法。任何线程都可以随时调用非同步方法。

【讨论】:

【参考方案4】:

锁不属于线程。锁实际上属于对象(或类级别锁的情况下的类),并且线程在同步上下文中获取对象(或类级别锁的情况下的类)上的锁。 现在,正如上面讨论的那样,Java 中没有锁传播。这是一个小演示:

public class TestThread 

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException 
        // TODO Auto-generated method stub
        ThreadCreator1 threadCreator1 = new ThreadCreator1();
        ThreadCreator2 threadCreator2 = new ThreadCreator2();

        Thread t1 = new Thread(threadCreator1,"Thread 1");
        Thread t3 = new Thread(threadCreator1,"Thread 3");
        Thread t2 = new Thread(threadCreator2,"Thread 2");

        t1.start();
        Thread.sleep(2000);
        t3.start();

    

public class ThreadCreator1 implements Runnable 

    private static final Task task= new Task();
    private static final Task2 task2= new Task2();

    @Override

    public void run() 

        try 

            if(Thread.currentThread().getName().equals("Thread 1"))
                task.startTask2(task2);
            if(Thread.currentThread().getName().equals("Thread 3"))
                task2.startTask();

         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        // TODO Auto-generated method stub

        /**/

        
    

public class Task 

    public static final Task task = new Task();
    public static List<String> dataList = new ArrayList<String>();
    ReentrantLock lock =  new ReentrantLock();



    public  void startTask2(Task2 task2) throws InterruptedException
    

        try

            lock.lock();
            //new Task2().startTask();
            task2.startTask();
        
        catch(Exception e)
        

        
        finally
            lock.unlock();
        
    


public class Task2 

    ReentrantLock lock = new ReentrantLock();
    public  void startTask() throws InterruptedException
    

        try
            //lock.lock();
            for(int i =0 ;i< 10;i++)
        
            System.out.println(" *** Printing i:"+i+" for:"+Thread.currentThread().getName());
            Thread.sleep(1000);
        
        
        catch(Exception e)
        

        
        /*finally
        
            lock.unlock();
        */
    


只是我在这里使用了可重入锁。 如果运行上面的代码,那么线程 1 和线程 3 之间会有交错,但是如果 Task2 类的锁部分没有被注释掉,那么就不会交错,首先获得锁的线程会先完全完成,然后它将释放锁,然后另一个线程可以继续。

【讨论】:

以上是关于如果一个同步方法调用另一个非同步方法,非同步方法是不是有锁的主要内容,如果未能解决你的问题,请参考以下文章

同步与异步,阻塞与非阻塞

同步与异步并发和并行阻塞和非阻塞

区分同步代码块静态同步方法非静态同步方法的锁

ReleaseMutex:从非同步代码块调用对象同步方法

如果不修改静态类变量,非同步静态方法是不是线程安全?

从另一个同步方法调用同步方法是不是安全?