java如何实现多个线程并发运行
如果不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么继续提高CPU性能的方法就是超线程CPU模式。那么,作业系统、应用程序要发挥CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式应用程序。
所以,掌握多线程编程模型,不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想。多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O,OEMBIOS等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。
[第一需要弄清的问题]
如同程序和进程的区别,要掌握多线程编程,第一要弄清的问题是:线程对象和线程的区别。
线程对象是可以产生线程的对象。比如在java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。
鉴于作者的水平,无法用更确切的词汇来描述它们的定义。但这两个有本质区别的概念请初学者细细体会,随着介绍的深入和例程分析的增加,就会慢慢明白它们所代表的真实含义。
天下难事必始于易,天下大事必始于细。
让我们先从最简单的"单线程"来入手:(1)带引号说明只是相对而言的单线程,(2)基于java。
class BeginClass{
public static void main(String[] args){
for(int i=0;i<100;i++)
System.out.println("Hello,World!");
}
}
如果我们成功编译了该java文件,然后在命令行上敲入:
java BeginClass
现在发生了什么呢?每一个java程序员,从他开始学习java的第一分钟里都会接触到这个问题,但是,你知道它到底发生发什么?
JVM进程被启动,在同一个JVM进程中,有且只有一个进程,就是它自己。然后在这个JVM环境中,所有程序的运行都是以线程来运行。JVM最先会产生一个主线程,由它来运行指定程序的入口点。在这个程序中,就是主线程从main方法开始运行。当main方法结束后,主线程运行完成。JVM进程也随之退出。
我们看到的是一个主线程在运行main方法,这样的只有一个线程执行程序逻辑的流程我们称
之为单线程。这是JVM提供给我们的单线程环境,事实上,JVM底层还至少有垃圾回收这样的后台线程以及其它非java线程,但这些线程对我们而言不可访问,我们只认为它是单线程的。
主线程是JVM自己启动的,在这里它不是从线程对象产生的。在这个线程中,它运行了main方法这个指令序列。理解它,但它没有更多可以研究的内容。
[接触多线程]
class MyThread extends Thread{
public void run(){
System.out.println("Thread say:Hello,World!");
}
}
public class MoreThreads{
public static void main(String[] args){
new MyThread();
new MyThread().start();
System.out.println("Main say:Hello,World");
}
}
执行这个程序,main方法第一行产生了一个线程对象,但并没有线程启动。
main方法第二行产生了一个线程对象,并启动了一个线程。
main方法第三行,产生并启动一个线程后,主线程自己也继续执行其它语句。
我们先不研究Thread对象的具体内容,稍微来回想一下上面的两个概念,线程对象和线程。在JAVA中,线程对象是JVM产生的一个普通的Object子类。而线程是CPU分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。如果一时想不明白,不要急,但你要记得它们不是一回事就行了。
累了吧?为不么不继续了?
基于这种风格来介绍多线程,并不是每个人都喜欢和接受的,如果你不喜欢,正好不浪费你的时间了,而如果你接受的话,那就看下一节吧。
在进入java平台的线程对象之前,我先插入两个基本概念。
[线程的并发与并行]
在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。
在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储如并发和并行这类术语,但我以我的经验能通俗地告诉大家它是怎么一回事,如果您看到我说的一些"标准"文档上说的不一样,只要意思一致,那您就不要挑刺了。
[JAVA线程对象]
现在我们来开始考察JAVA中线程对象。
在JAVA中,要开始一个线程,有两种方式。一是直接调用Thread实例的start()方法,二是
将Runable实例传给一个Thread实例然后调用它的start()方法。
在前面已经说过,线程对象和线程是两个完全不同的概念。这里我们再次深入一下,生成一个线程的实例,并不代表启动了线程。而启动线程是说在某个线程对象上启动了该实例对应的线程,当该线程结束后,并不会就立即消失。
对于从很多书籍上可以看到的基础知识我就不用多说了。既然是基础知识,我也着重于从普通文档上读不到的内容。所以本节我重点要说的是两种线程对象产生线程方式的区别。
class MyThread extends Thread{
public int x = 0;
public void run(){
for(int i=0;i<100;i++){
try{
Thread.sleep(10);
}catch(Exception e){}
System.out.println(x++);
}
}
}
如果我们生成MyThread的一个实例,然后调用它的start()方法,那么就产生了这个实例对应的线程:
public class Test {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread();
mt.start();
}
}
不用说,最终会打印出0到99,现在我们稍微玩一点花样:
public class Test {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread();
mt.start();
System.out.println(101);
}
}
也不用说,在基础篇(一)中我们知道由于单CPU的原因,一般会先打印101,然后打印0到99。不过我们可以控制线程让它按我们的意思来运行:
public class Test {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread();
mt.start();
mt.join();
System.out.println(101);
}
}
好了,我们终于看到,mt实例对应的线程(假如我有时说mt线程请你不要怪我,不过我尽量不这么说)。在运行完成后,主线程才打印101。因为我们让当前线程(这里是主线程)等待mt线程的运行结束。"在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后才继续运行。" 请大家一定要深刻理解并熟记这句话,而我这里引出这个知识点的目的是为了让你继续看下面的例子:
public class Test {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread();
mt.start();
mt.join();
Thread.sleep(3000);
mt.start();
}
}
当线程对象mt运行完成后,我们让主线程休息一下,然后我们再次在这个线程对象上启动线程。结果我们看到:
Exception in thread "main" java.lang.IllegalThreadStateException
也就是这种线程对象一时运行一次完成后,它就再也不能运行第二次了。我们可以看一下它有具体实现:
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
started = true;
group.add(this);
start0();
}
一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了,这意味着:
[通过Thread实例的start(),一个Thread的实例只能产生一个线程]
那么如果要在一个实例上产生多个线程(也就是我们常说的线程池),我们应该如何做呢?这就是Runnable接口给我们带来的伟大的功能。
class R implements Runnable{
private int x = 0;
public void run(){
for(int i=0;i<100;i++){
try{
Thread.sleep(10);
}catch(Exception e){}
System.out.println(x++);
}
}
}
正如它的名字一样,Runnable的实例是可运行的,但它自己并不能直接运行,它需要被Thread对象来包装才行运行:
public class Test {
public static void main(String[] args) throws Exception{
new Thread(new R()).start();
}
}
当然这个结果和mt.start()没有什么区别。但如果我们把一个Runnable实例给Thread对象多次包装,我们就可以看到它们实际是在同一实例上启动线程:
public class Test {
public static void main(String[] args) throws Exception{
R r = new R();
for(int i=0;i<10;i++)
new Thread(r).start();
}
}
x是实例对象,但结果是x被加到了999,说明这10个线程是在同一个r对象上运行的。请大家注意,因为这个例子是在单CPU上运行的,所以没有对多个线程同时操作共同的对象进行同步。这里是为了说明的方便而简化了同步,而真正的环境中你无法预知程序会在什么环境下运行,所以一定要考虑同步。
到这里我们做一个完整的例子来说明线程产生的方式不同而生成的线程的区别:
package debug;
import java.io.*;
import java.lang.Thread;
class MyThread extends Thread{
public int x = 0;
public void run(){
System.out.println(++x);
}
}
class R implements Runnable{
private int x = 0;
public void run(){
System.out.println(++x);
}
}
public class Test {
public static void main(String[] args) throws Exception{
for(int i=0;i<10;i++){
Thread t = new MyThread();
t.start();
}
Thread.sleep(10000);//让上面的线程运行完成
R r = new R();
for(int i=0;i<10;i++){
Thread t = new Thread(r);
t.start();
}
}
}
上面10个线程对象产生的10个线程运行时打印了10次1。下面10个线程对象产生的10个线程运行时打印了1到10。我们把下面的10个线程称为同一实例(Runnable实例)的多个线程。
java如何实现多个线程并发运行的更多相关文章
- JAVA多线程提高六:java5线程并发库的应用_线程池
前面我们对并发有了一定的认识,并且知道如何创建线程,创建线程主要依靠的是Thread 的类来完成的,那么有什么缺陷呢?如何解决? 一.对比new Threadnew Thread的弊端 a. 每次ne ...
- 使用Java线程并发库实现两个线程交替打印的线程题
背景:是这样的今天在地铁上浏览了以下网页,看到网上一朋友问了一个多线程的问题.晚上闲着没事就决定把它实现出来. 题目: 1.开启两个线程,一个线程打印A-Z,两一个线程打印1-52的数据. 2.实现交 ...
- Java线程并发:知识点
Java线程并发:知识点 发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用. 逃逸:在对象尚未准备 ...
- JAVA与多线程开发(线程基础、继承Thread类来定义自己的线程、实现Runnable接口来解决单继承局限性、控制多线程程并发)
实现线程并发有两种方式:1)继承Thread类:2)实现Runnable接口. 线程基础 1)程序.进程.线程:并行.并发. 2)线程生命周期:创建状态(new一个线程对象).就绪状态(调用该对象的s ...
- java面试总躲不过的并发(一): 线程池ThreadPoolExecutor基础梳理
本文核心:线程池ThreadPoolExecutor基础梳理 一.实现多线程的方式 1.继承Thread类,重写其run方法 2.实现Runnable接口,实现run方法 3.实现Callable接口 ...
- Java多线程(五) —— 线程并发库之锁机制
参考文献: http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一.Lock与ReentrantLock 前面的章节主要谈谈原子操作 ...
- Java多线程(四) —— 线程并发库之Atomic
一.从原子操作开始 从相对简单的Atomic入手(java.util.concurrent是基于Queue的并发包,而Queue,很多情况下使用到了Atomic操作,因此首先从这里开始). 很多情况下 ...
- 线程并发线程安全介绍及java.util.concurrent包下类介绍
线程Thread,在Java开发中多线程是必不可少的,但是真正能用好的并不多! 首先开启一个线程三种方式 ①new Thread(Runnable).start() ②thread.start(); ...
- java线程并发工具类CyclicBarrier、CountDownLatch及Semaphore
一.CyclicBarrier (原文链接:http://www.studyshare.cn/blog-front/blog/index ) 1.定义 CyclicBarrier是线程并发工具类之 ...
随机推荐
- (10)ubuntu内核源码树
ubuntu内核源码树目录: root@ubuntu:/lib/modules/3.13.0-32-generic/build#
- c++ 堆、栈、自由存储区、全局/静态存储区和常量存储区
在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区. 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区.里面的变量通常是局部变量.函数参数 ...
- 牛客网 暑期ACM多校训练营(第一场)J.Different Integers-区间两侧不同数字的个数-离线树状数组 or 可持久化线段树(主席树)
J.Different Integers 题意就是给你l,r,问你在区间两侧的[1,l]和[r,n]中,不同数的个数. 两种思路: 1.将数组长度扩大两倍,for(int i=n+1;i<=2* ...
- 牛客练习赛16 F 选值【二分/计数】
链接:https://www.nowcoder.com/acm/contest/84/F 来源:牛客网 题目描述 给定n个数,从中选出三个数,使得最大的那个减最小的那个的值小于等于d,问有多少种选法. ...
- Python的扩展接口[2] -> 动态链接库DLL[0] -> 动态链接库及辅助工具
动态链接库 / Dynamic Link Library 目录 动态链接库简介 函数封装DLL 组件对象模型COM 如何判断.dll文件是COM还是DLL 辅助工具 1 动态链接库简介 / DLL I ...
- hdu6035(树形DP)
hdu6035 题意 给出一棵树,现在定义两点之间距离为两点间最短路径上颜色集合的大小.问任意两点间距离之和. 分析 换个方向,题目其实等价于求每种颜色在多少条路径上出现过(每种颜色对于答案的贡献), ...
- 洛谷 P4212 外太空旅行
题目描述 在人类的触角伸向银河系的边缘之际,普通人上太空旅行已经变得稀松平常了.某理科试验班有n个人,现在班主任要从中选出尽量多的人去参加一次太空旅行活动. 可是n名同学并不是和平相处的.有的人,比如 ...
- SPCOMM控件对串口参数的设置
对于串口来说,一般大家都了解波特率,校验码,数据位之类的参数.然而在实际的数据传输中,有些参数也会影响数据的传输.现总结如下,以便大家查询.在对串口进行编程时,可用portman对串口参数进行跟踪,提 ...
- 理解 VMWare的3种网络模型 z
在说到VMware的网络模型之前,先说一下VMware的几个虚拟设备: ■ VMnet0:这是VMware用于虚拟桥接网络下的虚拟交换机: ■ VMnet1:这是VMware用于虚拟Host-Only ...
- [置顶]
cAdvisor、InfluxDB、Grafana搭建Docker1.12性能监控平台
通过cadvisor+influxdb+grafana三者有机结合,打造跨主机容器监控. 优点 1.跨主机监控,可扩展 2.容器自发现 3.历史数据长期保存 4.自定义配置程度高 缺点 1.不能自动隐 ...