MySQL中文全文索引插件 mysqlcft 1.0.0 安装使用文档

MySQL在高并发连接、数据库记录数较多的情况下,SELECT ... WHERE ... LIKE '%...%'的全文搜索方式不仅效率差,而且以通配符%和_开头作查询时,使用不到索引,需要全表扫描,对数据库的压力也很大。MySQL针对这一问题提供了一种全文索引解决方案,这不仅仅提高了性能和效率(因为MySQL对这些字段做了索引来优化搜索),而且实现了更高质量的搜索。但是,至今为止,MySQL对中文全文索引无法正确支持。

  中文与西方文字如英文的一个重要区别在于,西方文字以单词为单位,单词与单词之间以空格分隔。而中文以字为单位,词由一个或多个字组成,词与词之间没有空格分隔。当试图在一个含有中文字符的字段中使用全文搜索时,不会得到正确的结果,原因在于中文中没有像英文空格那样对词定界,不能以空格作为分割,对中文词语进行索引。

  引用《MySQL 5.1参考手册》中的一段话:

引用
12.7. 全文搜索功能(http://dev.mysql.com/doc/refman/5.1/zh/functions.html
● MySQL支持全文索引和搜索功能。MySQL中的全文索引类型FULLTEXT的索引。FULLTEXT 索引仅可用于 MyISAM 表;他们可以从CHAR、 VARCHAR或TEXT列中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或 CREATE INDEX被添加。对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引, 其速度比把资料输入现有FULLTEXT索引的速度更为快。

● FULLTEXT分析程序会通过寻找某些分隔符来确定单词的起始位置和结束位置,例如' ' (间隔符号)、 , (逗号)以及 . (句号 )。假如单词没有被分隔符分开,(例如在中文里 ), 则 FULLTEXT 分析程序不能确定一个词的起始位置和结束位置。为了能够在这样的语言中向FULLTEXT 索引添加单词或其它编入索引的术语,你必须对它们进行预处理,使其被一些诸如"之类的任意分隔符分隔开。

● 诸如汉语和日语这样的表意语言没有自定界符。因此, FULLTEXT分析程序不能确定在这些或其它的这类语言中词的起始和结束的位置。



  国内已有的MySQL中文全文索引解决方案有两个:一是海量科技的MySQL5.0.37--LinuxX86-Chinese+,二是hightman开发的mysql-5.1.11-ft-hightman,两者都是基于中文分词技术,对中文语句进行拆分。但是,两者都有弊端,一是不支持64位操作系统;二是对修改了MySQL源码,只支持某一MySQL版本,不便于跟进新版本;三是词库不能做到很大很全,对于专业性质较强的数据库内容(例如搜索“颐和园路东口”、“清华东路西口”等公交站点,“莱镇香格里”、“碧海云天”等楼盘名称),基于中文分词的全文索引经常搜索不出来任何内容,即使添加分词词库,也不会很全面。

  由于精准全文查询的需要,我借鉴了二元交叉切分算法的思想,用自创的“三字节交叉切分算法”,写出了这款“MySQL中文全文索引插件──mysqlcft 1.0.0”。由于开发时间仓促,难免存在未发现的问题,这将后续的版本中不断完善。对于百万条记录的MySQL表进行全文检索,mysqlcft已经够用。
 



  Mysqlcft 网址:http://code.google.com/p/mysqlcft/

  Mysqlcft 作者:张宴
 



  一、MySQL中文全文索引插件mysqlcft的特点:
  1、优点:
  ①、精准度很高:采用自创的“三字节交叉切分算法”,对中文语句进行分割,无中文分词词库,搜索精准度远比中文分词算法高,能达到LIKE '%...%"的准确率。
  ②、查询速度快:查询速度比LIKE '%...%"搜索快3~50倍,文章末尾有测试结果;
  ③、标准插件式:以MySQL 5.1全文索引的标准插件形式开发,不修改MySQL源代码,不影响MySQL的其他功能,可快速跟进MySQL新版本;
  ④、支持版本多:支持所有的MySQL 5.1 Release Candidate版本,即MySQL 5.1.22 RC~最新的MySQL 5.1.25 RC;
  ⑤、支持字符集:支持包括GBK、GB2312、UTF-8、Latin1、BIG5在内的MySQL字符集(其他字符集没有测试过);
  ⑥、系统兼容好:具有i386和x86_64两个版本,支持32位(i386)和64位(x86_64)CPU及Linux系统;
  ⑦、适合分布式:非常适合MySQL Slave分布式系统架构,无词库维护成本,不存在词库同步问题。

  2、缺点:
  ①、mysqlcft中文全文索引只适用于MyISAM表,因为MySQL只支持对MyISAM表建立FULLTEXT索引;
  ②、MySQL不能静态编译安装,否则无法安装mysqlcft插件;
  ③、基于“三字节交叉切分算法”的索引文件会比海量、ft-hightman等基于“中文分词算法”的索引文件稍大,但不是大很多。根据我的测试,mysqlcft全文索引的.MYI索引文件是.MYD数据文件的2~5倍。
 



  二、mysqlcft的核心思想──“三字节交叉切分算法”

  

点击在新窗口中浏览此图片



  注:本文以0~7数字序号代表“英文”、“数字”和“半个汉字”,以便说明。
  1、按三字节对中文语句进行切分,建立全文索引:
  例如:“全文索引”或“1台x光机”四个字会被交叉分拆为6份,建立反向索引:
  012  123  234  345  456  567

  2、按三字节对搜索的关键字进行切分,在全文索引中找出对应信息:
  例①:搜索关键字“文索”,用数字序号表示就是“2~5”,那么它将被切分成:
  234  345
  这样,就与全文索引对上了。

  例②:搜索关键字“x光机”,用数字序号表示就是“3~7”,那么它将被切分成:
  345  456  567
  这样,也与全文索引对上了。

  例③:搜索关键字“1台 光机”,用数字序号表示就是“0~2”和“4~7”,那么它将被切分成:
  012  456  567
  这样,多关键字搜索也与全文索引对上了。



  三、编译安装MySQL(如果已经装有不是静态编译安装的MySQL 5.1.22 RC~MySQL 5.1.25 RC,此步骤可省略)
  1、下载并编译安装MySQL 5.1.25 RC
  在http://dev.mysql.com/get/Downloads/MySQL-5.1/mysql-5.1.25-rc.tar.gz/from/pick(点击No thanks, just take me to the downloads!链接),选择一个镜像,下载MySQL 5.1.25 RC源码包:

tar zxvf mysql-5.1.25-rc.tar.gz
cd mysql-5.1.25-rc/
./configure --prefix=/usr/local/mysqlcft/ --without-debug --enable-assembler --with-extra-charsets=all --with-pthread --enable-thread-safe-client
make && make install

/usr/sbin/groupadd mysql
/usr/sbin/useradd -g mysql mysql
chmod +w /usr/local/mysqlcft
chown -R mysql:mysql /usr/local/mysqlcft



  2、创建MySQL数据文件存放目录/mysql/3306

mkdir -p /mysql/3306
chmod +w /mysql/3306
chown -R mysql:mysql /mysql/3306
mkdir -p /mysql/3306/data
chmod +w /mysql/3306/data
chown -R mysql:mysql /mysql/3306/data
chown -R mysql:mysql /mysql
#cp support-files/my-medium.cnf /mysql/3306/my.cnf
cd ../



  3、创建配置文件/mysql/3306/my.cnf

vi /mysql/3306/my.cnf


  输入以下内容(注意:必须设置ft_min_word_len = 1):

引用
[client]
#password       = your_password
port            = 3306
socket          = /mysql/3306/mysql.sock
default-character-set = gbk

[mysqld_safe]
datadir = /mysql/3306/data
log-error = /mysql/3306/mysql_error.log
pid-file = /mysql/3306/mysql.pid

[mysqld]
port            = 3306
socket          = /mysql/3306/mysql.sock
default-character-set = gbk
#init_connect = 'SET NAMES gbk'
skip-locking
#skip-slave-start
key_buffer = 512M
max_allowed_packet = 2M
table_cache = 1024
sort_buffer_size = 32M
read_buffer_size = 2M
read_rnd_buffer_size = 32M
max_length_for_sort_data = 64
myisam_sort_buffer_size = 128M
thread_cache = 8
query_cache_size = 64M
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8
#skip-name-resolve
set-variable = max_connections=1000
open_files_limit = 51200
ft_min_word_len = 1

low_priority_updates = 1
slave-skip-errors = 1032,1062,126
server-id       = 9
#master-host     =   host
#master-user     =   user
#master-password =   password
#master-port     =  3306
#replicate-do-db = db1
#replicate-do-db = db2

[mysqldump]
quick
max_allowed_packet = 16M

[mysql]
no-auto-rehash
# Remove the next comment character if you are not familiar with SQL
#safe-updates

[isamchk]
key_buffer = 256M
sort_buffer_size = 256M
read_buffer = 2M
write_buffer = 2M

[myisamchk]
key_buffer = 256M
sort_buffer_size = 256M
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout



  附:MySQL配置文件在全文索引应用中的优化

引用
[mysqld]
# key_buffer 指定用于索引的缓冲区大小,在全文索引中,增加它可得到更好的索引处理与查询性能
key_buffer = 512M

# sort_buffer_size 为查询排序时所能使用的缓冲区大小,全文索引的SQL语句之后通常会使用ORDER BY排序,增加它可以加快SQL语句执行时间。该参数对应的分配内存是每连接独占,100个连接使用的内存将是32M*100=3200M
sort_buffer_size = 32M

# 对大于可用内存的表执行GROUP BY或ORDER BY操作,应增加read_rnd_buffer_size的值以加速排序操作后面的行读取
read_rnd_buffer_size = 64M

# 如果表出现故障或索引出错,REPAIR TABLE时用到的缓冲区大小
myisam_sort_buffer_size = 128M

# 确定使用的filesort算法的索引值大小的限值
max_length_for_sort_data = 64

# MySQL全文索引查询所用关键词最小长度限制(不要改变这项值)
ft_min_word_len = 1

# 降低UPDATE优先级,设置查询优先
low_priority_updates = 1



  4、以mysql用户帐号的身份建立数据表

/usr/local/mysqlcft/bin/mysql_install_db --defaults-file=/mysql/3306/my.cnf --basedir=/usr/local/mysqlcft --datadir=/mysql/3306/data --user=mysql --pid-file=/mysql/3306/mysql.pid --skip-locking --port=3306 --socket=/mysql/3306/mysql.sock



  5、启动MySQL

/bin/sh /usr/local/mysqlcft/bin/mysqld_safe --defaults-file=/mysql/3306/my.cnf &



  附:停止MySQL

/usr/local/mysqlcft/bin/mysqladmin -u root -p -S /mysql/3306/mysql.sock shutdown


 



  四、安装mysqlcft中文全文索引插件
  1、从命令行登入MySQL服务器:

/usr/local/mysqlcft/bin/mysql -u root -p -S /mysql/3306/mysql.sock



  2、查看MySQL插件目录的默认路径的SQL语句:

SHOW VARIABLES LIKE 'plugin_dir';


  

点击在新窗口中浏览此图片

  3、下载mysqlcft中文全文索引插件,解压后拷贝mysqlcft.so文件到MySQL插件目录
  ①、32位Linux操作系统:

wget http://mysqlcft.googlecode.com/files/mysqlcft-1.0.0-i386-bin.tar.gz
tar zxvf mysqlcft-1.0.0-i386-bin.tar.gz
mkdir -p /usr/local/mysqlcft/lib/mysql/plugin/
cp mysqlcft.so /usr/local/mysqlcft/lib/mysql/plugin/



  ②、64位Linux操作系统:

wget http://mysqlcft.googlecode.com/files/mysqlcft-1.0.0-x86_64-bin.tar.gz
tar zxvf mysqlcft-1.0.0-x86_64-bin.tar.gz
mkdir -p /usr/local/mysqlcft/lib/mysql/plugin/
cp mysqlcft.so /usr/local/mysqlcft/lib/mysql/plugin/



  4、安装mysqlcft.so插件
  ①、从命令行登入MySQL服务器:

/usr/local/mysqlcft/bin/mysql -u root -p -S /mysql/3306/mysql.sock

②、安装mysqlcft.so插件的SQL语句:

INSTALL PLUGIN mysqlcft SONAME 'mysqlcft.so';
  ③、查看mysqlcft.so插件是否安装成功的SQL语句:
SELECT * FROM mysql.plugin;
SHOW PLUGINS;

点击在新窗口中浏览此图片



  附:如果要卸载mysqlcft.so插件,执行以下SQL语句(如果已经创建了mysqlcft索引,请先删除mysqlcft索引,再卸载mysqlcft.so插件):

UNINSTALL PLUGIN mysqlcft;


 



  五、为已经存在的表添加mysqlcft中文全文索引
  1、创建单列全文索引SQL语句

ALTER IGNORE TABLE 数据库名.表名 ADD FULLTEXT INDEX 全文索引名 (字段名) WITH PARSER mysqlcft;



  2、创建全文联合索引SQL语句

ALTER IGNORE TABLE 数据库名.表名 ADD FULLTEXT INDEX 全文联合索引名 (字段名1,字段名2) WITH PARSER mysqlcft;


 



  六、重建mysqlcft中文全文索引(索引损坏时需要用到)

REPAIR TABLE 数据库名.表名 QUICK;


 



  七、建表时创建mysqlcft中文全文索引+全文搜索测试
  1、以latin1字符集为例

CREATE DATABASE `mysqlcft_latin1` DEFAULT CHARACTER SET latin1;
USE `mysqlcft_latin1`;
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) DEFAULT NULL,
`body` mediumtext,
PRIMARY KEY (`id`),
FULLTEXT KEY `title_body` (`title`,`body`) WITH PARSER mysqlcft
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `mysqlcft_latin1`.`test` (`id`, `title`, `body`) VALUES (NULL, '北京房价', '北京市统计局、国家统计局北京调查总队近日联合对外发布消息,今年以来,北京的商品房价格一直呈上升趋势,五环路以内住宅期房均价已涨至13754元/平方米。');
INSERT INTO `mysqlcft_latin1`.`test` (`id`, `title`, `body`) VALUES (NULL, '北京中心城区今起可无线宽带上网 奥运期间免费', '新浪科技讯 6月25日消息,北京无线城市一期网络今日起试运行,即日起北京市民和海外游客可以通过无线网络在北京中心城区接入互联网。');
INSERT INTO `mysqlcft_latin1`.`test` (`id`, `title`, `body`) VALUES (NULL, '数据库', '欢迎使用MySQL中文全文索引插件mysqlcft!');

