Java并发问题及如何避免

当代码由多个线程执行时,会出现并发问题。然后,与单线程执行相比,您的代码的行为可能会有所不同,具体取决于何时和哪个线程访问变量。

这是一个例子:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ConcurrentTest {
	static int counter = 0;

	public static void main(String[] args) throws InterruptedException {

		ExecutorService service = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 1000; i++)
			service.submit(() -> ++counter);
		service.shutdown();
		service.awaitTermination(10, TimeUnit.SECONDS);
		if (service.isShutdown())
			System.out.printf("Total count: %d", counter);
	}
}

在上面的代码中:

  • 从0开始有一个静态变量计数器。
  • 创建具有10个线程的固定线程池执行程序(服务)。在多个线程中执行代码是必需的。
  • 创建具有1000次重复的迭代以增加计数器变量。
  • 该服务将关闭,等待时间为10秒。
  • 一旦我们确认服务已关闭,便会打印总数。

在单个线程中执行的同一代码将使计数器变为1000。您可以通过使用Executors.newFixedThreadPool(1)实例化服务来进行验证。

但是,在有多个线程的情况下(在我们的示例中为10个),当您执行上述代码时,计数器值会有所不同–根据随机因素,它可能是600、900或其他任何值。

 

这种随机性的原因是增量操作++不是线程安全的。这意味着一个线程获取counter的值,开始递增它的值,同时一个或多个线程也获取相同的counter值并递增。

当第一个线程完成计数器的递增时,它将返回旧值+1。其余线程也一样。因此,如果当两个或多个线程同时尝试增加计数器值时计数器值为111,则当所有并发线程完成时该计数器值为112。

为了克服这个问题,有一些原子类。原子意味着将执行一个操作而不会受到其他线程的干扰。在我们的示例中,我们必须像这样使用AtomicInteger代替原始int:


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

	static AtomicInteger counter = new AtomicInteger(0);

	public static void main(String[] args) throws InterruptedException {

		ExecutorService service = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 1000; i++)
			service.submit(() -> counter.incrementAndGet());
		service.shutdown();
		service.awaitTermination(10, TimeUnit.SECONDS);
		if (service.isShutdown())
			System.out.printf("Total count: %d", counter.get());
	}
}
 

在上面的代码中,对于原始类型整数,AtomicInteger方法crementAndAndGet()等于++,确保每个线程自动获取,递增和设置值,即不干扰其他线程。

与AtomicInteger相似,还有一个用于布尔值的AtomicBoolean类,一个用于long的AtomicLong等。对于集合,有一个ConcurrentHashMap用于哈希图,CopyOnWriteArrayList用于列表等等。这个想法很相似–在使用共享对象时确保线程安全。每个类分别具有不同的方法来帮助您执行可用的原子操作。

原子类的使用似乎是一个非常简单的例程,并且人们可能想知道为什么首先要有非原子类。原因之一是在时间上运行原子操作会产生成本。每个线程将必须与其余线程同步,并在必要时等待它们。设计代码时请牢记这一点,并确保与单线程执行相比,多线程不会使程序变慢,因为线程正在等待共享资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值