在上一篇文章<<Task序列化问题>>中,如果在Excutor关联一个单例对象数据会存在线程安全问题.在Object单例对象中,如果只读取成员变量,不进行其它变量操作.那样就会避免这种问题.但是,为了使程序运行百分百的安全,还是建议大家按照以下方法避免Spark中Task多线程–线程安全的问题.
方案一:
package day05.TaskThread
import Util.DateUtils
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object TaskThreadNotSafe01 {
def main(args: Array[String]): Unit = {
val isLocal=args(0).toBoolean
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName)
if (isLocal){
conf.setMaster("local[*]")
}
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(1))
val timeRDD: RDD[Long] = lines.map(line => {
/**
* 这个有个缺点,就是每dateutils.parse1(line),就用一个new dateutils,有点浪费资源,
* 其实我们可以使用MapPartition来操作数据,使用迭代器迭代出每一个分区的数据,给每个分区
* 只需要 new DataUtils().有几个分区,就new 几个.这样既解决了线程安全的问题,还不浪费资源
*/
val dateutils = new DateUtils()
val time: Long = dateutils.parse1(line)
time
})
val R: Array[Long] = timeRDD.collect()
println(R.toBuffer)
sc.stop()
}
}
缺点:每dateutils.parse1(line),就需要new DataUtils(),有点浪费资源,我们可以使用MapPartitions来操作数据,使用迭代器迭代出每一个分区的数据,一个分区只需要一个 new DataUtils().--------有几个分区,就new 几个.既解决了线程安全的问题,又不浪费资源–何乐而不为呢!
优化后的代码如下:
package day05.TaskThread
import Util.DateUtils
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object TaskThreadNotSafe02 {
def main(args: Array[String]): Unit = {
val isLocal = args(0).toBoolean
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName)
if (isLocal) {
conf.setMaster("local[*]")
}
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(1))
val timeRDD: RDD[Long] = lines.mapPartitions(it => {
//一个分区new一个DateUtils
val datautils = new DateUtils
it.map(line => {
datautils.parse1(line)
})
})
val r: Array[Long] = timeRDD.collect()
println(r.toBuffer)
sc.stop()
}
}
好多同学说,还有一种解决方案是加锁,这种方法与Spark的并行运算逻辑有悖,Spark之所以快速处理大容量的数据,就是因为它是分布式并行运算.
本人并不推荐这种方法.(仅代表本人观点)