多线程执行需要考虑到的开销:
1)线程创建开销
2)CPU上下文切换开销
引起CPU上下文切换的条件:
1)时间片执行完毕
2)线程在时间片执行完毕前阻塞或结束
所以要减少CPU上下文切换的开销,从以上两点考虑,可以有以下做法:
1)无锁并发编程。
2)CAS算法。在juc包下,有很多类是使用了CAS算法来更新数据的。CAS(compare and swap),有兴趣的同学自行查阅。
3)使用最少线程。
4)协程。在单线程里实现多任务的调度。主要应用场景为:如果存在长时间的IO操作,这个时候线程一直处于阻塞状态,在多线程场景下更会将资源占用的问题放大,使用协程自行维持多个任务的切换,能够有效减少开销。
下例使用了无锁并发编程和CAS算法实现1到1千万的累加。至于是不是最少线程,我才懒得去慢慢试呢 : )
而协程的话,在java里实在是太不常见了,有兴趣的同学自行查阅。下例中使用的callable和future姑且用来帮助理解协程吧。
public class Test1 {
private static AtomicLong res = new AtomicLong(0);
public static void main(String[] args) throws Exception{
System.out.println(count());
}
/***
* 计算1到1千万的和
*/
private static long count() throws Exception{
int n = 10000000;
int num = 100;
long itemLength = n / num;
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<Long>> futures = new ArrayList<>();
for (int i = 0; i < num; i++) {
Future<Long> future = executorService.submit(new CountThread(i, itemLength));
futures.add(future);
}
for (Future<Long> future: futures) {
res.addAndGet(future.get());
}
return res.get();
}
private static long countItem(long start, long end) {
boolean isOdd = (end - start + 1) % 2 == 1;
int pairs = (int) ((end - start + 1) / 2);
return isOdd ? (long) ((pairs + 0.5) * (start + end)) : pairs * (start + end);
}
static class CountThread implements Callable<Long> {
private int i;
private long itemLength;
CountThread(int i, long itemLength) {
this.i = i;
this.itemLength = itemLength;
}
@Override
public Long call() throws Exception {
return countItem(i * itemLength + 1, (i + 1) * itemLength);
}
}
}
死锁:
简单来说就是,桌上有一碗汤和一个勺子,A拿着勺子B拿着汤,A和B都想喝汤,但是A不想把勺子给B,B不想把汤给A,两个人就僵着了。示例如下:
public class Test1 {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Test1().deadLock();
}
private void deadLock() {
new Thread(() -> {
synchronized (resource1) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println("Thread1 get all resource");
}
}
}).start();
new Thread(() -> {
synchronized (resource2) {
synchronized (resource1) {
System.out.println("Thread2 get all resource");
}
}
}).start();
}
}
防止死锁的方法:其实就在于编程习惯上,要自行去规避一些可能造成死锁的场景。比如避免一个线程同时获取多个锁或锁内同时占用多个资源,比如避免解锁失败的情况等待。
资源限制:
这一概念主要是出现在硬件限制上,比如带宽、CPU、硬盘读写速度等。软件限制则是人为设定的数据库的连接数和socket连接数等。
解决途径也从软硬件角度出发:
硬件方面,哪个不行换哪个咯,单机没法解决的问题就用集群咯。软件方面则回到了并发的开销问题上。