JDK并发包使用

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_15764943/article/details/85038860

JUC

在 Java 5.0 提供了 java.util.concurrent(JUC)并发包,提供并发编程中很常用的工具类。

变量与线程安全

1、volatile
修饰成员变量
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程;
保证此变量对所有的线程的可见性。

2、线程容器ThreadLocal
当前线程向容器中设置的值,只有当前线程获取。其它线程无法获取,避免了线程访问数据的安全问题。
1)简介
Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。
2)代码示例
在这里插入图片描述

并发容器

(1)Hashtable 效率低
Map<String, String> hashtable = new Hashtable<>();

(2)synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());

(3)ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
示例代码:

public class DefaultTokenManager implements TokenManager {
    private static Map<String, String> tokenMap = new ConcurrentHashMap<>();
    @Override
    public String createToken(String username) {
        String token = CodecUtil.createUUID();
        tokenMap.put(token, username);
        return token;
    }
    @Override
    public boolean checkToken(String token) {
        return !StringUtil.isEmpty(token) && tokenMap.containsKey(token);
    }
}

并发队列

安全队列
队列容器,存放数据,当队列中数据被取完时,取数据操作线程被堵塞;当队列中数据被加满是,加数据操作线程被堵塞

BlockingQueue接口多个实现类,一下为其中三个实现类

(1)ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。

BlockingQueue queue = new ArrayBlockingQueue(1024);
queue.put("1");
Object object = queue.take();

(2)LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。和ArrayBlockingQueue一样,LinkedBlockingQueue 也是以先进先出的方式存储数据。

BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();
BlockingQueue<String> bounded   = new LinkedBlockingQueue<String>(1024);
bounded.put("Value");
String value = bounded.take();

(3)SynchronousQueue队列内部仅允许容纳一个元素。当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费。

并发工具类

CountDownLatch、Semaphore、ReentrantReadWriteLock、ReentrantLock

(1)CountDownLatch
用于监听某些初始化操作,等初始化操作完毕后,通知主线程继续执行

final CountDownLatch countDown = new CountDownLatch(2);

countDown.await();  //线程阻塞   
countDown.countDown();  //激活一次
countDown.countDown();  //激活二次

(2)Semaphore
Semaphore是计数信号量,经常用于限制获取某种资源的线程数量

final Semaphore semp = new Semaphore(3);     
semp.acquire();   //获取许可
//业务代码              //这里面只允许3个线程来执行
semp.release();   //释放许可

(3)读写锁线程安全ReentrantReadWriteLock

private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReadLock readLock = rwLock.readLock();    //获取读锁
private final WriteLock wirteLock = rwLock.writeLock();    //获取写锁

readLock.lock();
readLock.unlock();

读读共享,写写互斥,读写互斥

读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。

  • 如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;
  • 如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。
  • 总之,读的时候上读锁,写的时候上写锁!
    private Object data = null; //共享数据 ,只能有一个线程写该数据,但可以有多个线程同时读
    ReadWriteLock rwl = new ReentrantReadWriteLock(); //读写锁
    rwl.readLock().lock();//上读锁 可以有多个线程同时读 不管是否异常做释放锁操作
    rw1.writeLock().lock();//添加写锁,保证只能有一个线程进行写操作 不管是否异常做释放锁操作

(4)线程安全ReentrantLock

定义锁:private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
使用:
lock.lock();加锁  
c1.await();   //线程阻塞 等待,c1.signal(); //发信号解除阻塞
lock.unlock();释放锁

线程池

实现线程池创建线程比手动new Thread()好,效率高,统一管理

(1)Java通过Executors提供四种线程池:

①newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
②newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。Runtime.getRuntime().availableProcessors()
③newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
④newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

(2)简单示例

cachedThreadPool.execute(new Runnable(){...})    //需要传入实现Runnable接口的的业务类
scheduledThreadPool.schedule(new Runnable(){...},3, TimeUnit.SECONDS)   //延迟3秒执行
scheduledThreadPool.schedule(new Runnable(){...},1,3, TimeUnit.SECONDS)   //延迟1秒后,每隔3秒执行一次   定时周期执行任务   比Timeer

FutureTask future = new FutureTask(new UserFuture());   //构造FutureTask,传入业务代码执行的对象,该类需要实现Callable接口

ExecutorService executor = Executors.newFixedThreadPool(1);  //定义线程池,里面可开辟两个线程
Future f = executor.submit(future);  //单独启动一个线程(线程池)去执行  不影响下面代码执行
f.get();   //返回null表示任务执行完成
future.get();  //异步获取业务类处理的结果  并阻塞一个线程

猜你喜欢

转载自blog.csdn.net/qq_15764943/article/details/85038860