为什么要学习并发编程?
•1.发挥多处理的强大能力
•2.建模的简单性
•3.异步事件的简化处理
•4.响应更加灵敏的用户界面
•1.找工作,面试
•2.了解并发编程的原理,提高代码的编写能力
•3.解决工作中遇到的并发问题
......
线程与进程
首先要先学会区分线程和进程:
- 进程:运行中的程序,是资源分配的基本单位。进程中包含多个线程,线程共享进程的资源
- 线程是处理器调度的基本单位
并发的优缺点?
优点:
- 资源利用率更好
- 程序设计在某些情况下更简单
- 程序响应更快
缺点:
- 安全性问题
- 活跃性问题(饥饿)
- 性能问题
多线程就一定快吗?
/**
* ConcurrencyTest:多线程效率测试。
*
* @author YUSIR
* @version 2019-02-28
*/
public class ConcurrencyTest {
private static final long COUNT = 10001;
/**
* 多线程测试.
*/
private static void concurrency() throws InterruptedException {
long startTime = 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 = 0;
for (long i = 0; i < COUNT; i++) {
b--;
}
// 子线程正常运行,父线程会等待
thread.join();
long time = System.currentTimeMillis() - startTime;
System.out.println("concurrency :" + time + "ms,b=" + b);
}
/**
* 单线程串行执行.
*/
private static void serial() {
long startTime = System.currentTimeMillis();
int a = 0;
for (long i = 0; i < COUNT; i++) {
a += 5;
}
int b = 0;
for (long i = 0; i < COUNT; i++) {
b--;
}
long time = System.currentTimeMillis() - startTime;
System.out.println("serial:" + time + "ms,b=" + b + ",a=" + a);
}
public static void main(String[] args) throws InterruptedException {
concurrency();
serial();
}
运行结果:
concurrency :6ms,b=-10001
serial:0ms,b=-10001,a=50005
此时多线程运行效率并不比传统的串行运行效率高
但是,当
COUNT = 100000001;
运行结果:
concurrency :38ms,b=-100000001
serial:62ms,b=-100000001,a=500000005
此时多线程就比串行快了
原因:线程有创建和上下文切换的开销,首先要先理解什么是上下文的切换——CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务.但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务是,可以再加载这个任务状态.所以任务从保存到再加载的过程就是一次上下文切换,上下文切换会影响多线程的执行速度!
线程的状态
1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.wait(),notify(),notifyAll()等方法的介绍
创建线程的多种方式
- 继承Thread类
public class Demo1 extends Thread {
@Override
public void run() {
// 测试当前线程是否已被中断
while(!interrupted()) {
System.out.println("线程执行了 .. ");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 实现Runnable接口
/**
* 作为线程任务存在
*
* @author worker
*
*/
public class Demo2 implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("thread running ...");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new Demo2());
thread.start();
}
}
- 匿名内部类的方式
public class Demo3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable");
}
}) {
public void run() {
System.out.println("sub");
};
}.start();
}
}
使用匿名内部类创建线程是不符合规范的,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
- 带返回值的线程
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 1.方法可以有返回值,并且可以抛出异常
* 2、执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
*/
public class Demo4 implements Callable<Integer> {
public static void main(String[] args) throws Exception {
Demo4 d = new Demo4();
// 1.执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
FutureTask<Integer> task = new FutureTask<>(d);
Thread t = new Thread(task);
t.start();
System.out.println("我先干点别的。。。");
// 2.接收线程运算后的结果。等所有线程执行完,获取值,因此FutureTask可用于闭锁
Integer result = task.get();
System.out.println("线程执行的结果为:" + result);
}
@Override
public Integer call() throws Exception {
System.out.println("正在进行紧张的计算....");
Thread.sleep(3000);
return 1;
}
}
- 定时器(quartz)
1.使用Timer()创建定时器
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
public class Demo5 {
public static void main(String[] args) {
// Timer的内部只有一个线程,多线程并行处理定时任务时,Timer运行多个TimeTask时,
// 只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// 实现定时任务
System.out.println("timertask is run");
}
}, 0, 1000);
}
}
2. 使用ScheduledExecutorService创建
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Demo5_1
*
* @author YUSIR
* @version 2019-02-28
*/
public class Demo5_1 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
// 延时任务
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
//do something
}
}, 1, TimeUnit.SECONDS);
// 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//do something
}
}, 1, 1, TimeUnit.SECONDS);
// 循环任务,以上一次任务的结束时间计算下一次任务的开始时间
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
//do something
}
}, 1, 1, TimeUnit.SECONDS);
}
}
- 线程池的实现
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo6 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
threadPool.shutdown();
}
}
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutoer的方式去创建,这样的处理方式可以避免资源耗尽的风险。
1).newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会消耗非常大的内存
2).newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大是Integer.MAX_VALUE。可能会创建非常多的线程。
- Lambda表达式实现
import java.util.Arrays;
import java.util.List;
public class Demo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
int res = new Demo7().add(values);
System.out.println("计算的结果为:" + res);
}
public int add (List<Integer> values) {
// values.parallelStream().forEach(System.out :: println);
return values.parallelStream().mapToInt( i -> i * 2).sum();
}
}
- Spring实现多线程
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
/**
* Demo8:spring创建线程池.
*
* @author YUSIR
* {@link EnableAsync}启用异步任务
* @version 2019-02-28
*/
@EnableAsync
public class Demo8 {
@Bean
public Executor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数
executor.setCorePoolSize(5);
//最大线程数
executor.setMaxPoolSize(10);
//队列最大长度
executor.setQueueCapacity(25);
executor.initialize();
return executor;
}
@Service
public class AsyncTaskService {
// 这里可以注入spring中管理的其他bean,这也是使用spring来实现多线程的一大优势
@Async // 这里进行标注为异步任务,在执行此方法的时候,会单独开启线程来执行
public void f1() {
System.out.println("f1 : " + Thread.currentThread().getName() + " " + UUID.randomUUID().toString());
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Async
public void f2() {
System.out.println("f2 : " + Thread.currentThread().getName() + " " + UUID.randomUUID().toString());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Demo8.class);
AsyncTaskService service = context.getBean(AsyncTaskService.class);
for (int i = 0; i < 10; i++) {
service.f1();
service.f2();
}
context.close();
}
}
基于xml配置
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心线程数 -->
<property name="corePoolSize" value="5" />
<!-- 最大线程数 -->
<property name="maxPoolSize" value="10" />
<!-- 队列最大长度 >=mainExecutor.maxSize -->
<property name="queueCapacity" value="25" />
<!-- 线程池维护线程所允许的空闲时间 -->
<property name="keepAliveSeconds" value="3000" />
<!-- 线程池对拒绝任务(无线程可用)的处理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃. -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>