1.并发编程的目的
是为了让程序运行得更快。(但是,并不少启动更多的线程就能让程序最大限度地并发执行)
2.为了实现改目的,可能会面临的问题
2.1 上下文切换的问题
2.2 死锁的问题
2.3 资源的限制(硬件和软件资源的限制)
3.多线程的实现
时间片是CPU分配给各个线程的时间。
CPU通过给每个改线程分配CPU时间片,不停地切换线程的执行。由于时间片时间很短(几十毫秒)。让我们感觉是同时执行的。
上下文的切换:当前任务执行一个时间片后,会切换到下一个任务。
切换前会保存一下任务的状态,以便下次切换回来。
4.上下文切换
4.1 多线程是否一定快
不一定。
并发和串行执行时间demo代码如下:
public class ConcurrentTimeTest {
private static final long count = 10000000;
// private static final long count = 1000000000;
public static void main(String[] args) throws Exception {
concurrent();
serial();
}
private static void concurrent() throws InterruptedException {
long start = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a += 5;
}
}
});
thread.start();
int b = getBValue();
long currentCostTime = System.currentTimeMillis() - start;
thread.join();
System.out.println("currentCostTime:" + currentCostTime + "ms\t" + "b=" + b);
}
private static void serial() {
long start = System.currentTimeMillis();
int a = 0;
for (long i = 0; i < count; i++) {
a += 5;
}
int b = getBValue();
long serialTime = System.currentTimeMillis() - start;
System.out.println("serialTime:" + serialTime + "ms\ta=" + a + "\tb=" + b);
}
private static int getBValue() {
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
return b;
}
}
 
得出的结论:数据量达不到百万以上的量级,多线程反而会慢。因为多线程有线程创建和上下文的切换
4.2 减少上下文切换的方法
(1)无锁并发编程。一批数据根据hash算法取模,不同段,交给不同线程去处理。
(2)CAS算法。Java的Atomic包,使用CAS算法来更新数据,无需加锁
(3)使用最少线程。任务很少却创建了很多线程来处理。会导致大量的线程处于等待状态
(4)协程。单线程内实现多任务的调度。
5.死锁
死锁demo如下:
public class DeadLockTest {
private static final String lockA = "A";
private static final String lockB = "B";
public static void main(String[] args) {
deadLock();
}
private static void deadLock(){
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(lockB+"---in--"+lockA);
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockB){
synchronized (lockA){
System.out.println(lockA+"---in---"+lockB);
}
}
}
});
thread1.start();
thread2.start();
}
}
现象: thread1和tread2,两个线程相互等待对方释放锁。
如何避免死锁?
1.避免一个线程获取多个锁。
2.避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3.尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制。
4.对于数据库锁,加锁和解锁必须在同一个数据库连接里,否则会出现解锁失败的情况。
6. 资源的限制
1.硬件和软件资源的限制
硬件的限制有宽带上传和下载的速度,硬盘读写速度和CPU的处理速度等。
软件资源的限制。数据库的连接数和socket连接数等。
2.资源限制可能导致的问题
并发编程提高执行效率,根本上是把串行的部分改变为并行。但是,资源有限的情况下,仍然会是串行执行。在加上线程的创建和上下文的切换,效率会极其低下。
3.解决资源限制的问题
硬件资源限制的话,可以使用集群来并行执行。数据id%机器数得到机器号,然后,对应机器执行对应的数据。
软件资源限制的话,可以考虑用连接池对数据库连接等连接复用。
参考资料:
《 Java并发编程的艺术》
|