TimeUnit是 java.util.concurrent 中的一个枚举类(时间单元类)。一般让进行控制线程睡眠时使用。
TimeUnit提供了可读性更好的线程暂停操作,通常用来替换Thread.sleep(),相比 Thread.sleep()方法的一个好处就是 TimeUnit可以设置时间单位。
这个类支持有:日(DAYS)、时(HOURS)、分(MINUTS)、秒(SECONDS)、毫秒(MILLISECONDS)、微秒(MICROSECONDS)、纳秒(NANOSECONDS)。
1、实例:进行休眠控制,休眠2秒
使用Thread.sleep() 方法处理
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("[sleep start]"+System.currentTimeMillis());
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[sleep end]"+System.currentTimeMillis());
}
}).start();
}
直接使用TimeUnit类来处理
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("[sleep start]"+System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[sleep end]"+System.currentTimeMillis());
}
}).start();
}
2、时间单位的转换
在 TimeUnit类中最为重要的特点是可以方便的进行各种时间单位的转换,它提供了一个convert()方法
实例:1小时转换为秒数
public static void main(String[] args) {
long sec = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS);
System.out.println("1小时转换为秒数:" + sec);
long minutes = TimeUnit.MINUTES.convert(sec, TimeUnit.SECONDS);
System.out.println(sec + "秒有转换为分钟:" + minutes);
}
简单使用:volatile关键字与内存可见性
JUC里的同步容器类
1、java 基础数据集合容器中
线程安全与非线程安全的对象如下
Collection |--List |--ArrayList
| |--LinkList
| |--Vector -->线程安全
|
|
| --Set |--TreeSet
|--HashSet
Map |--TreeMap
|--HashMap
|--HashTable -->线程安全
StringBuffer -->线程安全
StringBulider -->非线程安全
解决这些非线程安全的集合的线程安全
通过使用的 Collections.synchronizedXXX()方法来转换。HashMap的话可以直接使用HashTable转换
(1)List用线程安全对象处理,map同理
public class CollectionDemo {
private static Vector list = new Vector();
// private static List list = new ArrayList();
// private static List list = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args){
//让50个线程去执行
for (int i = 0; i < 50; i++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
list.add(i);
}
System.out.println("集合大小:--->"+list.size());//预期的集合大小应该是50x100=5000
}
}
}
(2)读写处理
public class CollectionDemo {
private static List
注意:上面的解决方法,一是程序效率低;二是在复合操作的时候会报并发修改异常(ConcurrentModificationException)。
2、JUC 解决非线程安全的集合类
JUC里面提供了一系列同步容器类用来解决非线程安全的集合类,我们只需要在多线程并发编程中,用这些同步容器类类替换掉原来的HashMap,ArrayList,HashSet集合,就可以了保证即是线程安全的,比使用 Collections.synchronizedXXX()的效率高。
HashMap -- ConcurrentHashMap
TreeMap -- ConcurrentSkipListMap
ArrayList -- CopyOnWriteArrayList
ArraySet -- CopyOnWriteArraySet等等
CopyOnWriteArrayList和CopyOnWriteArraySet 每次存入要新建一个存储结构,写操作效率低比Vector都低,浪费空间,用于写得少,读得多。
public class CollectionDemo {
private static CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
public static void main(String[] args){
for (int i = 0; i < 5; i++) {
new MyThread().start();
}
}
private static class MyThread extends Thread{
@Override
public void run() {
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(Thread.currentThread().getName() + "==" + iterator.next());
}
list.add("add" + Thread.currentThread().getName());
}
}
}
简单了解三个类用于同步一批线程的行为,分别是CountDownLatch、Semaphore和CyclicBarrier。
1、CountDownLatch类
CountDownLatch是一个计数器闭锁,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作。
例如:为了让主线程等待工作线程执行完成,主线程调用await操作让主线程阻塞,当工作线程完成初始化过程之后,每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
public class CountDownLatchDemo {
public static void main(String[] args) {
// 创建 CountDownLatch, 3个线程任务
CountDownLatch countDownLatch = new CountDownLatch(3);
LatchDemo latchDemo = new LatchDemo(countDownLatch);
for (int i = 0; i < 3; i++) {
new Thread(latchDemo).start();
}
try {
// 主线程执行await方法,进行等待,知道计数器的值为0时继续向下执行
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程运行完成");
}
public static class LatchDemo implements Runnable{
private CountDownLatch countDownLatch = null;
public LatchDemo(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
for (int i = 0; i <5; i++) {
System.out.println(Thread.currentThread().getName() + "==" + i);
}
} finally {
// 计数器减1
countDownLatch.countDown();
}
}
}
}
2、Semaphore类
Semaphore是一个控制访问多个共享资源的计数信号量,用于管理一组资源。它相当于控制使用公共资源的活动线程的数量。在信号量上的两种操作:
例如:一个停车场有5个车位,假设有10台车进来停车,一个车位同时只能被一台车使用,只有车使用完开走了,其他车才能继续使用。
public static void main(String[] args) {
// 5个车位
Semaphore semaphore = new Semaphore(5);
for (int i = 1; i <= 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位了");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "离开了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}).start();
}
}
3、CyclicBarrier类
CyclicBarrier 的字面意思是回环栅栏/可循环使用(Cyclic)的屏障(Barrier),通过它可以实现让一组线程等待至某个状态之后再全部同时执行。
它允许一组线程到达某个公共屏障点 (common barrier point)时被阻塞,直到最后一个线程到达屏障(同步点)时,屏障才会开门,所有被屏障拦截的线程都到齐了才会继续干活。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
实例:周末5人组织大巴去旅游,总共有两个景点,每个景点约定好游玩时间,一个景点结束后需要集中一起出发到下一个景点。
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable(){
// 当所有线程到达barrier时执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "最后一个到达,人齐了!");
}
});
BarrierDemo barrierDemo = new BarrierDemo(cyclicBarrier);
for (int i = 0; i < 5; i++) {
new Thread(barrierDemo).start();
}
}
public static class BarrierDemo implements Runnable {
private CyclicBarrier cyclicBarrier = null;
public BarrierDemo(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "到达景点1");
cyclicBarrier.await();// 线程在这里等待,直到所有线程都到达barrier。
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "到达景点2" );
cyclicBarrier.await();// 线程在这里等待,直到所有线程都到达barrier。
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
}
Exchanger用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger exchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
try {
String a = "A数据";
String exchangeData = exchanger.exchange(a); //交换我自己的数据,并且获取别人的数据
System.out.println("线程a:" + exchangeData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
String b = "b数据";
String exchangeData = exchanger.exchange(b); //交换我自己的数据,并且获取别人的数据
System.out.println("线程b:" + exchangeData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
参考文章: JUC回顾之-AQS同步器的实现原理: