diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java
index 8ea8d72d97fd793667d4d0ea8831cb275a74c804..a4e26e084cb26a2b33636465fd06b613b18346ba 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_C.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -39,11 +39,13 @@ public class JNI_C implements CRFFI {
          */
         @Override
         @TruffleBoundary
-        public synchronized void invoke(NativeCallInfo nativeCallInfo, Object[] args) {
-            if (traceEnabled()) {
-                traceDownCall(nativeCallInfo.name, args);
+        public void invoke(NativeCallInfo nativeCallInfo, Object[] args) {
+            synchronized (JNI_CRFFINode.class) {
+                if (traceEnabled()) {
+                    traceDownCall(nativeCallInfo.name, args);
+                }
+                c(nativeCallInfo.address.asAddress(), args);
             }
-            c(nativeCallInfo.address.asAddress(), args);
         }
     }
 
diff --git a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java
index ee4a044a1f19a7438b001314ff4bc6c0a47718fc..7aae50716574d0dfd8ea5be6e62a49f5bcdd7fb7 100644
--- a/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java
+++ b/com.oracle.truffle.r.runtime.ffi/src/com/oracle/truffle/r/runtime/ffi/jni/JNI_Call.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -45,7 +45,9 @@ import com.oracle.truffle.r.runtime.ffi.UpCallsRFFIFactory;
  * they are passed as an array and the JNI code has to call back to get the args (not very
  * efficient).
  *
- * The JNI layer is not (currently) MT safe, so all calls are single threaded.
+ * The JNI layer is not (currently) MT safe, so all calls are single threaded. N.B. Since the calls
+ * take place from a {@link JNI_CallRFFINode}, and this is duplicated in separate contexts, we must
+ * synchronize on the class.
  */
 public class JNI_Call implements CallRFFI {
 
@@ -53,14 +55,15 @@ public class JNI_Call implements CallRFFI {
 
         @Override
         @TruffleBoundary
-        public synchronized Object invokeCall(NativeCallInfo nativeCallInfo, Object[] args) {
-            long address = nativeCallInfo.address.asAddress();
-            Object result = null;
-            if (traceEnabled()) {
-                traceDownCall(nativeCallInfo.name, args);
-            }
-            try {
-                switch (args.length) {
+        public Object invokeCall(NativeCallInfo nativeCallInfo, Object[] args) {
+            synchronized (JNI_CallRFFINode.class) {
+                long address = nativeCallInfo.address.asAddress();
+                Object result = null;
+                if (traceEnabled()) {
+                    traceDownCall(nativeCallInfo.name, args);
+                }
+                try {
+                    switch (args.length) {
             // @formatter:off
             case 0: result = call0(address); break;
             case 1: result = call1(address, args[0]); break;
@@ -75,60 +78,67 @@ public class JNI_Call implements CallRFFI {
             default:
                 result = call(address, args); break;
                 // @formatter:on
-                }
-                return result;
-            } finally {
-                if (traceEnabled()) {
-                    traceDownCallReturn(nativeCallInfo.name, result);
+                    }
+                    return result;
+                } finally {
+                    if (traceEnabled()) {
+                        traceDownCallReturn(nativeCallInfo.name, result);
+                    }
                 }
             }
         }
 
         @Override
         @TruffleBoundary
-        public synchronized void invokeVoidCall(NativeCallInfo nativeCallInfo, Object[] args) {
-            if (traceEnabled()) {
-                traceDownCall(nativeCallInfo.name, args);
-            }
-            long address = nativeCallInfo.address.asAddress();
-            try {
-                switch (args.length) {
-                    case 0:
-                        callVoid0(address);
-                        break;
-                    case 1:
-                        callVoid1(address, args[0]);
-                        break;
-                    default:
-                        throw RInternalError.shouldNotReachHere();
-                }
-            } finally {
+        public void invokeVoidCall(NativeCallInfo nativeCallInfo, Object[] args) {
+            synchronized (JNI_CallRFFINode.class) {
                 if (traceEnabled()) {
-                    traceDownCallReturn(nativeCallInfo.name, null);
+                    traceDownCall(nativeCallInfo.name, args);
+                }
+                long address = nativeCallInfo.address.asAddress();
+                try {
+                    switch (args.length) {
+                        case 0:
+                            callVoid0(address);
+                            break;
+                        case 1:
+                            callVoid1(address, args[0]);
+                            break;
+                        default:
+                            throw RInternalError.shouldNotReachHere();
+                    }
+                } finally {
+                    if (traceEnabled()) {
+                        traceDownCallReturn(nativeCallInfo.name, null);
+                    }
                 }
             }
         }
 
         @Override
-        public synchronized void setTempDir(String tempDir) {
-            if (traceEnabled()) {
-                traceDownCall("setTempDir", tempDir);
-            }
-            RFFIVariables.setTempDir(tempDir);
-            nativeSetTempDir(tempDir);
-            if (traceEnabled()) {
-                traceDownCallReturn("setTempDir", null);
+        public void setTempDir(String tempDir) {
+            synchronized (JNI_CallRFFINode.class) {
+                if (traceEnabled()) {
+                    traceDownCall("setTempDir", tempDir);
+                }
+                RFFIVariables.setTempDir(tempDir);
+                nativeSetTempDir(tempDir);
+                if (traceEnabled()) {
+                    traceDownCallReturn("setTempDir", null);
+                }
             }
         }
 
         @Override
-        public synchronized void setInteractive(boolean interactive) {
-            if (traceEnabled()) {
-                traceDownCall("setInteractive", interactive);
-            }
-            nativeSetInteractive(interactive);
-            if (traceEnabled()) {
-                traceDownCallReturn("setInteractive", null);
+        public void setInteractive(boolean interactive) {
+            synchronized (JNI_CallRFFINode.class) {
+                if (traceEnabled()) {
+                    traceDownCall("setInteractive", interactive);
+                }
+                nativeSetInteractive(interactive);
+                if (traceEnabled()) {
+                    traceDownCallReturn("setInteractive", null);
+                }
             }
         }
 
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java
index 181da4cc9d31082accd86f6a2728539eb311a475..bff24d02281e3874c40b0c6648be35a83b339980 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1995-2012, The R Core Team
  * Copyright (c) 2003, The R Foundation
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -382,7 +382,7 @@ public class DLL {
      * R_DEFAULT_PACKAGES do throw RErrors.
      */
 
-    private static DLLInfo doLoad(String absPath, boolean local, boolean now, boolean addToList) throws DLLException {
+    private static synchronized DLLInfo doLoad(String absPath, boolean local, boolean now, boolean addToList) throws DLLException {
         Object handle = RFFIFactory.getRFFI().getDLLRFFI().dlopen(absPath, local, now);
         if (handle == null) {
             String dlError = RFFIFactory.getRFFI().getDLLRFFI().dlerror();
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLLRFFI.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLLRFFI.java
index 3659d2f7e2ec07bd0cd62c0a750fcaa18d1f792c..09e61c5bb0bb1d7d8122caf034cd3d04ee34f047 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLLRFFI.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLLRFFI.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,12 @@ package com.oracle.truffle.r.runtime.ffi;
 
 import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle;
 
+/**
+ * Caller should not assume that this interface is implemented in a thread-safe manner. In
+ * particular, pairs of {@link #dlopen}/{@link #dlerror} and {@link #dlsym}/{@link #dlerror} should
+ * be atomic in the caller.
+ *
+ */
 public interface DLLRFFI {
     /**
      * Open a DLL.