SELECT * FROM mysqlcft_latin1.test WHERE MATCH(title,body) AGAINST ('13754元/平方米' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_latin1.test WHERE MATCH(title,body) AGAINST ('6月25日' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_latin1.test WHERE MATCH(title,body) AGAINST ('北京' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_latin1.test WHERE MATCH(title,body) AGAINST ('北京 宽带' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_latin1.test WHERE MATCH(title,body) AGAINST ('mysqlcft' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_latin1.test WHERE MATCH(title,body) AGAINST ('数据' IN BOOLEAN MODE);



  2、以gbk字符集为例

CREATE DATABASE `mysqlcft_gbk` DEFAULT CHARACTER SET gbk;
USE `mysqlcft_gbk`;
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) DEFAULT NULL,
`body` mediumtext,
PRIMARY KEY (`id`),
FULLTEXT KEY `title_body` (`title`,`body`) WITH PARSER mysqlcft
) ENGINE=MyISAM DEFAULT CHARSET=gbk;

INSERT INTO `mysqlcft_gbk`.`test` (`id`, `title`, `body`) VALUES (NULL, '北京房价', '北京市统计局、国家统计局北京调查总队近日联合对外发布消息,今年以来,北京的商品房价格一直呈上升趋势,五环路以内住宅期房均价已涨至13754元/平方米。');
INSERT INTO `mysqlcft_gbk`.`test` (`id`, `title`, `body`) VALUES (NULL, '北京中心城区今起可无线宽带上网 奥运期间免费', '新浪科技讯 6月25日消息,北京无线城市一期网络今日起试运行,即日起北京市民和海外游客可以通过无线网络在北京中心城区接入互联网。');
INSERT INTO `mysqlcft_gbk`.`test` (`id`, `title`, `body`) VALUES (NULL, '数据库', '欢迎使用MySQL中文全文索引插件mysqlcft!');

SELECT * FROM mysqlcft_gbk.test WHERE MATCH(title,body) AGAINST ('13754元/平方米' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_gbk.test WHERE MATCH(title,body) AGAINST ('6月25日' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_gbk.test WHERE MATCH(title,body) AGAINST ('北京' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_gbk.test WHERE MATCH(title,body) AGAINST ('北京 宽带' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_gbk.test WHERE MATCH(title,body) AGAINST ('mysqlcft' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_gbk.test WHERE MATCH(title,body) AGAINST ('数据' IN BOOLEAN MODE);



  3、以UTF-8字符集为例

CREATE DATABASE `mysqlcft_utf8` CHARACTER SET utf8;
USE `mysqlcft_utf8`;
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) DEFAULT NULL,
`body` mediumtext,
PRIMARY KEY (`id`),
FULLTEXT KEY `title_body` (`title`,`body`) WITH PARSER mysqlcft
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `mysqlcft_utf8`.`test` (`id`, `title`, `body`) VALUES (NULL, '北京房价', '北京市统计局、国家统计局北京调查总队近日联合对外发布消息,今年以来,北京的商品房价格一直呈上升趋势,五环路以内住宅期房均价已涨至13754元/平方米。');
INSERT INTO `mysqlcft_utf8`.`test` (`id`, `title`, `body`) VALUES (NULL, '北京中心城区今起可无线宽带上网 奥运期间免费', '新浪科技讯 6月25日消息,北京无线城市一期网络今日起试运行,即日起北京市民和海外游客可以通过无线网络在北京中心城区接入互联网。');
INSERT INTO `mysqlcft_utf8`.`test` (`id`, `title`, `body`) VALUES (NULL, '数据库', '欢迎使用MySQL中文全文索引插件mysqlcft!');

SELECT * FROM mysqlcft_utf8.test WHERE MATCH(title,body) AGAINST ('13754元/平方米' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_utf8.test WHERE MATCH(title,body) AGAINST ('6月25日' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_utf8.test WHERE MATCH(title,body) AGAINST ('北京' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_utf8.test WHERE MATCH(title,body) AGAINST ('北京 宽带' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_utf8.test WHERE MATCH(title,body) AGAINST ('mysqlcft' IN BOOLEAN MODE);
SELECT * FROM mysqlcft_utf8.test WHERE MATCH(title,body) AGAINST ('数据' IN BOOLEAN MODE);


   八、性能测试报告
  服务器:DELL PowerEdge 6850 (四颗双核Xeon 3.0GHz,8GB内存) 4U机架式服务器
  操作系统:RedHat AS4 (x86_64位)
  数据库:MySQL 5.1.25 RC + mysqlcft 1.0.0
  数据表:超过80万条(807346条)记录的表,字段“id”为int类型,主键;字段“title”为varchar类型,字段“body”为text类型。“title”和“body”分别建有INDEX普通单列索引、INDEX联合索引,FULLTEXT单字段全文索引、FULLTEXT联合全文索引。

  1、在字段“title”中搜索中文关键字:
SELECT * FROM database.table WHERE MATCH(title) AGAINST ('朝阳区' IN BOOLEAN MODE) limit 0,30;
30 rows in set (0.04 sec)
SELECT * FROM database.table WHERE title LIKE '%朝阳区%' limit 0,30;
30 rows in set (6.56 sec)

SELECT * FROM database.table WHERE MATCH(title) AGAINST ('通州区' IN BOOLEAN MODE) ORDER BY id DESC limit 0,30;
30 rows in set (0.13 sec)
SELECT * FROM database.table WHERE title LIKE '%通州区%' ORDER BY id DESC limit 0,30;
30 rows in set (8.15 sec)

SELECT * FROM database.table WHERE MATCH(title) AGAINST ('建国门外' IN BOOLEAN MODE) ORDER BY id DESC limit 0,30;
30 rows in set (0.08 sec)
SELECT * FROM database.table WHERE title LIKE '%建国门外%' ORDER BY id DESC limit 0,30;
30 rows in set (5.34 sec)

SELECT * FROM database.table WHERE MATCH(title) AGAINST ('靠近通惠河' IN BOOLEAN MODE) ORDER BY id DESC limit 0,30;
4 row in set (0.06 sec)
SELECT * FROM database.table WHERE title LIKE '%靠近通惠河%' ORDER BY id DESC limit 0,30;
4 row in set (12.88 sec)



  2、在字段“body”中搜索中文关键字:

SELECT * FROM database.table WHERE MATCH(body) AGAINST ('海淀区' IN BOOLEAN MODE) ORDER BY id DESC limit 0,30;
30 rows in set (0.23 sec)
SELECT * FROM database.table WHERE body LIKE '%海淀区%' ORDER BY id DESC limit 0,30;
30 rows in set (15.71 sec)

SELECT * FROM database.table WHERE MATCH(body) AGAINST ('莱镇香格里' IN BOOLEAN MODE) ORDER BY id DESC limit 0,30;
6 rows in set (0.18 sec)
SELECT * FROM database.table WHERE body LIKE '%莱镇香格里%' ORDER BY id DESC limit 0,30;
6 row in set (13.34 sec)


  3、在字段“title”和“body”中,搜索同时包含“西城区”和“商场”两个关键字的记录:
SELECT * FROM database.table WHERE MATCH(title,body) AGAINST ('西城区 商场' IN BOOLEAN MODE) ORDER BY id DESC limit 0,30;
13 rows in set (0.27 sec)
SELECT * FROM database.table WHERE title LIKE '%西城区%商场%' AND body LIKE '%西城区%商场%' ORDER BY id DESC limit 0,30;
13 rows in set (51.74 sec)

多线程 / 高并发

1. stop() 和 suspend() 方法为何不推荐使用?
反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象
处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出
真正的问题所在。
suspend() 方法容易发生死锁。调用 suspend() 的时候,目标线程会停下来,但却仍
然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被 "挂
起" 的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任
何一个锁定的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread
类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()
命其进入等待状态。若标志指出线程应当恢复,则用一个 notify() 重新启动线程。
2. sleep() 和 wait() 有什么区别?
sleep 就是正在执行的线程主动让出 cpu,cpu 去执行其他线程,在 sleep 指定的时
间过后,cpu 才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep
方法并不会释放锁,即使当前线程使用 sleep 方法让出了 cpu,但其他被同步锁挡住
了的线程也无法得到执行。wait 是指在一个已经进入了同步锁的线程内,让自己暂时
让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用
了 notify 方法(notify 并不释放锁,只是告诉调用过 wait 方法的线程可以去参与获
得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果 notify
多线程 / 高并发
方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在 notfiy 方法后
增加一个等待和一些代码,看看效果),调用 wait 方法的线程就会解除 wait 状态和
程序可以再次得到锁后继续向下运行。
3. 同步和异步有何异同,在什么情况下分别使用他们?
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读
的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等
待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
4. 当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其
它方法?
•  其他方法前是否加了 synchronized 关键字,如果没加,则能。
•  如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
•  如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不
能。
•  如果其他方法是 static,它用的同步锁是当前类的字节码,与非静态的方法不
能同步,因为非静态的方法用的是 this。
5. 简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?
主要相同点:Lock 能完成 synchronized 所实现的所有功能。
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。
synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally
从句中释放。Lock 还有更强大的功能,例如,它的 tryLock 方法可以非阻塞方式去拿
锁。
举例说明(对下面的题用 lock 进行了改写)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
/**
* @param args
*/
private int j;
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadTest tt = new ThreadTest();
for(int i=0;i<2;i++)
{
new Thread(tt.new Adder()).start();
new Thread(tt.new Subtractor()).start();
}
}
private class Subtractor implements Runnable
{
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
{
/*synchronized (ThreadTest.this) {
System.out.println("j--=" + j--);
//这里抛异常了,锁能释放吗?
}*/
lock.lock();
try
{
System.out.println("j--=" + j--);
}finally
{
lock.unlock();
}
}
}
}
private class Adder implements Runnable
{
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
{
/*synchronized (ThreadTest.this) {
System.out.println("j++=" + j++);
}*/
lock.lock();
try
{
System.out.println("j++=" + j++);
}finally
{
lock.unlock();
}
}
}
}
}
6. 概括的解释下线程的几种可用状态。
•  新建 new。
•  就绪 放在可运行线程池中,等待被线程调度选中,获取 cpu。
•  运行 获得了 cpu。
•  阻塞
o  等待阻塞 执行 wait() 。
o  同步阻塞 获取对象的同步琐时,同步锁被别的线程占用。
o  其他阻塞 执行了 sleep() 或 join() 方法)。
•  死亡。
7. 什么是 ThreadLocal?
ThreadLocal 用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全
局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用
同步的时候,我们可以选择 ThreadLocal 变量。
每个线程都会拥有他们自己的 Thread 变量,它们可以使用 get()\set() 方法去获取他
们的默认值或者在线程内部改变他们的值。ThreadLocal 实例通常是希望它们同线程
状态关联起来是 private static 属性。
8. run() 和 start() 区别。
run( ):只是调用普通 run 方法
start( ):启动了线程, 由 Jvm 调用 run 方法
启动一个线程是调用 start() 方法,使线程所代表的虚拟处理机处于可运行状态,这意
味着它可以由 JVM 调度并执行。这并不意味着线程就会立即运行。run() 方法可以产
生必须退出的标志来停止一个线程。
9. 请说出你所知道的线程同步的方法。
wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。 sleep():使一个
正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉
InterruptedException 异常。 notify():唤醒一个处于等待状态的线程,注意的是在
调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒
哪个线程,而且不是按优先级。 notityAll():唤醒所有处入等待状态的线程,注意并
不是给所有唤醒线程一个对象的锁,而是让它们竞争。
10. 线程调度和线程控制。
线程调度(优先级):与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线
程获取 CPU 资源的概率较大,优先级低的并非没机会执行。 线程的优先级用 1-10 之
间的整数表示,数值越大优先级越高,默认的优先级为 5。 在一个线程中开启另外一
个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
线程控制
•  sleep( ) // 线程休眠 join( ) // 线程加入 yield( ) // 线程礼让
setDaemon( ) // 线程守护
中断线程
•  stop( ) interrupt( ) ==(首先选用)==
11. 什么是线程饿死,什么是活锁?
当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。
JavaAPI 中线程活锁可能发生在以下情形:
•  当所有线程在序中执行 Object.wait(0),参数为 0 的 wait 方法。程序将发生
活锁直到在相应的对象上有线程调用 Object.notify() 或者 Object.notifyAll()。
•  当所有线程卡在无限循环中。
12. 多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法 wait(), sleep() 或 yield() 它
们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么做
的目的是为了保留 CPU 缓存。
在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。
为了避免重建缓存和减少等待重建的时间就可以使用它了。
13. volatile 变量是什么?volatile 变量和 atomic 变量有什么不同?
volatile 则是保证了所修饰的变量的可见。因为 volatile 只是在保证了同一个变量在
多线程中的可见性,所以它更多是用于修饰作为开关状态的变量,即 Boolean 类型的
变量。
volatile 多用于修饰类似开关类型的变量、Atomic 多用于类似计数器相关的变量、其
它多线程并发操作用 synchronized 关键字修饰。
volatile 有两个功用:
•  这个变量不会在多个线程中存在复本,直接从内存读取。
•  这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后
面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障
之前。
14. volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?
volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。
在 Java 中除了 long 和 double 之外的所有基本类型的读和赋值,都是原子性操作。
而 64 位的 long 和 double 变量由于会被 JVM 当作两个分离的 32 位来进行操
作,所以不具有原子性,会产生字撕裂问题。但是当你定义 long 或 double 变量时,
如果使用 volatile 关键字,就会获到(简单的赋值与返回操作的)原子性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值