Bro*_*bas 10 java multithreading counter thread-safety
我从一个非常简单的多线程示例开始.我正在尝试制作线程安全计数器.我想创建两个线程,间歇性地递增计数器以达到1000.代码如下:
public class ThreadsExample implements Runnable {
static int counter = 1; // a global counter
public ThreadsExample() {
}
static synchronized void incrementCounter() {
System.out.println(Thread.currentThread().getName() + ": " + counter);
counter++;
}
@Override
public void run() {
while(counter<1000){
incrementCounter();
}
}
public static void main(String[] args) {
ThreadsExample te = new ThreadsExample();
Thread thread1 = new Thread(te);
Thread thread2 = new Thread(te);
thread1.start();
thread2.start();
}
}
Run Code Online (Sandbox Code Playgroud)
据我所知,while循环现在意味着只有第一个线程可以访问计数器,直到达到1000.输出:
Thread-0: 1
.
.
.
Thread-0: 999
Thread-1: 1000
Run Code Online (Sandbox Code Playgroud)
我该如何解决这个问题?如何让线程共享计数器?
ini*_*mfs 19
两个线程都可以访问您的变量.
您所看到的现象称为线程饥饿.在进入代码的受保护部分时(抱歉我之前错过了),其他线程将需要阻塞,直到持有监视器的线程完成(即监视器被释放时).虽然可以预期当前线程将监视器传递给排队等待的下一个线程,但对于同步块,java不保证下一个线程接收监视器的任何公平性或排序策略.完全可能(甚至可能)一个线程释放并尝试重新获取监视器,以便在另一个等待一段时间的线程上获取它.
来自Oracle:
Starvation描述了一种情况,即线程无法获得对共享资源的定期访问,并且无法取得进展.当"贪婪"线程使共享资源长时间不可用时,就会发生这种情况.例如,假设一个对象提供了一个通常需要很长时间才能返回的同步方法.如果一个线程经常调用此方法,则通常还需要阻止对同一对象进行频繁同步访问的其他线程.
虽然你的两个线程都是"贪婪"线程的例子(因为它们反复释放并重新获取监视器),但是技术上首先启动线程0,从而使线程1处于饥饿状态.
解决方案是使用支持公平性的并发同步方法(例如ReentrantLock),如下所示:
public class ThreadsExample implements Runnable {
static int counter = 1; // a global counter
static ReentrantLock counterLock = new ReentrantLock(true); // enable fairness policy
static void incrementCounter(){
counterLock.lock();
// Always good practice to enclose locks in a try-finally block
try{
System.out.println(Thread.currentThread().getName() + ": " + counter);
counter++;
}finally{
counterLock.unlock();
}
}
@Override
public void run() {
while(counter<1000){
incrementCounter();
}
}
public static void main(String[] args) {
ThreadsExample te = new ThreadsExample();
Thread thread1 = new Thread(te);
Thread thread2 = new Thread(te);
thread1.start();
thread2.start();
}
}
Run Code Online (Sandbox Code Playgroud)
请注意删除synchronized关键字以支持方法中的ReentrantLock.具有公平策略的这种系统允许长等待线程有机会执行,从而消除饥饿.
Ber*_*ers 13
你可以使用AtomicInteger.它是一个可以原子递增的类,因此调用其increment方法的两个单独的线程不会交错.
public class ThreadsExample implements Runnable {
static AtomicInteger counter = new AtomicInteger(1); // a global counter
public ThreadsExample() {
}
static void incrementCounter() {
System.out.println(Thread.currentThread().getName() + ": " + counter.getAndIncrement());
}
@Override
public void run() {
while(counter.get() < 1000){
incrementCounter();
}
}
public static void main(String[] args) {
ThreadsExample te = new ThreadsExample();
Thread thread1 = new Thread(te);
Thread thread2 = new Thread(te);
thread1.start();
thread2.start();
}
}
Run Code Online (Sandbox Code Playgroud)