一步一步掌握java的线程机制(一)----创建线程
现在将1年前写的有关线程的文章再重新看了一遍,发现过去的自己还是照本宣科,毕竟是刚学java的人,就想将java的精髓之一---线程进制掌握到手,还是有点难度。等到自己已经是编程一年级生了,还是无法将线程这个高级的概念完全贯通,所以,现在趁着自己还在校,尽量的掌握多点有关线程机制的知识。
我们以一个简单的例子开始下手:
public class SwingTypeTester extends JFrame implements CharacterSource{ protected RandomCharacterGenerator producer; private CharacterDisplayCanvas displayCanvas; private CharacterDisplayCanvas feedbackCanvas; private JButton quitButton; private JButton startButton; private CharacterEventHandler handler; public SwingTypeTester() { initComponents(); } private void initComponents() { handler = new CharacterEventHandler(); displayCanvas = new CharacterDisplayCanvas(); feedbackCanvas = new CharacterDisplayCanvas(); quitButton = new JButton(); startButton = new JButton(); add(displayCanvas, BorderLayout.NORTH); add(feedbackCanvas, BorderLayout.CENTER); JPanel p = new JPanel(); startButton.setLabel("Start"); quitButton.setLabel("Quit"); p.add(startButton); p.add(quitButton); add(p, BorderLayout.SOUTH); addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent evt){ quit(); } }); feedbackCanvas.addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent ke){ char c = ke.getKeyChar(); if(c != KeyEvent.CHAR_UNDEFINED){ newCharacter((int)c); } } }); startButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { producer = new RandomCharacterGenerator(); displayCanvas.setCharacterSource(producer); producer.start(); startButton.setEnabled(false); feedbackCanvas.setEnabled(true); feedbackCanvas.requestFocus(); } }); quitButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { quit(); } }); pack(); } private void quit(){ System.exit(0); } public void addCharacterListener(CharacterListener cl){ handler.addCharacterListener(cl); } public void removeCharacterListener(CharacterListener cl){ handler.removeCharacterListener(cl); } public void newCharacter(int c){ handler.fireNewCharacter(this, c); } public void nextCharacter(){ throw new IllegalStateException("We don't produce on demand"); } public static void main(String[] args){ new SwingTypeTester().show(); } }
这是一个java的Swing小例子,就是每隔一段时间就会显示一个随机的字母或者数字。具体的源码我会放在后面,现在只是对其中涉及到线程的部分进行重点讲解。
使用到线程的地方就只有那个显示下一个字母或者数字的功能,它需要在前一个字母或者数字在显示一段时间后显示出来,并且它的产生是不断进行的,除非我们按下停止按钮。这是需要一个线程不断在运行的:
public class RandomCharacterGenerator extends Thread implements CharacterSource { static char[] chars; static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789"; static { chars = charArray.toCharArray(); } Random random; CharacterEventHandler handler; public RandomCharacterGenerator() { random = new Random(); handler = new CharacterEventHandler(); } public int getPauseTime() { return (int) (Math.max(1000, 5000 * random.nextDouble())); } @Override public void addCharacterListener(CharacterListener cl) { handler.addCharacterListener(cl); } @Override public void removeCharacterListener(CharacterListener cl) { handler.removeCharacterListener(cl); } @Override public void nextCharacter() { handler.fireNewCharacter(this, (int) chars[random.nextInt(chars.length)]); } public void run() { for (;;) { nextCharacter(); try { Thread.sleep(getPauseTime()); } catch (InterruptedException ie) { return; } } } }
虽然方法多,但是这个类只有一个方法run()方法是值得我们注意的。
开启线程的方式是非常简单的,只要声明一个Thread,然后在适当的时候start就行。创建Thread的方式可以像是这样,创建一个Thread的子类,然后实现它的run()方法,在run()方法中进行该线程的主要工作。当然,我们也可以在需要线程的地方才创建一个Thread,但是这里的情况就是我们的Thread类还需要实现其他接口(当然,这个设计并不好,但我们会以这个例子的逐步完善工作,将一些线程的基本知识融会进去)。
要想明白线程机制,我们还是得从一些基本内容的概念下手。感谢一年前的我,虽然文章写得不咋样,但是作为一个勤奋的记录员,还是将一些基本知识都记录下来,省得我去找。
线程和进程是两个完全不同的概念,进程是运行在自己的地址空间内的自包容的程序,而线程是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个线程。
还有一个抽象的概念,就是任务和线程的区别。线程似乎是进程内的一个任务,但实际上在概念上两者并不一样。准确点讲,任务是由执行线程来驱动的,而任务是附着在线程上的。
现在正式讲讲线程的创建。
正如我们前面讲的,任务是由执行线程驱动的,没有附着任务的线程根本就不能说是线程,所以我们在创建线程的时候,将任务附着到线程上。所谓的任务,对应的就是Runnable,我们要在这个类中编写相应的run()方法来描述这个任务所要执行的命令,接着就是将任务附着到线程上。像是这样:
Thread thread = new Thread(new Runnable(){ @Override public void run(){ ... } });
接着我们只要通过start()启动该Thread就行。
如果我们在main()方法中启动线程,我们就会发现,就算线程还没有执行完毕,剩下的代码还是会被运行。这是因为我们的main()方法也是一个线程,我们可以在main线程中启动一个线程。所以,任何线程都可以开启另一个线程。
这样的话,问题也就来了:如果一个程序中开启了多个线程,那么,它们的执行顺序是怎样的,毕竟程序的内存空间是有限的,不可能允许无限多个线程同时进行。事实就是,它们是交替进行的,而且还是我们无法控制的,是由线程调度器控制的,而且每次执行的顺序都是不一样的!
这就是多线程最大的问题,如果我们的程序设计不好,在这样的情况下,就很容易出现问题,而且是我们所无法把握的问题。
另一种方式就是上面使用的:创建一个Thread的子类,然后实现run()方法,接着同样是通过start()来开启它。
这两种方式到底应该采取哪种好呢?如果不想类的管理太麻烦,建议还是采取第一种方式,而且这也是我们在大部分的情况下所采用的,它充分使用了java的匿名内部类,但如果还想我们的Thread能够体现出其他行为而不单单只是个执行任务的线程,那么可以采取第二种方式,这样我们可以通过实现接口的方式让Thread具有更多的功能,但是必须注意,Thread的子类只能承载一个任务,但是第一种方式却可以非常自由的根据需要创建相应的任务。
除了上面两种方法,java还提供了第三种方法:Executor(执行器)。
Executor会在客户端和任务之间提供一个间接层,由这个间接层来执行任务,并且允许管理异步任务的执行,而无需通过显式的管理线程的生命周期。
ExecutorService exec = Executors.newCachedThreadPool(); exec.executor(new RunnableClass);
其中,CachedThreadPool是一种线程池。线程池在多线程处理技术中是一个非常重要的概念,它会将任务添加到队列中,然后在创建线程后自动启动这些任务。线程池的线程都是后台线程,每个线程都是用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。线程池中的线程数目是有一个最大值,但这并不意味着只能运行这样多的线程,它的真正意思是同时能够运行的最大线程数目,所以可以等待其他线程运行完毕后再启动。
线程池都有一个线程池管理器,用于创建和管理线程池,还有一个工作线程,也就是线程池中的线程。我们必须提供给线程池中的工作线程一个任务,这些任务都是实现了一个任务接口,也就是Runnable。线程池还有一个重要的组成:任务队列,用于存放没有处理的任务,这就是一种缓冲机制。
通过线程池的介绍,我们可以知道,使用到线程池的情况就是这样:需要大量的线程来完成任务,并且完成任务的时间比较短,就像是我们现在的服务器,同时间接受多个请求并且处理这些请求。
java除了上面的CachedThreadPool,还有另一种线程池:FixedThreadPool。CachedThreadPool会在执行过程中创建与所需数量相同的线程,然后在它回收旧线程的时候停止创建新的线程,也就是说,它每次都要保证同时运行的线程的数量不能超过所规定的最大数目。而FixedThreadPool是一次性的预先分配所要执行的线程,像是这样:
ExecutorService exec = Executors.newFixedThreadPool(5);
就是无论要分配的线程的数目是多少,都是运行5个线程。这样的好处是非常明显的,就是用于限制线程的数目。CachedThreadPool是按需分配线程,直到有的线程被回收,也就是出现空闲的时候才会停止创建新的线程,这个过程对于内存来说,代价是非常高昂的,因为我们不知道实际上需要创建的线程数量是多少,只会一直不断创建新线程。
看上去似乎FixedThreadPool比起CachedThreadPool更加好用,但实际上使用更多的是CachedThreadPool,因为一般情况下,无论是什么线程池,现有线程都有可能会被自动复用,而CachedThreadPool在线程结束的时候就会停止创建新的线程,也就是说,它能确保结束掉的线程的确是结束掉了,不会被重新启动,而FixedThreadPool无法保证这点。
接下来我们可以看看使用上面两种线程池的简单例子:
public void main(String[] args){ ExecutorService cachedExec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++){ cachedExec.execute(new RunnableClass); } cachedExec.shutdown(); ExecutorService fixedExec = Executors.newFixedThreadPool(3); for(int i = 0; i < 5; i++){ fixedExec.execute(new RunnableClass); } fixedExec.shutdown();
}
CachedThreadPool会不断创建线程直到有线程空闲下来为止,而FixedThreadPool会用3个线程来执行5个任务。
在java中,还有一种执行线程的模式:SingleThreadExecutor。顾名思义,该执行器只有一个线程。它就相当于数量为1的FixedThreadPool,如果我们向它提交多个任务,它们就会按照提交的顺序排队,直到上一个任务执行完毕,因为它们就只有一个线程可以运行。这种方式是为了防止竞争,因为任何时刻都只有一个任务在运行,从而不需要同步共享资源。
竞争是线程机制中一个非常重要的现象,有关于它的解决贯穿了整个线程机制的发展,而且可怕的是,就算是合理的解决方案,也无法保证我们已经完全避免了这个问题,因为无法预知的错误仍然存在于不远的将来。
知识点
相关教程
更多一步一步掌握线程机制(五)---等待与通知机制
在之前我们关于停止Thread的讨论中,曾经使用过设定标记done的做法,一旦done设置为true,线程就会结束,一旦为false,线程就会永远运行下去。这样做法会消耗掉许多CPU循环,是一种对内存不友好的行为。 java中的对象不仅拥有锁,而且它们本身就可以通过调用相关方法使自己成为等待者和通知者。 Object对象本身有两个方法:wait()和notify()。wait()会等待条件的发生,
一步一步掌握java的线程机制(二)----Thread的生命周期
之前讲到Thread的创建,那是Thread生命周期的第一步,其后就是通过start()方法来启动Thread,它会执行一些内部的管理工作然后调用Thread的run()方法,此时该Thread就是alive(活跃)的,而且我们还可以通过isAlive()方法来确定该线程是否启动还是终结。 一旦启动Thread后,我们就只能执行一个方法:run(),而run()方法就是负责执行Thread的任务,
一步一步掌握线程机制(三)---synchronized和volatile的使用
现在开始进入线程编程中最重要的话题---数据同步,它是线程编程的核心,也是难点,就算我们理解了数据同步的基本原理,但是我们也无法保证能够写出正确的同步代码,但基本原理是必须掌握的。 要想理解数据同步的基本原理,首先就要明白,为什么我们要数据同步? public class CharacterDisplayCanvas extends JComponent implements
一步一步掌握线程机制(四)---同步方法和同步块
在之前例子的基础上,我们增加新的功能:根据正确与不正确的响应来显示玩家的分数。 public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private Characte
一步一步学solr:什么是solr?
简介 Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果; 特点 Solr是一个独立的企业搜索服务器REST-like API。 你把文件(称为“索引”) 通过XML、JSON、CSV通过HTTP或二进
一步一步学lucene——(第一步:概念篇)【转】
一步一步学lucene——(第一步:概念篇) 信息检索的概念 信息检索(Information Retrieval)是指信息按一定的方式组织起来,并根据信息用户的需要找出有关的信息的过程和技术。狭义的信息检索就是信息检索过程的后半部分,即从 信息集合中找出所需要的信息的过程,也就是我们常说的信息查寻(Information Search 或Information Seek)
Hadoop实战教程:一步一步运行WordCont
WordCount是学习Hadoop的经典入门范例。下面通过一步步的操作,来编译、打包、运行WordCount程序。 1、在Hadoop 1.0.4的解压目录的如下位置可以找到WordCount.java的源文件 src/examples/org/apache/hadoop/examples/WordCount.java 2、新建一个dev的文件夹,将WordCount.java拷贝至dev/wo
一步一步学lucene——(第一步:概念篇)
信息检索的概念 信息检索(Information Retrieval)是指信息按一定的方式组织起来,并根据信息用户的需要找出有关的信息的过程和技术。狭义的信息检索就是信息检索过程的后半部分,即从 信息集合中找出所需要的信息的过程,也就是我们常说的信息查寻(Information Search 或Information Seek)。 我们在下边研究的lucene就是对信息做全文检索的一种手段,或者
荐 使用python一步一步搭建微信公众平台(一)
最近无聊,想玩玩微信的公众平台,后来发现乐趣无穷啊~ 使用的工具,python 新浪SAE平台,微信的公众平台 你需要先在微信的公众平台与新浪SAE平台上各种注册,微信平台注册的时候需要你拍张手持身份证的照片,还有几天的审核期 微信公众平台:http://mp.weixin.qq.com 新浪SAE:http://sae.sina.com.cn/ 等待微信公众审核通过后,登录公众平台后,点击高
Hadoop实例WordCount程序一步一步运行
虽说现在用Eclipse下开发 Hadoop程序很方便了,但是命令行方式对于小程序开发验证很方便。这是初学hadoop时的笔记,记录下来以备查。 1. 经典的WordCound程序(WordCount.java),可参见hadoop0.18文档 import java.io.IOException; import java.util.A
使用python一步一步搭建微信公众平台(文章链接)
使用python一步一步搭建微信公众平台 转自:http://my.oschina.net/u/1785471/blog/291326
一步一步教你将普通的wifi路由器变为智能广告路由器
一步一步教你将普通的wifi路由器变为智能广告路由器 相信大家对WiFi智能广告路由器已经不再陌生了,现在很多公共WiFi上网,都需要登录并且验证,这也就是WiFi广告路由器的最重要的功能。大致就是下面的流程: 但是,传统的智能WiFi广告路由器比较比较昂贵,且配置相当复杂,那么,有没有一种简单的办法将你自己的WiFi路由器变成广告路由器呢? 答案当然是肯定的,这也是今天我要教大家的办法。用软件的
一步一步学solr:tomcat7+solr4.9环境搭建
solr下载地址:http://www.apache.org/dyn/closer.cgi/lucene/solr/4.9.0 tomcat下载地址:http://tomcat.apache.org/download-70.cgi 首先将下载好的2个包解压,我在E:/test文件夹 一个空的tomcat 找到你解压好的solr4.9.0目录, 将E:\solr-4.9.0\example\w
使用python一步一步搭建微信公众平台(二)----搭建一个中英互译的翻译工具
距离上次写使用python一步一步搭建微信公众平台(一)已经有几个月了,当中自已也搭建了一个中英文互译的小应用,可是由于英文翻中文好弄,中文翻译成英文一直有问题,知道是编码的问题,但是一直搞不定,于是就先搁浅了,今天又搞了搞,终于搞定了,在这里也说下,同时也说下json格式的应用 1. 准备,先在使用python一步一步搭建微信公众平台(一)中基本实现自动回复的功能后,接着在有道词典上申请一个ke
荐 使用python一步一步搭建微信公众平台(三)----添加用户关注后的欢迎信息与听音乐功能
在使用python一步一步搭建微信公众平台(二)中最后我们留下了一个问题,就是单引号的content在render到xml中时,返回给用户的信息会被转码,如‘<>&’会被转为<>&用户在收到有<>&等内容的时候都会转,非常难看,我查找了一些网页,几乎都是问如果将特殊符号转为转义,而没有说怎么转回来,最后看到说这个转
最新教程
更多java线程状态详解(6种)
java线程类为:java.lang.Thread,其实现java.lang.Runnable接口。 线程在运行过程中有6种状态,分别如下: NEW:初始状态,线程被构建,但是还没有调用start()方法 RUNNABLE:运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行状态” BLOCK:阻塞状态,表示线程阻塞
redis从库只读设置-redis集群管理
默认情况下redis数据库充当slave角色时是只读的不能进行写操作,如果写入,会提示以下错误:READONLY You can't write against a read only slave. 127.0.0.1:6382> set k3 111 (error) READONLY You can't write against a read only slave. 如果你要开启从库
Netty环境配置
netty是一个java事件驱动的网络通信框架,也就是一个jar包,只要在项目里引用即可。
Netty基于流的传输处理
在TCP/IP的基于流的传输中,接收的数据被存储到套接字接收缓冲器中。不幸的是,基于流的传输的缓冲器不是分组的队列,而是字节的队列。 这意味着,即使将两个消息作为两个独立的数据包发送,操作系统也不会将它们视为两个消息,而只是一组字节(有点悲剧)。 因此,不能保证读的是您在远程定入的行数据
Netty入门实例-使用POJO代替ByteBuf
使用TIME协议的客户端和服务器示例,让它们使用POJO来代替原来的ByteBuf。
Netty入门实例-时间服务器
Netty中服务器和客户端之间最大的和唯一的区别是使用了不同的Bootstrap和Channel实现
Netty入门实例-编写服务器端程序
channelRead()处理程序方法实现如下
Netty开发环境配置
最新版本的Netty 4.x和JDK 1.6及更高版本
电商平台数据库设计
电商平台数据库表设计:商品分类表、商品信息表、品牌表、商品属性表、商品属性扩展表、规格表、规格扩展表
HttpClient 上传文件
我们使用MultipartEntityBuilder创建一个HttpEntity。 当创建构建器时,添加一个二进制体 - 包含将要上传的文件以及一个文本正文。 接下来,使用RequestBuilder创建一个HTTP请求,并分配先前创建的HttpEntity。
MongoDB常用命令
查看当前使用的数据库 > db test 切换数据库 > use foobar switched to db foobar 插入文档 > post={"title":"领悟书生","content":"这是一个分享教程的网站","date":new
快速了解MongoDB【基本概念与体系结构】
什么是MongoDB MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
windows系统安装MongoDB
安装 下载MongoDB的安装包:mongodb-win32-x86_64-2008plus-ssl-3.2.10-signed.msi,按照提示步骤安装即可。 安装完成后,软件会安装在C:\Program Files\MongoDB 目录中 我们要启动的服务程序就是C:\Program Files\MongoDB\Server\3.2\bin目录下的mongod.exe,为了方便我们每次启动,我
Spring boot整合MyBatis-Plus 之二:增删改查
基于上一篇springboot整合MyBatis-Plus之后,实现简单的增删改查 创建实体类 添加表注解TableName和主键注解TableId import com.baomidou.mybatisplus.annotations.TableId; import com.baomidou.mybatisplus.annotations.TableName; import com.baom
分布式ID生成器【snowflake雪花算法】
基于snowflake雪花算法分布式ID生成器 snowflake雪花算法分布式ID生成器几大特点: 41bit的时间戳可以支持该算法使用到2082年 10bit的工作机器id可以支持1024台机器 序列号支持1毫秒产生4096个自增序列id 整体上按照时间自增排序 整个分布式系统内不会产生ID碰撞 每秒能够产生26万ID左右 Twitter的 Snowflake分布式ID生成器的JAVA实现方案