Java 以同步、线程安全的方式加载共享资源
问题 我只想加载一次共享资源并将它们保存在内存中。目前,我正在使用一种同步方法进行加载,并使用HashMap将加载的资源保存在内存中 问题 问题1: 有没有更好的方法使用标准Java方法来实现这一点 问题2: 下面的代码生成6个线程,其中3个访问资源1,另3个访问资源2。日志输出如下所示:Java 以同步、线程安全的方式加载共享资源,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,问题 我只想加载一次共享资源并将它们保存在内存中。目前,我正在使用一种同步方法进行加载,并使用HashMap将加载的资源保存在内存中 问题 问题1: 有没有更好的方法使用标准Java方法来实现这一点 问题2: 下面的代码生成6个线程,其中3个访问资源1,另3个访问资源2。日志输出如下所示: Get resource id: 1 Load resource id: 1 Counter: 1 Get resource id: 2 Thread Thread[Thread-0,5,main], loa
Get resource id: 1
Load resource id: 1
Counter: 1
Get resource id: 2
Thread Thread[Thread-0,5,main], loaded resource id: 1
Load resource id: 2
Counter: 2
Thread Thread[Thread-5,5,main], loaded resource id: 2
Get resource id: 2
Thread Thread[Thread-4,5,main], loaded resource id: 2
Get resource id: 2
Thread Thread[Thread-3,5,main], loaded resource id: 2
Get resource id: 1
Get resource id: 1
Thread Thread[Thread-2,5,main], loaded resource id: 1
Thread Thread[Thread-1,5,main], loaded resource id: 1
问题是加载资源id 1的线程被阻塞,直到加载资源id 2的线程完成。即使资源id为2仍在加载,如何允许资源id为1的线程继续
代码
下面是示例代码:
import java.util.HashMap;
import java.util.Map;
public class SharedResourceLoader {
/**
* Resource loading counter, shows how often the loading is invoked.
*/
public static int loadCounter = 0;
/**
* Map of type <Resource Id, Resource> for keeping loaded resources in memory
*/
public static Map<Integer,Resource> resourceMap = new HashMap<>();
/**
* Get a resource by Id
* @param id
* @return
*/
public static Resource getResource( int resourceId) {
Resource resource = resourceMap.get( resourceId);
if( resource == null) {
resource = loadResource( resourceId);
}
return resource;
}
/**
* Get a resource by Id synchronized. If it isn't found, load it.
* @param resourceId
* @return
*/
public static synchronized Resource loadResource( int resourceId) {
System.out.println("Get resource id: " + resourceId);
Resource resource = resourceMap.get( resourceId);
if( resource == null) {
System.out.println("Load resource id: " + resourceId);
// load resource
resource = new Resource( resourceId);
resource.load();
// keep resource in memory
resourceMap.put( resourceId, resource);
// just a counter to see how often this method is accessed
loadCounter++;
System.out.println("Counter: " + loadCounter);
}
return resource;
}
/**
* Start a thread that accesses the resource with the given id
* @param resourceId
*/
public static void spawnThread( int resourceId) {
Thread thread = new Thread( new Runnable() {
@Override
public void run() {
Resource resource = getResource(resourceId);
System.out.println( "Thread " + Thread.currentThread() + ", loaded resource id: " + resource.id);
}
});
thread.start();
}
public static class Resource {
int id;
public Resource( int id) {
this.id = id;
}
public void load() {
// dummy sleep, e. g. resource loading happens here
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) {
spawnThread( 1);
spawnThread( 1);
spawnThread( 1);
spawnThread( 2);
spawnThread( 2);
spawnThread( 2);
}
}
import java.util.HashMap;
导入java.util.Map;
公共类SharedResourceLoader{
/**
*资源加载计数器,显示调用加载的频率。
*/
公共静态int loadCounter=0;
/**
*将加载的资源保留在内存中的类型映射
*/
public static Map resourceMap=new HashMap();
/**
*通过Id获取资源
*@param-id
*@返回
*/
公共静态资源getResource(int resourceId){
Resource=resourceMap.get(resourceId);
if(资源==null){
resource=loadResource(resourceId);
}
返回资源;
}
/**
*按Id同步获取资源。如果找不到,请加载它。
*@param resourceId
*@返回
*/
公共静态同步资源loadResource(int resourceId){
System.out.println(“获取资源id:+resourceId”);
Resource=resourceMap.get(resourceId);
if(资源==null){
System.out.println(“加载资源id:+resourceId”);
//加载资源
资源=新资源(资源ID);
resource.load();
//将资源保存在内存中
resourceMap.put(resourceId,resource);
//只需一个计数器即可查看访问此方法的频率
loadCounter++;
System.out.println(“计数器:“+loadCounter”);
}
返回资源;
}
/**
*启动使用给定id访问资源的线程
*@param resourceId
*/
公共静态线程(int-resourceId){
Thread Thread=新线程(new Runnable(){
@凌驾
公开募捐{
Resource=getResource(resourceId);
System.out.println(“线程”+Thread.currentThread()+”,加载的资源id:“+resource.id”);
}
});
thread.start();
}
公共静态类资源{
int-id;
公共资源(int id){
this.id=id;
}
公共空荷载(){
//虚拟睡眠,例如资源加载在这里发生
试一试{
睡眠(5000);
}捕捉(中断异常e){
}
}
}
公共静态void main(字符串[]args){
产卵线程(1);
产卵线程(1);
产卵线程(1);
产卵线程(2);
产卵线程(2);
产卵线程(2);
}
}
有没有更好的方法使用标准Java方法来实现这一点
可能吧,但这取决于你的要求。我倾向于在ConcurrentMap
上使用computeIfAbsent
延迟加载所需的值
e、 g
静态最终ConcurrentMap=新ConcurrentHashMap();
静态资源loadResource(int resourceId){
返回map.computeIfAbsent(资源ID,r->{
资源=新资源(r);
resource.load();
返回资源;
}
}
这将允许并发访问不同的密钥,但如果正在加载密钥,它将阻止在加载之前尝试使用它的任何其他线程
即使资源id为2仍在加载,如何允许资源id为1的线程继续
假设线程1不需要线程2正在加载的资源,如果它是相同的资源,则可以执行此操作。如果您引用的是不同的资源,请参见上文。您可以检查数据是否正在加载,以及数据是否正在加载,而不尝试在映射中查找。很可能这不是一个好主意
有没有更好的方法使用标准Java方法来实现这一点
可能吧,但这取决于您的需求。我倾向于在ConcurrentMap
上使用computeIfAbsent
延迟加载所需的值
e、 g
静态最终ConcurrentMap=新ConcurrentHashMap();
静态资源loadResource(int resourceId){
返回map.computeIfAbsent(资源ID,r->{
资源=新资源(r);
resource.load();
返回资源;
}
}
这将允许并发访问不同的密钥,但如果正在加载密钥,它将阻止在加载之前尝试使用它的任何其他线程
即使资源id为2仍在加载,如何允许资源id为1的线程继续
假设线程1不需要线程2正在加载的资源,如果它是相同的资源,则可以执行此操作。如果您引用的是不同的资源,请参见上文。您可以检查数据是否正在加载,并且不尝试查看映射。这很可能不是一个好主意。请查看
java.util.nachrrent
和java.util.concurrent.atomic
包
如果我是你,我会使用ConcurrentHashMap
:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class SharedResourceLoader {
/**
* Resource loading counter, shows how often the loading is invoked.
*/
private static final AtomicInteger loadCounter = new AtomicInteger();
/**
* Map of type <Resource Id, Resource> for keeping loaded resources in memory
*/
public static ConcurrentMap<Integer, Resource> resourceMap = new ConcurrentHashMap<>();
/**
* Get a resource by Id
*
* @param resourceId
* @return
*/
public static Resource getResource(int resourceId) {
Resource resource = resourceMap.get(resourceId);
if (resource == null) {
resource = loadResource(resourceId);
}
return resource;
}
/**
* Get a resource by Id synchronized. If it isn't found, load it.
*
* @param resourceId
* @return
*/
public static Resource loadResource(int resourceId) {
System.out.println("Get resource id: " + resourceId);
Resource resource = resourceMap.get(resourceId);
if (resource == null) {
System.out.println("Load resource id: " + resourceId);
// load resource
final Resource r = resourceMap.putIfAbsent(resourceId, resource = new Resource(resourceId));
// important!
if (r != null) {
resource = r;
}
if (resource.load()) {
// just a counter to see how often this method is accessed
loadCounter.getAndIncrement();
}
System.out.println("Counter: " + loadCounter);
}
return resource;
}
public static int loadCounter() {
return loadCounter.get();
}
/**
* Start a thread that accesses the resource with the given id
*
* @param resourceId
*/
public static void spawnThread(int resourceId) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Resource resource = getResource(resourceId);
System.out.println("Thread " + Thread.currentThread() + ", loaded resource id: " + resource.id);
}
});
thread.start();
}
public static class Resource {
final int id;
final AtomicBoolean loaded = new AtomicBoolean(false);
public Resource(int id) {
this.id = id;
}
public boolean load() {
if (loaded.compareAndSet(false, true)) {
// dummy sleep, e. g. resource loading happens here
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
return true;
}
return false;
}
}
public static void main(String[] args) {
spawnThread(1);
spawnThread(1);
spawnThread(1);
spawnThread(2);
spawnThread(2);
spawnThread(2);
}
}
只需看看
java.util.concurrent
和java.util.concurrent.atomic
包
如果我是你,我会使用ConcurrentHashMap
:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class SharedResourceLoader {
/**
* Resource loading counter, shows how often the loading is invoked.
*/
private static final AtomicInteger loadCounter = new AtomicInteger();
/**
* Map of type <Resource Id, Resource> for keeping loaded resources in memory
*/
public static ConcurrentMap<Integer, Resource> resourceMap = new ConcurrentHashMap<>();
/**
* Get a resource by Id
*
* @param resourceId
* @return
*/
public static Resource getResource(int resourceId) {
Resource resource = resourceMap.get(resourceId);
if (resource == null) {
resource = loadResource(resourceId);
}
return resource;
}
/**
* Get a resource by Id synchronized. If it isn't found, load it.
*
* @param resourceId
* @return
*/
public static Resource loadResource(int resourceId) {
System.out.println("Get resource id: " + resourceId);
Resource resource = resourceMap.get(resourceId);
if (resource == null) {
System.out.println("Load resource id: " + resourceId);
// load resource
final Resource r = resourceMap.putIfAbsent(resourceId, resource = new Resource(resourceId));
// important!
if (r != null) {
resource = r;
}
if (resource.load()) {
// just a counter to see how often this method is accessed
loadCounter.getAndIncrement();
}
System.out.println("Counter: " + loadCounter);
}
return resource;
}
public static int loadCounter() {
return loadCounter.get();
}
/**
* Start a thread that accesses the resource with the given id
*
* @param resourceId
*/
public static void spawnThread(int resourceId) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Resource resource = getResource(resourceId);
System.out.println("Thread " + Thread.currentThread() + ", loaded resource id: " + resource.id);
}
});
thread.start();
}
public static class Resource {
final int id;
final AtomicBoolean loaded = new AtomicBoolean(false);
public Resource(int id) {
this.id = id;
}
public boolean load() {
if (loaded.compareAndSet(false, true)) {
// dummy sleep, e. g. resource loading happens here
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
return true;
}
return false;
}
}
public static void main(String[] args) {
spawnThread(1);
spawnThread(1);
spawnThread(1);
spawnThread(2);
spawnThread(2);
spawnThread(2);
}
}
非常感谢。你有枪吗
Get resource id: 1
Get resource id: 2
Get resource id: 2
Get resource id: 2
Get resource id: 1
Get resource id: 1
Load resource id: 1
Load resource id: 2
Load resource id: 2
Load resource id: 2
Load resource id: 1
Load resource id: 1
Counter: 0
Counter: 0
Counter: 0
Counter: 0
Thread Thread[Thread-5,5,main], loaded resource id: 2
Thread Thread[Thread-4,5,main], loaded resource id: 2
Thread Thread[Thread-1,5,main], loaded resource id: 1
Thread Thread[Thread-2,5,main], loaded resource id: 1
Counter: 1
Thread Thread[Thread-3,5,main], loaded resource id: 2
Counter: 2
Thread Thread[Thread-0,5,main], loaded resource id: 1