Это абсолютно зависит от библиотеки C. Если функция не хранит никакое состояние (например, просто вычисляет некоторый результат и использует только локальные переменные в стеке), будет безопасно использовать его из нескольких потоков. В противном случае вы должны позаботиться о том, чтобы состояние сохранялось отдельно для каждого потока, например, используя malloc и возвращая дескриптор для дальнейших вызовов, чтобы последующие вызовы повторно использовали состояние в блокированном блоке памяти.
Если я не знаю внутреннюю часть кода, я предпочитаю создавать небольшое приложение C и вызывать его с помощью Runtime.exec, но это полезно, только если функция C выполняет достаточно работы, чтобы оправдать накладные расходы exec.