Java并发编程基础
Posted lypzz.com
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程基础相关的知识,希望对你有一定的参考价值。
并发与并行
并发与并行的区别?
并发:同时完成多个任务,无需等待当前任务完成即可执行其它任务。例如解决IO密集型任务。
并行:同时在多个CPU中执行多个任务,将任务分为多个部分,在多个CPU中执行多个子任务。用于解决CPU密集型任务。
总之,并发(并行)是为了减少等待(阻塞)时间的技术
举个例子来说明单线程,并发,并行的区别:
小明正在听歌,她妈妈让他做作业,这时,小明...
- 先把歌听完了,再开始写作业,小明就是单线程的
- 暂停歌曲播放,开始写作业,1分钟后停止写作业,开始播放音乐,如此反复,小明就是并发的
- 一边听歌,一边写作业,小明就是并行的
什么时候应该使用并发?
- 永远不要使用并发
- 除非程序运行的慢到无法忍受的地步
- 除非没有条件更换具有更好性能的机器的时候
- 除非没有更好的数据结构和算法的时候
- 再考虑使用并发
- 不要自己实现,优先考虑使用成熟的并发库
- 但是,你无法避免并发,必须理解它
多线程机制
CPU时间分片机制,CPU将轮流给每个任务分配其占用的时间。这个过程由线程调度器自动控制。
Java并发编程的实现
Runnable接口
定义Runnable接口的实现类,作为子线程的任务
class ARunnable implements Runnable
{
@Override
public void run()
{
// TODO
}
}
创建Thread实例,传入任务
Thread t=new Thread(new ARunnable());
t.start();
可以使用匿名内部类或者lambda表达式进行简化
new Thread(()->{
// TODO
}).start();
Thread类
定义Thread的派生类,重写其run方法
Thread本身就实现了Runnable接口,所以其run方法实际上是Runnable接口中的
class AThread extends Thread
{
@Override
public void run()
{
// TODO
}
}
创建Thread实例,启动线程
Thread t = new AThread();
t.start();
与Runnable一样,也可以进行简写
new Thread(()->{
// TODO
}).start();
Callable接口
使用Runnable定义任务时,执行完毕后不能返回任何值。如果需要在任务中返回值,可以使用Callable接口。
定义Callable接口的实现类,重写call()方法
class ACallable implements Callable<String>
{
@Override
public String call()
{
return "over";
}
}
使用执行器Executor的submit()方法执行任务
ExecutorService executor = Executors.newCachedThreadPool();
ACallable task = new ACallable();
Future<String> future = executor.submit(task);
返回Future类型的对象,使用阻塞的get()方法获取返回值
try
{
System.out.println(future.get());
}
catch(InterruptedException | ExecutionException e)
{
e.printStackTrace();
}
executor.shutdown();
Fork-Join框架
Java SE7中添加了fork-join框架,专门处理可以分解为子任务的情况,如下所示
if(任务规模小于规定的值)
{
直接处理
}
else
{
分解为子任务;
递归处理每一个子任务;
合并结果;
}
使用步骤:
-
定义任务
如果任务有返回值,扩展
RecursiveTask
;如果任务没有返回值,扩展RecursiveAction
。它们都是ForkJoinTask
的抽象子类,重写compute()方法 -
创建
ForkJoinPool
对象 -
调用
invoke()
方法执行任务 -
调用
join()
方法获取结果
示例:统计随机生成的double数组中,值大于0.5的数字个数
class Counter extends RecursiveTask<Integer>
{
private static final int THRESHOLD=10000;
private double[] nums;
private int from;
private int to;
public Counter(double[] nums,int from,int to)
{
this.nums=nums;
this.from=from;
this.to=to;
}
@Override
protected Integer compute()
{
if(to-from<THRESHOLD) // 直接运行
{
int count=0;
for(int i = from;i < to;i++)
{
if(nums[i]>0.5)
{
count++;
}
}
return count;
}
else
{
//分解为子任务
int mid=(from+to)/2;
Counter first = new Counter(nums,from,mid);
Counter second = new Counter(nums,mid,to);
//递归执行子任务
invokeAll(first,second);
//合并结果
return first.join()+ second.join();
}
}
}
ForkJoinPool pool=new ForkJoinPool();
Counter counter=new Counter(nums,0,nums.length);
pool.invoke(counter);
int count = counter.join();
Java线程生命周期与管理
线程休眠
静态的sleep()方法会抛出中断异常,必须在子线程中捕获处理,因为异常不能跨线程抛出
try
{
//Thread.sleep(1000L);
TimeUnit.MILLISECONDS.sleep(1000L);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
线程优先级
线程的优先级表示该线程的重要性,调度器倾向于让优先级更高的线程优先执行。然而,这种行为是不确定的。因此,不要依赖于优先级进行任何假设。
Java中线程优先级从低到高分为10个等级(1-10),默认为5
void setPriority(int newPriority)
int getPriority()
线程让步
静态的yield()方法给线程调度器一个暗示,表示当前任务完成的差不多了,可以让具有相同优先级的其它线程优先执行。
但是,线程调度器并不保证按照暗示执行,所以,不要依赖于yield()进行任何假设。
void yield()
后台线程
后台线程也称守护线程,用于在程序运行的过程中,在后台提供通用的服务。
当所有非后台线程结束时,程序也就终止了,同时自动杀死所有的后台线程。main线程不是后台线程。
void setDaemon(boolean on)
boolean isDaemon()
必须在线程启动之前调用setDaemon()方法。
如果是一个后台线程,那么它创建的任何线程都被自动设置为后台线程。
后台线程会在main方法终止后“突然”关闭,即使是finally代码快中的代码也不一定会被执行。
线程的加入
在线程A中调用线程B的join()方法,线程A将被挂起,等到线程B执行完毕后,线程A再从挂起的地方继续执行。join()方法可以带上一个参数,表示挂起的时间。
void join()
void join(long millis)
void join(long millis, int nanos)
join()方法内部使用wait()方法实现。
中断线程
调用interrupt()方法可以中断线程。
当在一个被阻塞的线程上调用interrupt()时,会抛出异常InterruptedException。
被中断的线程可以决定如何响应中断,是处理完异常后继续执行,还是中止执行。
静态的interrupted()或者非静态isInterrupted()方法用于检测当前线程的中断状态。调用前者会清除该线程的中断状态。如果捕获了中断异常,其中断状态总是返回false
void interrupt()
boolean isInterrupted()
boolean interrupted()
线程状态(生命周期)
Java中定义了线程的6种状态,这6种状态定义在Thread内部枚举类State中
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
- 新建(NEW):当线程被new时的状态,此时它已经分配了必需的系统资源,并执行了初始化。
- 就绪(RUNNABLE):调用start()方法后,线程准备就绪,只要调度器分配了时间片给它,就可以开始执行任务
- 阻塞(BLOCKED):线程尝试获取锁时的状态
- 等待(WAITING):当线程调用了非计时的wait()或join()方法后,处于等待状态
- 计时等待(TIMED_WAITING):当先调用了sleep()或计时的wait(),join()方法后,处于计时等待状态
- 终止(TERMINATED):线程执行完毕或者由于未捕获的异常而终止
可以使用getState()方法获取线程状态,返回枚举类型的线程状态
State getState()
以上是关于Java并发编程基础的主要内容,如果未能解决你的问题,请参考以下文章