作为被面试官最喜欢问到的23种设计模式之一,我们不得不熟练掌握单例模式以及洞悉多线程环境下,单例模式所存在的非线程安全问题以及它的解决方式。 注:这篇文章主要讲述多线程环境下单例模式存在的非线程安全问题,并不详细讲述单例模式。 何为单例模式 首先我们先大概了解一下单例模式的定义: 单例类只能有一个实例。 单例类必须自己创建自己的唯一实例。 单例类必须给所有其他对象提供这一实例。 单例模式的应用非常广泛,例如在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。选择单例模式就是为了避免不一致状态。 单例模式的实现有三种方式:饿汉式(天生线程安全),懒汉式,登记式(可忽略)。 对于上面单例模式的实现方式我在这里不做过多介绍,我们着重来看一下懒汉式在多线程环境下出现的问题以及它的解决策略。 设计线程安全的单例模式 D 继续阅读 >>


董恒毅 17/08/29 14:14:58
在上一篇博客JVM–解析Java内存区域及数据的内存分配与线程安全之间的一些联系中也说到了,想要理解volatile关键字,我们需要掌握Java虚拟机运行时数据区的相关知识,但是这还不够,只有理解了Java的内存模型,我们才能开始讲述volatile,而Java虚拟机运行时数据区是掌握Java内存模型的基础,所以如果你还没有看上一篇博客,请点击上方链接~~~ 引言 既然本节讲述volatile关键字,那么就先抛个砖引个玉(以下代码在64位jdk1.8下进行测试,不同jdk版本运行结果有可能不一样): public class RunThread implements Runnable { private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRu 继续阅读 >>


董恒毅 17/08/16 09:39:38
一直觉得自己之前写的使用定时抓取构建IP代理池实在过于简陋,并且有一部分的代码写的并不合理,刚好最近又在学习多线程,就将之前的代码进行了重构,也方便对抓取代理ip有需求的人。之前自己写的那篇文章就不删除了,里面用到了MySQL以及循环调用ip的方法(一些东西也是值得了解的。取其精华,弃其糟粕吧),大家有兴趣的可以看一下(最主要的还是不舍得访问量,哈哈)。 注:由于xici代理网的ip代理并不是很稳定,所以自己实现的ip代理池并不可用(4000个IP最后通过一系列逻辑处理和过滤之后大概只剩30多个,并且这30个ip也极不稳定),但感觉实现ip代理池的原理就是这样,在往后估计也就是性能上的优化。虽然ip代理池并不可用,但是如果你想要学习多线程以及抓取ip做爬虫的话,这篇文章我觉得是可以给你一些思路上的启发。 怎么设计一个IP代理池 其实设计一个IP代理池是非常容易的一件事情,我们来看一下其中的步骤: 1.首先肯定是从提供代理ip的网站上对ip进行抓取 2.对抓取下来的ip进行初步的过滤,比如我一开始就将IP类型不是HTTPS并且IP链接 继续阅读 >>


董恒毅 17/08/10 17:16:02
最近一直在看《Java多线程编程核心技术》的第二章,主要讲的是线程共享变量与线程私有变量以及如何写出线程安全的代码。看这部分一开始没太注意,只是记住了一条规则,“类中的成员变量,也叫实例变量,也叫全局变量,它是非线程安全,是所有线程共享的变量,定义在方法中的私有变量是线程安全的,是每个线程私有的”。很好理解不是吗,然后一帆风顺的看到了关于volatile这部分的知识,看过之后我陷入了凌乱。。。关于这部分我之后进行总结,而现在我觉得你如果真的想写出线程安全的代码,那么Java的内存分配以及布局就是我们需要掌握的基础。为此,我粗略的看了一下《深入理解Java虚拟机》这本书的第二章,并且查阅了一些资料,现在汇总整理如下。 注:学习这部分内容之前如果你对进程的内存映像或数据在内存中的分配有大概的了解,建议你先忘记它们,因为这是讲Java虚拟机运行时的数据区,和之前的知识并不相同,所以学习的时候不要拿自己以前所了解的知识进行比较与衡量。 Java虚拟机运行时的数据区 先来看一张图片: 在这里我们只需要关注线程共享区中的堆,以及线程独占区中的虚拟机栈, 继续阅读 >>


董恒毅 17/08/08 14:26:58
注:本系列博客参考《Java多线程编程核心技术》,主要是对书上的知识点进行总结,并记录学习过程。 一直对并发这块比较感兴趣,也到了系统学习Java多线程的时间。目前所学习的书籍是《Java多线程编程核心技术》,买回来之后听说这本书不怎么样,豆瓣评分也就7点几,目前读完了第一章,感觉确实不是很好,但是也不算太坑,总的来说还是可以入手的。好了,废话不多说,开始正题。 首先我们来看一份代码: public class CountOperate extends Thread { public CountOperate() { out.println("CountOperate---begin"); out.println("Thread.currentThread.getName()=" + Thread.currentThread().getName()); out.println("this.name()=" + this.getName()); out.println("Thr 继续阅读 >>


董恒毅 17/08/04 10:15:20
为什么我们需要掌握这些“高端”的树型结构 事实上,大型数据库的组织结构一般采用树型结构,我们必须要解决频繁更新数据的能力,要求支持高效的动态查找能力,包括记录的插入,删除,精确匹配查询,范围查询和最大值、最小值查询。但是由于数据库中包含了大量的记录,所以线性表的查询本身会因为记录太大而无法存储到主存之中,另外对于记录的插入和删除操作更需要移动大量的元素,这本身的效率是非常低下的。 二叉查找树(BST)的定义 二叉查找树要么是一颗空树,要么满足以下的定义: 若它的左子树不为空,那么它左子树上所有节点的值均小于等于根节点 若它的右子树不为空,那么它右子树上所有节点的值均小于根节点 它的左右子树均是二叉查找树 这明显是一个递归定义,因此我们对于BST的操作大多是建立在递归之上的。 二叉查找树有一个重要的特征:对一颗二叉查找树进行中序遍历,可以得到一个递增序列,那么我们只要将中序遍历的遍历顺序反过来,那么我们就会得到一个递减序列,这也正是二叉查找树得名的原因。 BST的数据结构 typedef int 继续阅读 >>


董恒毅 17/08/03 10:16:00
何为堆 一个数组序列我们可以将其用完全二叉树或近似完全二叉树(不是满二叉树的完全二叉树)表示出来,当数组下标为i时,它的父节点为(i-1)/2,左孩子为(2i+1),右孩子为(2i+2),这种对应关系说明数组下标为0的地方也要存储数据。(关于完全二叉树和满二叉树我在这里不做介绍) 堆是在完全二叉树的基础上递归定义的,堆分为大顶堆和小顶堆。 大顶堆:根节点的数值大于孩子节点,完全二叉树的左右子树同时满足这个条件。 小顶堆:根节点的数值小于孩子节点,完全二叉树的左右子树同时满足这个条件。 从这种数据结构中我们可以发现:大顶堆的根节点也就是数组的第一个元素必定是最大值,而小顶堆必定是最小值,看到这,我想大家已经大概能感觉的到堆这种数据结构为什么可以用来排序了。 在来看个大顶堆和小顶堆的图解吧: 堆排序的过程 要想写出堆排序的代码,首先我们一定要清楚堆排序的过程,根据堆这种数据结构的特性,我总结了一下堆排序的过程: 首先我们需要将一个数组初始化为堆 在初始化堆的过程中我们必定要移动数组中元素的位置 初始化完 继续阅读 >>


董恒毅 17/07/27 20:25:04
快速排序实现代码:快速排序 可以看到我的代码有一个错误版,我在这里给大家分析一下为什么会出现错误,并且将之记录以便今后进行查阅。 快速排序(错误版分析) int Quick :: process(int array[], int l, int r) { int temp = array[l]; while(l != r) { while(array[r] >= temp) r--; array[l] = array[r]; l++; while(array[l] <= temp) l++; array[r] = array[l]; r--; } array[l] = temp; return l; } 如上是我自己实现的单趟快速排序。 快排的算法思想 从待排序列中任意选择一个记录,以该记录为关键字,凡数组中元素小于该关键字的都移动至该关键字前面,反之移动到后面。致使一趟快速排序之后,以关 继续阅读 >>


董恒毅 17/07/22 00:20:16
理解归并 一接触算法,我就觉得自己应该去转行,一个归并排序可以说是实现了一整天,从看书到整理思路,看书上的代码,然后再根据自己的思想写一遍,感觉其中的过程是如此艰难,而且还是在参照书本的前提下进行的学习,真心好累。。。 不过功夫不负有心人,总算是将自顶向下的归并排序实现出来了,我觉得归并排序最大的难点就在于两次递归,要理清这个思路是比较复杂的,我就将自己学习归并排序的艰难路程记录下来,供大家参考学习。 首先我们需要理解什么是归并,假设我们现在已经有两个有序的数组,现在要将两个有序的数组合并为一个大的依旧有序的数组,这个过程就叫做归并,也是归并排序所要求的最基础的操作。我们来看一下这个归并的实现代码:(这个代码的实现还是很简单的) #include<iostream> using namespace std; #define SIZE 10 int main() { int array[SIZE]; //辅助数组 int array_result[SIZE]; int mid = SIZE/2; i 继续阅读 >>


董恒毅 17/07/21 15:04:15
设定防火墙开放指定端口 由于自己的腾讯云突然无法访问8080端口,在网上查阅了相关资料之后发现是防火墙的问题,因为Centos 7防火墙默认是不开放任何端口的,所以我们要对防火墙进行设置。 Centos 7 不在使用以前的iptables,而是对防火墙进行了加强,现在使用的是firewalld,它的位置在/usr/lib/firewalld(系统配置)和/etc/firewalld(用户配置)都有相关设置,并不建议修改/usr/lib/firewalld因为这是系统配置。 现在我们要开启8080端口,只要运行以下命令就可以了: 1.查看已开放的端口(默认不开放任何端口) firewall-cmd --list-ports 2.开启8080端口 firewall-cmd --zone=public(作用域) --add-port=8080/tcp(端口和访问类型) --permanent(永久生效) 3.重启防火墙 firewall-cmd --reload 4.移除指定端口 firewall-cmd --zone= public --remo 继续阅读 >>


董恒毅 17/07/10 11:34:15