github上新上传了一个fork/join的多线程框架, 项目地址:https://github.com/jndf/multithreading-pratice
需要的朋友可以看看,代码如有错误,请多提出指正意见。
走过路过,麻烦给点个星,好人一生平安。
接着上篇文章:
Fork/Join框架
这个框架主要是应用与一些可以多分支同时运行的操作,最后将每个线程的运行结果合在一起,作为总结果返回。
依旧是具体看注释:
/**
* 写一个Fork/join 多线程框架
*
*/
public class Demo5 {
/**
* ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。
* 它提供在任务中执行 fork() 和 join() 操作的机制,通常情况下我们不需要直接继承 ForkJoinTask 类,而只需要继承它的子类,Fork/Join 框架提供了以下两个子类:
*
* RecursiveAction :用于没有返回结果的任务。
* RecursiveTask :用于有返回结果的任务。
* ForkJoinPool :ForkJoinTask 需要通过 ForkJoinPool 来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。
* 当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
*/
//线程
static class ForkJoinTest1 implements Callable{
/**
* 执行线程的代码块
*/
@Override
public Object call() {
SumTest sumTest1 = new SumTest(1,2);
SumTest sumTest2 = new SumTest(3,4);
/**
* fork将多个线程分流运算
*/
sumTest1.fork();
sumTest2.fork();
/**
* join将多个线程结果获取,这样就达到了forkjoin的把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的思想
*/
return (int)sumTest1.join()+(int)sumTest2.join();
}
}
//测试类
static class SumTest extends RecursiveTask{
int startNum;
int endNum;
public SumTest(int startNum,int endNum){
this.startNum = startNum;
this.endNum = endNum;
}
//计算加法
@Override
protected Object compute() {
return (int)startNum+endNum;
}
}
public static void main(String[] args) {
ForkJoinPool ForkJoinPool = new ForkJoinPool();
ForkJoinTest1 forkJoinTest1 = new ForkJoinTest1();
Future sum = ForkJoinPool.submit(forkJoinTest1);
try {
System.out.println(sum.get());
}catch(InterruptedException e) {
}catch(ExecutionException e) {
}
}
}
接下来是一些理论知识:
线程安全关键字:
当多线程操作时,有可能出现如同数据库事务隔离问题(脏读,幻读等)相似的问题,其实应该说是,大量执行数据库实务操作,其实就是执行多线程,而出现上述问题的原因,则是没有确保事务的原子和隔离性,这也是线程不安全的原因;
因此线程安全,就是指让线程们有序的去访问对象资源,进行操作,像 削弱之王宫本武藏说的一样:不要急,一个一个来~;
在这里提到volatile与synchronized两个线程安全关键字;
主要说说他们的区别:volatile使用场景较为苛刻,因为它主要保证的是,被线程操作的变量实时可见,说白了就是很多时候线程不安全是因为一个线程改变了数据,在改变数据的时候,另一个线程也加入进来改变数据的行列,这样数据就会出现问题。
而volatile可以让数据的改变实时可见,只要一个数据被改变了,就会马上把这个数据修改后的值实时通知给所有线程,这样就减少了数据结果的不确定性;
所以他的使用场景也是多线程改变同一个数据这种情况,个人理解比较适合fork/join,欢迎大神指教。
而synchronized则是将线程的安全考虑的更全面,添加了synchronized的资源只能被线程有序访问,其他的就等着吧,因此与等待通知机制合起来使用效果更佳。而且线程访问完synchronized修饰的对象,资源会自动释放,省时省力,这个关键字的应用也比较普遍。
然后随着使用,synchronized就出现了这么一个问题,并因此引申出了锁。
我们往下看:
JAVA锁的应用场景:
synchronized只是保证了线程安全,但是牺牲了性能,在某一个时刻cpu只会运行一个线程,而其他线程都进入阻塞状态,
那么当正在运行的线程由于要等待io,或者其他原因导致占用线程时间过长,将会影响到线程的使用效率。
因此使用锁这一概念,来将线程效率根据不同应用场景,来进行相应的实现。
有一个使用场景:比如可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock也可以办到。
具体关于锁的详解看下一篇文章