线程池,原子操作类,线程安全集合类以及乐观锁的一些介绍
创建线程的第三种方法:
用Callable接口
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + "线程开始执行...");
return "ok";
}
});
//创建和启动线程
new Thread(task).start();
//获取返回的结果
System.out.println(task.get());
Callable接口与Runable接口的区别:
1)Callable有返回值
2)可以抛出检查异常
线程池
创建有限的线程资源为更多的任务提供服务,是一种享元模式。
创建固定大小的线程池
Executors.newFixedThreadPool(2);
核心线程数等于最大线程数(没有救急线程成)
阻塞队列无界,可以放任意数量的任务。
适合执行数量有限,长时间执行的任务。
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//执行没有返回值的线程
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行...");
}
});
//有返回值的线程
Future submit = threadPool.submit(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"线程开始执行...");
return "ok";
}
});
//获得返回结果
System.out.println(submit.get());
threadPool.shutdown();//不接收新任务,当所有任务运行结束,整个线程池关闭。
创建缓冲线程池
Executors.newCachedThreadPool()
核心线程数是0,最大线程数Integer的最大值(无上限的意思)
生存时间60秒
适合任务数比较密集,但每个任务都执行的时间较短的情况
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.submit(() -> System.out.println(Thread.currentThread().getName()+"线程开始执行"));
threadPool.shutdown();
创建单线程线程池
Executors.newSingleThreadExecutor()
使用场景:希望多个任务排队执行
ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.submit(()->{
System.out.println(Thread.currentThread().getName()+"线程开始执行...");
});
threadPool.shutdown();
"单线程线程池"与"固定大小线程池固定1个线程池"的区别:
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
创建带有日程安排功能的线程池
Executors.newScheduledThreadPool(5)
让任务推迟一段时间执行
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
//参数1:任务对象
//参数2:推迟时间
threadPool.schedule(()->{
System.out.println(Thread.currentThread().getName()+"线程开始执行...");
},1L,TimeUnit.SECONDS);
threadPool.shutdown();
以一定频率反复执行任务
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
//参数1:任务对象
//参数2:初始推迟时间
//参数3.4:时间间隔和单位
service.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName()+"线程开始执行...");
},0,1,TimeUnit.SECONDS);
Thread.sleep(5000);//让他运行5秒后在停止
service.shutdown();
从上一个任务结束到下一个任务开始的时间
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
service.scheduleWithFixedDelay(()->{
System.out.println(Thread.currentThread().getName()+"线程开始执行...");
},0,1,TimeUnit.SECONDS);
//delay表示从上一个任务结束到下一个任务开始之间的时间
Thread.sleep(5000);//让他运行5秒后在停止
service.shutdown();
原子操作类
AtomicInteger
AtomicBoolean
private static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndIncrement(); // 自增 i++
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndDecrement(); // 自减 i--
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
线程安全集合类
StringBuffer 线程安全
String 不可变类,都是线程安全
Random 线程安全
Vector 实现了List ,并且线程安全
HashTable 实现了map,并且线程安全
5.0新增的线程安全集合类
ConcurrentHashMap 实现了Map,并且线程安全
ConcurrentSkipListMap 实现了Map(可排序),并且线程安全
CopyOnWriteArrayList 实现了List,并且线程安全
阻塞队列
BlockingQueue
// 创建队列
Queue<String> queue = new LinkedList<>();
queue.offer("1");
queue.offer("2");
queue.offer("3");
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// 线程安全的队列, 其中capacity 表示队列的元素上限
BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(3);
// 或 new LinkedBlockingDeque<>(3);
queue1.put("a");
queue1.put("b");
queue1.put("c");
System.out.println(queue1);
/*
queue1.put("d");// 如果放入多于上限的元素时,put方法会被阻塞*/
System.out.println(queue1.take());
System.out.println(queue1.take());
System.out.println(queue1.take());
System.out.println(queue1);
System.out.println(queue1.take());
/*如果队列中没有元素了,take方法会被阻塞*/
}
用队列来实现生产者与消费者的例子
private static BlockingQueue<Product> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
// 生产者线程
new Thread(()->{
for (int i = 0; i < 10; i++) {
Product p = new Product(i);
System.out.println(Thread.currentThread().getName()+"生产了:"+p);
try {
queue.put(p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 消费者线程
for (int j = 0; j < 5; j++) {
new Thread(()->{
for (int i = 0; i < 2; i++) {
try {
Product p = queue.take();
System.out.println(Thread.currentThread().getName()+"消费了:"+p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
ThreadLocal
public class ThreadLocalTest {
//线程局部变量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
@Override
protected Object initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");//存入当前线程
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
SimpleDateFormat sdf = local.get(); //获取本线程自己的局部变量
Date date = sdf.parse("1997-07-22");
System.out.println(Thread.currentThread().getName()+" "+date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
}
乐观锁
juc 中的大部分类是通过无锁并发实现的(没有用synchronized)
CAS 机制 compare And swap 比较并交换
synchronized 可以称之为悲观锁
cas 体现的是乐观锁
首先不会给共享资源加锁,而是做一个尝试
先拿到旧值,查看旧值是否跟共享区域的值相等
如果不等,那么说明别的线程改动了共享区域的值,我的修改失败
如果相等,那么就让我的修改成功
如果修改失败,没关系,重新尝试
int var5;
// 修改失败,没关系,重新尝试 自旋
do {
// 获取共享区域的最新值
var5 = this.getIntVolatile(var1, var2); // 10
// 比较并交换 最新值 最新值+1
} while(! this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
重入锁 ReentrantLock
.lock() 加锁
.unlock() 解锁
例子:
static int i = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantLock rl = new ReentrantLock();
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
try {
rl.lock(); // 加锁
i++;
} finally {
rl.unlock(); // 保证解锁一定被执行
}
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
try {
rl.lock(); // 加锁
i--;
} finally {
rl.unlock(); // 保证解锁一定被执行
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
synchronized 性能上比较 ReentrantLock 在高并发下低,ReentrantLock的内存占用会高一些
CountDownLatch
countdown 倒计时
当希望多个线程执行完毕后,再接着做下一步操作时,
例子:
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(3);// 构造方法需要指定倒计时的数字
new Thread(()->{
System.out.println("线程1开始运行"+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1准备完成"+new Date());
cdl.countDown();
}).start();
new Thread(()->{
System.out.println("线程2开始运行"+new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2准备完成"+new Date());
cdl.countDown();
}).start();
new Thread(()->{
System.out.println("线程3开始运行"+new Date());
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3准备完成"+new Date());
cdl.countDown();
}).start();
// 主线程等待,直到倒计时为0
System.out.println("主线程等待");
cdl.await();
System.out.println("ready go....");
}
一个应用例子:模拟10个玩家加载进度
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
String[] all = new String[10];
for (int j = 0; j < 10; j++) {
int x = j;
new Thread(()->{
Random r = new Random();
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(r.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (all){
all[x]=(i+"%");
System.out.print("\r"+Arrays.toString(all));
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("\nend...");
}
循环栅栏
// CyclicBarrier 可循环的 屏障(栅栏)
// 当满足CyclicBarrier设置的线程个数时,继续执行,没有满足则等待
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行
new Thread(()->{
System.out.println("线程1开始.."+new Date());
try {
cb.await(); // 当个数不足时,等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程1继续向下运行..."+new Date());
}).start();
new Thread(()->{
System.out.println("线程2开始.."+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
cb.await(); // 2 秒后,线程个数够2,继续运行
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程2继续向下运行..."+new Date());
}).start();
与倒计时锁的区别:倒计时锁只能使用一次,倒计时结束这个对象就没用了。
而循环栅栏可以重复利用。
信号量
Semaphore s = new Semaphore(3); // 限制了能同时运行的线程上限
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
s.acquire(); // 获得此信号量
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release(); // 释放信号量
}
}).start();
}