What are the possible ways to make code thread-safe without using the synchronized
keyword?
7 回答
其实方法很多:
- 如果您没有可变状态,则根本不需要同步。
- 如果可变状态仅限于单个线程,则无需同步。这可以通过使用局部变量或
java.lang.ThreadLocal
. - 您还可以使用内置同步器。
java.util.concurrent.locks.ReentrantLock
与使用块和方法时访问的锁具有相同的功能synchronized
,并且功能更强大。
只有方法本地的变量/引用。或者确保任何实例变量都是不可变的。
您可以通过使所有数据不可变来使您的代码线程安全,如果没有可变性,那么一切都是线程安全的。
其次,您可能想看看 java 并发 API,它提供了读/写锁,在有很多读者和少数写者的情况下表现更好。纯同步关键字也会阻止两个阅读器。
////////////FIRST METHOD USING SINGLE boolean//////////////
public class ThreadTest implements Runnable {
ThreadTest() {
Log.i("Ayaz", "Constructor..");
}
private boolean lockBoolean = false;
public void run() {
Log.i("Ayaz", "Thread started.." + Thread.currentThread().getName());
while (lockBoolean) {
// infinite loop for other thread if one is accessing
}
lockBoolean = true;
synchronizedMethod();
}
/**
* This method is synchronized without using synchronized keyword
*/
public void synchronizedMethod() {
Log.e("Ayaz", "processing...." + Thread.currentThread().getName());
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
System.out.println("Exp");
}
Log.e("Ayaz", "complete.." + Thread.currentThread().getName());
lockBoolean = false;
}
} //end of ThreadTest class
//For testing use below line in main method or in Activity
ThreadTest threadTest = new ThreadTest();
Thread threadA = new Thread(threadTest, "A thead");
Thread threadB = new Thread(threadTest, "B thead");
threadA.start();
threadB.start();
///////////SECOND METHOD USING TWO boolean/////////////////
public class ThreadTest implements Runnable {
ThreadTest() {
Log.i("Ayaz", "Constructor..");
}
private boolean isAnyThreadInUse = false;
private boolean lockBoolean = false;
public void run() {
Log.i("Ayaz", "Thread started.." + Thread.currentThread().getName());
while (!lockBoolean)
if (!isAnyThreadInUse) {
isAnyThreadInUse = true;
synchronizedMethod();
lockBoolean = true;
}
}
/**
* This method is synchronized without using synchronized keyword
*/
public void synchronizedMethod() {
Log.e("Ayaz", "processing...." + Thread.currentThread().getName());
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
System.out.println("Exp");
}
Log.e("Ayaz", "complete.." + Thread.currentThread().getName());
isAnyThreadInUse = false;
}
} // end of ThreadTest class
//For testing use below line in main method or in Activity
ThreadTest threadTest = new ThreadTest();
Thread t1 = new Thread(threadTest, "a thead");
Thread t2 = new Thread(threadTest, "b thead");
t1.start();
t2.start();
为了保持可预测性,您必须确保对可变数据的所有访问都是按顺序进行的,或者处理由并行访问引起的问题。
最粗暴的保护使用synchronized
关键字。除此之外,至少有两层可能性,每一层都有其好处。
锁/信号量
这些可能非常有效。例如,如果您有一个由多个线程读取但仅由一个线程更新的结构,您可能会发现它ReadWriteLock
很有用。
如果您选择与算法匹配的锁,则锁的效率会更高。
原子
例如,使用AtomicReference
通常可以提供完全无锁的功能。这通常可以提供巨大的好处。
原子背后的原因是允许它们失败,但告诉您它们以您可以处理的方式失败。
例如,如果您想更改一个值,您可以读取它,然后写入它的新值,只要它仍然是旧值。这称为“比较和设置” cas
,通常可以在硬件中实现,因此非常有效。然后,您需要的只是:
long old = atomic.get();
while ( !atomic.cas(old, old+1) ) {
// The value changed between my get and the cas. Get it again.
old = atomic.get();
}
但是请注意,可预测性并不总是必需的。
那么有很多方法可以实现这一点,但每种方法都包含多种口味。Java 8 还附带了新的并发特性。可以确保线程安全的一些方法是:
Semaphores
Locks- Reentrantlock,ReadWriteLock,StampedLock
(Java 8)
为什么你需要这样做?
仅使用局部变量/引用并不能解决大多数复杂的业务需求。此外,如果实例变量是不可变的,它们的引用仍然可以被其他线程更改。
一种选择是使用SingleThreadModel之类的东西,但强烈建议不要使用它。
您还可以查看Kal上面建议的并发 api