在 Java 5.0 提供了 java.util.concurrent(JUC)并发包,提供并发编程中很常用的工具类。
1、volatile
修饰成员变量
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程;
保证此变量对所有的线程的可见性。
2、线程容器ThreadLocal
当前线程向容器中设置的值,只有当前线程获取。其它线程无法获取,避免了线程访问数据的安全问题。
1)简介
Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。
2)代码示例
(1)Hashtable 效率低
Map
(2)synchronizedMap
Map
(3)ConcurrentHashMap
Map
示例代码:
public class DefaultTokenManager implements TokenManager {
private static Map 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 unbounded = new LinkedBlockingQueue();
BlockingQueue bounded = new LinkedBlockingQueue(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自己控制的,你只要上好相应的锁即可。
(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(); //异步获取业务类处理的结果 并阻塞一个线程