Java多线程程序设计初步入门

Java多线程程序设计初步入门,第1张

在Java语言产生前 传统的程序设计语言的程序同一时刻只能单任务 *** 作 效率非常低 例如程序往往在接收数据输入时发生阻塞 只有等到程序获得数据后才能继续运行 随着Internet的迅猛发展 这种状况越来越不能让人们忍受 如果网络接收数据阻塞 后台程序就处于等待状态而不继续任何 *** 作 而这种阻塞是经常会碰到的 此时CPU资源被白白的闲置起来 如果在后台程序中能够同时处理多个任务 该多好啊!应Internet技术而生的Java语言解决了这个问题 多线程程序是Java语言的一个很重要的特点 在一个Java程序中 我们可以同时并行运行多个相对独立的线程 例如 我们如果创建一个线程来进行数据输入输出 而创建另一个线程在后台进行其它的数据处理 如果输入输出线程在接收数据时阻塞 而处理数据的线程仍然在运行 多线程程序设计大大提高了程序执行效率和处理能力

线程的创建

我们知道Java是面向对象的程序语言 用Java进行程序设计就是设计和使用类 Java为我们提供了线程类Thread来创建线程 创建线程与创建普通的类的对象的 *** 作是一样的 而线程就是Thread类或其子类的实例对象 下面是一个创建启动一个线程的语句

Thread thread =new Thread(); file://声明一个对象实例 即创建一个线程

Thread run(); file://用Thread类中的run()方法启动线程

从这个例子 我们可以通过Thread()构造方法创建一个线程 并启动该线程 事实上 启动线程 也就是启动线程的run()方法 而Thread类中的run()方法没有任何 *** 作语句 所以这个线程没有任何 *** 作 要使线程实现预定功能 必须定义自己的run()方法 Java中通常有两种方式定义run()方法

通过定义一个Thread类的子类 在该子类中重写run()方法 Thread子类的实例对象就是一个线程 显然 该线程有我们自己设计的线程体run()方法 启动线程就启动了子类中重写的run()方法

通过Runnable接口 在该接口中定义run()方法的接口 所谓接口跟类非常类似 主要用来实现特殊功能 如复杂关系的多重继承功能 在此 我们定义一个实现Runnable() 接口的类 在该类中定义自己的run()方法 然后以该类的实例对象为参数调用Thread类的构造方法来创建一个线程

线程被实际创建后处于待命状态 激活(启动)线程就是启动线程的run()方法 这是通过调用线程的start()方法来实现的

下面一个例子实践了如何通过上述两种方法创建线程并启动它们

// 通过Thread类的子类创建的线程 class thread extends Thread { file://自定义线程的run()方法 public void run() { System out println( Thread is running… ); } } file://通过Runnable接口创建的另外一个线程 class thread implements Runnable { file://自定义线程的run()方法 public void run() { System out println( Thread is running… ); } } file://程序的主类 class Multi_Thread file://声明主类 { plubic static void mail(String args[]) file://声明主方法 { thread threadone=new thread (); file://用Thread类的子类创建线程 Thread threado=new Thread(new thread ()); file://用Runnable接口类的对象创建线程 threadone start(); threado start(); file://strat()方法启动线程 } }

运行该程序就可以看出 线程threadone和threado交替占用CPU 处于并行运行状态 可以看出 启动线程的run()方法是通过调用线程的start()方法来实现的(见上例中主类) 调用start()方法启动线程的run()方法不同于一般的调用方法 调用一般方法时 必须等到一般方法执行完毕才能够返回start()方法 而启动线程的run()方法后 start()告诉系统该线程准备就绪可以启动run()方法后 就返回start()方法执行调用start()方法语句下面的语句 这时run()方法可能还在运行 这样 线程的启动和运行并行进行 实现了多任务 *** 作

线程的优先级

对于多线程程序 每个线程的重要程度是不尽相同 如多个线程在等待获得CPU时间时 往往我们需要优先级高的线程优先抢占到CPU时间得以执行 又如多个线程交替执行时 优先级决定了级别高的线程得到CPU的次数多一些且时间多长一些 这样 高优先级的线程处理的任务效率就高一些

Java中线程的优先级从低到高以整数 ~ 表示 共分为 级 设置优先级是通过调用线程对象的setPriority()方法 如上例中 设置优先级的语句为

thread threadone=new thread (); file://用Thread类的子类创建线程

Thread threado=new Thread(new thread ()); file://用Runnable接口类的对象创建线程

threadone setPriority( ); file://设置threadone的优先级

threado setPriority( ); file://设置threado的优先级

threadone start(); threado start(); file://strat()方法启动线程

这样 线程threadone将会优先于线程threado执行 并将占有更多的CPU时间 该例中 优先级设置放在线程启动前 也可以在启动后进行设置 以满足不同的优先级需求

线程的(同步)控制

一个Java程序的多线程之间可以共享数据 当线程以异步方式访问共享数据时 有时候是不安全的或者不和逻辑的 比如 同一时刻一个线程在读取数据 另外一个线程在处理数据 当处理数据的线程没有等到读取数据的线程读取完毕就去处理数据 必然得到错误的处理结果 这和我们前面提到的读取数据和处理数据并行多任务并不矛盾 这儿指的是处理数据的线程不能处理当前还没有读取结束的数据 但是可以处理其它的数据

如果我们采用多线程同步控制机制 等到第一个线程读取完数据 第二个线程才能处理该数据 就会避免错误 可见 线程同步是多线程编程的一个相当重要的技术

在讲线程的同步控制前我们需要交代如下概念

用Java关键字synchonized同步对共享数据 *** 作的方法

在一个对象中 用synchonized声明的方法为同步方法 Java中有一个同步模型 监视器 负责管理线程对对象中的同步方法的访问 它的原理是 赋予该对象唯一一把 钥匙 当多个线程进入对象 只有取得该对象钥匙的线程才可以访问同步方法 其它线程在该对象中等待 直到该线程用wait()方法放弃这把钥匙 其它等待的线程抢占该钥匙 抢占到钥匙的线程后才可得以执行 而没有取得钥匙的线程仍被阻塞在该对象中等待

file://声明同步的一种方式 将方法声明同步

class store  {public synchonized void store_in(){… }public synchonized void store_out(){  … }}

  利用wait() notify()及notifyAll()方法发送消息实现线程间的相互联系

Java程序中多个线程通过消息来实现互动联系的 这几种方法实现了线程间的消息发送 例如定义一个对象的synchonized 方法 同一时刻只能够有一个线程访问该对象中的同步方法 其它线程被阻塞 通常可以用notify()或notifyAll()方法唤醒其它一个或所有线程 而使用wait()方法来使该线程处于阻塞状态 等待其它的线程用notify()唤醒

一个实际的例子就是生产和销售 生产单元将产品生产出来放在仓库中 销售单元则从仓库中提走产品 在这个过程中 销售单元必须在仓库中有产品时才能提货 如果仓库中没有产品 则销售单元必须等待

程序中 假如我们定义一个仓库类store 该类的实例对象就相当于仓库 在store类中定义两个成员方法 store_in() 用来模拟产品制造者往仓库中添加产品 strore_out()方法则用来模拟销售者从仓库中取走产品 然后定义两个线程类 customer类 其中的run()方法通过调用仓库类中的store_out()从仓库中取走产品 模拟销售者 另外一个线程类producer中的run()方法通过调用仓库类中的store_in()方法向仓库添加产品 模拟产品制造者 在主类中创建并启动线程 实现向仓库中添加产品或取走产品

如果仓库类中的store_in() 和store_out()方法不声明同步 这就是个一般的多线程 我们知道 一个程序中的多线程是交替执行的 运行也是无序的 这样 就可能存在这样的问题

仓库中没有产品了 销售者还在不断光顾 而且还不停的在 取 产品 这在现实中是不可思义的 在程序中就表现为负值 如果将仓库类中的stroe_in()和store_out()方法声明同步 如上例所示 就控制了同一时刻只能有一个线程访问仓库对象中的同步方法 即一个生产类线程访问被声明为同步的store_in()方法时 其它线程将不能够访问对象中的store_out()同步方法 当然也不能访问store_in()方法 必须等到该线程调用wait()方法放弃钥匙 其它线程才有机会访问同步方法

lishixinzhi/Article/program/Java/gj/201311/27301

引言

随着双核 四核等多核处理器的推广 多核处理器或超线程单核处理器的计算机已很常见 基于多核处理的编程技术也开始受到程序员们普遍关注 这其中一个重要的方面就是构建多线程应用程序(因为不使用多线程的话 开发人员就不能充分发挥多核计算机的强大性能)

本文针对的是构建基于单核计算机的多线程应用程序 目的在于介绍多线程相关的基本概念 内涵 以及如何通过System Threading命名空间的类 委托和BackgroundWorker组件等三种手段构建多线程应用程序

本文如果能为刚接触多线程的朋友起到抛砖引玉的作用也就心满意足了 当然 本人才疏学浅 文中难免会有不足或错误的地方 恳请各位朋友多多指点

理解多线程

我们通常理解的应用程序就是一个 exe文件 当运行 exe应用程序以后 系统会在内存中为该程序分配一定的空间 同时加载一些该程序所需的资源 其实这就可以称为创建了一个进程 可以通过Windows任务管理器查看这个进程的相关信息 如映像名称 用户名 内存使用 PID(唯一的进程标示)等 如图下所示

而线程则只是进程中的一个基本执行单元 一个应用程序往往只有一个程序入口 如

[STAThread]

static void Main()   //应用程序主入口点

{

Application EnableVisualStyles();

Application SetCompatibleTextRenderingDefault(false);

Application Run(new MainForm());

}

进程会包含一个进入此入口的线程 我们称之为主线程 其中 特性 [STAThread] 指示应用程序的默认线程模型是单线程单元(相关信息可参考 us/library/system stathreadattribute(VS ) aspx) 只包含一个主线程的进程是线程安全的 相当于程序仅有一条工作线 只有完成了前面的任务才能执行排在后面的任务

然当在程序处理一个很耗时的任务 如输出一个大的文件或远程访问数据库等 此时的窗体界面程序对用户而言基本像是没反应一样 菜单 按钮等都用不了 因为窗体上控件的响应事件也是需要主线程来执行的 而主线程正忙着干其他的事 控件响应事件就只能排队等著主线程忙完了再执行

为了克服单线程的这个缺陷 Win API可以让主线程再创建其他的次线程 但不论是主线程还是次线程都是进程中独立的执行单元 可以同时访问共享的数据 这样就有了多线程这个概念

相信到这 应该对多线程有个比较感性的认识了 但笔者在这要提醒一下 基于单核计算机的多线程其实只是 *** 作系统施展的一个障眼法而已(但这不会干扰我们理解构建多线程应用程序的思路) 他并不能缩短完成所有任务的时间 有时反而还会因为使用过多的线程而降低性能 延长时间 之所以这样 是因为对于单CPU而言 在一个单位时间(也称时间片)内 只能执行一个线程 即只能干一件事 当一个线程的时间片用完时 系统会将该线程挂起 下一个时间内再执行另一个线程 如此 CPU以时间片为间隔在多个线程之间交替执行运算(其实这里还与每个线程的优先级有关 级别高的会优先处理) 由于交替时间间隔很短 所以造成了各个线程都在 同时 工作的假象 而如果线程数目过多 由于系统挂起线程时要记录线程当前的状态数据等 这样又势必会降低程序的整体性能 但对于这些 多核计算机就能从本质上(真正的同时工作)提高程序的执行效率

线程异步与线程同步

从线程执行任务的方式上可以分为线程同步和线程异步 而为了方便理解 后面描述中用 同步线程 指代与线程同步相关的线程 同样 用 异步线程 表示与线程异步相关的线程

线程异步就是解决类似前面提到的执行耗时任务时界面控件不能使用的问题 如创建一个次线程去专门执行耗时的任务 而其他如界面控件响应这样的任务交给另一个线程执行(往往由主线程执行) 这样 两个线程之间通过线程调度器短时间(时间片)内的切换 就模拟出多个任务 同时 被执行的效果

线程异步往往是通过创建多个线程执行多个任务 多个工作线同时开工 类似多辆在宽广的公路上并行的汽车同时前进 互不干扰(读者要明白 本质上并没有 同时 仅仅是 *** 作系统玩的一个障眼法 但这个障眼法却对提高我们的程序与用户之间的交互 以及提高程序的友好性很有用 不是吗)

在介绍线程同步之前 先介绍一个与此紧密相关的概念——并发问题

前面提到 线程都是独立的执行单元 可以访问共享的数据 也就是说 在一个拥有多个次线程的程序中 每个线程都可以访问同一个共享的数据 再稍加思考你会发现这样可能会出问题 由于线程调度器会随机的挂起某一个线程(前面介绍的线程间的切换) 所以当线程a对共享数据D的访问(修改 删除等 *** 作)完成之前被挂起 而此时线程b又恰好去访问数据D 那么线程b访问的则是一个不稳定的数据 这样就会产生非常难以发现bug 由于是随机发生的 产生的结果是不可预测的 这样样的bug也都很难重现和调试 这就是并发问题

为了解决多线程共同访问一个共享资源(也称互斥访问)时产生的并发问题 线程同步就应运而生了 线程同步的机理 简单的说 就是防止多个线程同时访问某个共享的资源 做法很简单 标记访问某共享资源的那部分代码 当程序运行到有标记的地方时 CLR(具体是什么可以先不管 只要知道它能控制就行)对各线程进行调整 如果已有线程在访问一资源 CLR就会将其他访问这一资源的线程挂起 直到前一线程结束对该资源的访问 这样就保证了同一时间只有一个线程访问该资源 打个比方 就如某资源放在只有一独木桥相连的孤岛上 如果要使用该资源 大家就得排队 一个一个来 前面的回来了 下一个再去 前面的没回来 后面的就原地待命

这里只是把基本的概念及原理做了一个简单的阐述 不至于看后面的程序时糊里糊涂的 具体如何编写代码 下面的段落将做详细介绍

创建多线程应用程序

这里做一个简单的说明 下面主要通过介绍通过System Threading命名空间的类 委托和BackgroundWorker组件三种不同的手段构建多线程应用程序 具体会从线程异步和线程同步两个方面来阐述

通过System Threading命名空间的类构建

在 NET平台下 System Threading命名空间提供了许多类型来构建多线程应用程序 可以说是专为多线程服务的 由于本文仅是想起到一个 抛砖引玉 的作用 所以对于这一块不会探讨过多 过深 主要使用System Threading Thread类

先从System Threading Thread类本身相关的一个小例子说起 代码如下 解释见注释

using System;

using System Threading; //引入System Threading命名空间

namespace MultiThread

{

class Class

{

static void Main(string[] args)

{

Console WriteLine( 显示当前线程的相关信息 );

//声明线程变量并赋值为当前线程

Thread primaryThread = Thread CurrentThread;

//赋值线程的名称

primaryThread Name = 主线程 ;

//显示线程的相关信息

Console WriteLine( 线程的名字 { } primaryThread Name);

Console WriteLine( 线程是否启动? { } primaryThread IsAlive);

Console WriteLine( 线程的优先级 { } primaryThread Priority);

Console WriteLine( 线程的状态 { } primaryThread ThreadState);

Console ReadLine();

}

}

}

输出结果如下

显示当前线程的相关信息

线程的名字 主线程

线程是否启动? True

线程的优先级 Normal

线程的状态 Running

对于上面的代码不想做过多解释 只说一下Thread CurrentThread得到的是执行当前代码的线程

异步调用线程

这里先说一下前台线程与后台线程 前台线程能阻止应用程序的终止 既直到所有前台线程终止后才会彻底关闭应用程序 而对后台线程而言 当所有前台线程终止时 后台线程会被自动终止 不论后台线程是否正在执行任务 默认情况下通过Thread Start()方法创建的线程都自动为前台线程 把线程的属性IsBackground设为true时就将线程转为后台线程

下面先看一个例子 该例子创建一个次线程执行打印数字的任务 而主线程则干其他的事 两者同时进行 互不干扰

using System;

using System Threading;

using System Windows Forms;

namespace MultiThread

{

class Class

{

static void Main(string[] args)

{

Console WriteLine( 两个线程同时工作 );

//主线程 因为获得的是当前在执行Main()的线程

Thread primaryThread = Thread CurrentThread;

primaryThread Name = 主线程 ;

Console WriteLine( > { } 在执行主函数 Main() Thread CurrentThread Name);

//次线程 该线程指向PrintNumbers()方法

Thread SecondThread = new Thread(new ThreadStart(PrintNumbers));

SecondThread Name = 次线程 ;

//次线程开始执行指向的方法

SecondThread Start();

//同时主线程在执行主函数中的其他任务

MessageBox Show( 正在执行主函数中的任务 主线程在工作 );

Console ReadLine();

}

//打印数字的方法

static void PrintNumbers()

{

Console WriteLine( > { } 在执行打印数字函数 PrintNumber() Thread CurrentThread Name);

Console WriteLine( 打印数字 );

for (int i = ; i < ; i++)

{

Console Write( { } i);

//Sleep()方法使当前线程挂等待指定的时长在执行 这里主要是模仿打印任务

Thread Sleep( );

}

Console WriteLine();

}

}

}

程序运行后会看到一个窗口d出 如图所示 同时控制台窗口也在不断的显示数字

输出结果为

两个线程同时工作

> 主线程 在执行主函数 Main()

> 次线程 在执行打印数字函数 PrintNumber()

打印数字

这里稍微对 Thread SecondThread = new Thread(new ThreadStart(PrintNumbers)); 这一句做个解释 其实 ThreadStart 是 System Threading 命名空间下的一个委托 其声明是 public delegate void ThreadStart() 指向不带参数 返回值为空的方法 所以当使用 ThreadStart 时 对应的线程就只能调用不带参数 返回值为空的方法 那非要指向含参数的方法呢?在System Threading命名空间下还有一个ParameterizedThreadStart 委托 其声明是 public delegate void ParameterizedThreadStart(object obj) 可以指向含 object 类型参数的方法 这里不要忘了 object 可是所有类型的父类哦 有了它就可以通过创建各种自定义类型 如结构 类等传递很多参数了 这里就不再举例说明了

并发问题

这里再通过一个例子让大家切实体会一下前面说到的并发问题 然后再介绍线程同步

using System;

using System Threading;

namespace MultiThread

{

class Class

{

static void Main(string[] args)

{

Console WriteLine( 并发问题演示 );

//创建一个打印对象实例

Printer printer = new Printer();

//声明一含 个线程对象的数组

Thread[] threads = new Thread[ ];

for (int i = ; i < ; i++)

{

//将每一个线程都指向printer的PrintNumbers()方法

threads[i] = new Thread(new ThreadStart(printer PrintNumbers));

//给每一个线程编号

threads[i] Name = i ToString() + 号线程 ;

}

//开始执行所有线程

foreach (Thread t in threads)

t Start();

Console ReadLine();

}

}

//打印类

public class Printer

{

//打印数字的方法

public void PrintNumbers()

{

Console WriteLine( > { } 正在执行打印任务 开始打印数字 Thread CurrentThread Name);

for (int i = ; i < ; i++)

{

Random r = new Random();

//为了增加冲突的几率及 使各线程各自等待随机的时长

Thread Sleep( r Next( ));

//打印数字

Console Write( { } i);

}

Console WriteLine();

}

}

}

上面的例子中 主线程产生的 个线程同时访问同一个对象实例printer的方法PrintNumbers() 由于没有锁定共享资源(注意 这里是指控制台) 所以在PrintNumbers()输出到控制台之前 调用PrintNumbers()的线程很可能被挂起 但不知道什么时候(或是否有)挂起 导致得到不可预测的结果 如下是两个不同的结果(当然 读者的运行结果可能会是其他情形)

情形一

情形二

线程同步

线程同步的访问方式也称为阻塞调用 即没有执行完任务不返回 线程被挂起 可以使用C#中的lock关键字 在此关键字范围类的代码都将是线程安全的 lock关键字需定义一个标记 线程进入锁定范围是必须获得这个标记 当锁定的是一个实例级对象的私有方法时使用方法本身所在对象的引用就可以了 将上面例子中的打印类Printer稍做改动 添加lock关键字 代码如下

//打印类

public class Printer

{

public void PrintNumbers()

{

//使用lock关键字 锁定d的代码是线程安全的

lock (this)

{

Console WriteLine( > { } 正在执行打印任务 开始打印数字 Thread CurrentThread Name);

for (int i = ; i < ; i++)

{

Random r = new Random();

//为了增加冲突的几率及 使各线程各自等待随机的时长

Thread Sleep( r Next( ));

//打印数字

Console Write( { } i);

}

Console WriteLine();

}

}

}

}

同步后执行结果如下

也可以使用System Threading命名空间下的Monitor类进行同步 两者内涵是一样的 但Monitor类更灵活 这里就不在做过多的探讨 代码如下

//打印类

public class Printer

{

public void PrintNumbers()

{

Monitor Enter(this);

try

{

Console WriteLine( > { } 正在执行打印任务 开始打印数字 Thread CurrentThread Name);

for (int i = ; i < ; i++)

{

Random r = new Random();

//为了增加冲突的几率及 使各线程各自等待随机的时长

Thread Sleep( r Next( ));

//打印数字

Console Write( { } i);

}

Console WriteLine();

}

finally

{

Monitor Exit(this);

}

}

}

输出结果与上面的一样

通过委托构建多线程应用程序

在看下面的内容时要求对委托有一定的了解 如果不清楚的话推荐参考一下博客园张子阳的《C# 中的委托和事件》 里面对委托与事件进行由浅入深的较系统的讲解

这里先举一个关于委托的简单例子 具体解说见注释

using System;

namespace MultiThread

{

//定义一个指向包含两个int型参数 返回值为int型的函数的委托

public delegate int AddOp(int x int y);

class Program

{

static void Main(string[] args)

{

//创建一个指向Add()方法的AddOp对象p

AddOp pAddOp = new AddOp(Add);

//使用委托间接调用方法Add()

Console WriteLine( + = { } pAddOp( ));

Console ReadLine();

}

//求和的函数

static int Add(int x int y)

{

int sum = x + y;

return sum;

}

}

}

运行结果为

+ =

线程异步

先说明一下 这里不打算讲解委托线程异步或同步的参数传递 获取返回值等 只是做个一般性的开头而已 如果后面有时间了再另外写一篇关于多线程中参数传递 获取返回值的文章

注意观察上面的例子会发现 直接使用委托实例 pAddOp( ) 就调用了求和方法 Add() 很明显 这个方法是由主线程执行的 然而 委托类型中还有另外两个方法——BeginInvoke()和EndInvoke() 下面通过具体的例子来说明 将上面的例子做适当改动 如下

using System;

using System Threading;

using System Runtime Remoting Messaging;

namespace MultiThread

{

//声明指向含两个int型参数 返回值为int型的函数的委托

public delegate int AddOp(int x int y);

class Program

{

static void Main(string[] args)

{

Console WriteLine( 委托异步线程 两个线程 同时 工作 );

//显示主线程的唯一标示

Console WriteLine( 调用Main()的主线程的线程ID是 { } Thread CurrentThread ManagedThreadId);

//将委托实例指向Add()方法

AddOp pAddOp = new AddOp(Add);

//开始委托次线程调用 委托BeginInvoke()方法返回的类型是IAsyncResult

//包含这委托指向方法结束返回的值 同时也是EndInvoke()方法参数

IAsyncResult iftAR = pAddOp BeginInvoke( null null);

Console WriteLine( nMain()方法中执行其他任务 n );

int sum = pAddOp EndInvoke(iftAR);

Console WriteLine( + = { } sum);

Console ReadLine();

}

//求和方法

static int Add(int x int y)

{

//指示调用该方法的线程ID ManagedThreadId是线程的唯一标示

Console WriteLine( 调用求和方法 Add()的线程ID是 { } Thread CurrentThread ManagedThreadId);

//模拟一个过程 停留 秒

Thread Sleep( );

int sum = x + y;

return sum;

}

}

}

运行结果如下

委托异步线程 两个线程 同时 工作

调用Main()的主线程的线程ID是

Main()方法中执行其他任务

调用求和方法 Add()的线程ID是

+ =

线程同步

委托中的线程同步主要涉及到上面使用的pAddOp BeginInvoke( null null)方法中后面两个为null的参数 具体的可以参考相关资料 这里代码如下 解释见代码注释

using System;

using System Threading;

using System Runtime Remoting Messaging;

namespace MultiThread

{

//声明指向含两个int型参数 返回值为int型的函数的委托

public delegate int AddOp(int x int y);

class Program

{

static void Main(string[] args)

{

Console WriteLine( 线程同步 阻塞 调用 两个线程工作 );

Console WriteLine( Main() invokee on thread { } Thread CurrentThread ManagedThreadId);

//将委托实例指向Add()方法

AddOp pAddOp = new AddOp(Add);

IAsyncResult iftAR = pAddOp BeginInvoke( null null);

//判断委托线程是否执行完任务

//没有完成的话 主线程就做其他的事

while (!iftAR IsCompleted)

{

Console WriteLine( Main()方法工作中 );

Thread Sleep( );

}

//获得返回值

int answer = pAddOp EndInvoke(iftAR);

Console WriteLine( + = { } answer);

Console ReadLine();

}

//求和方法

static int Add(int x int y)

{

//指示调用该方法的线程ID ManagedThreadId是线程的唯一标示

Console WriteLine( 调用求和方法 Add()的线程ID是 { } Thread CurrentThread ManagedThreadId);

//模拟一个过程 停留 秒

Thread Sleep( );

int sum = x + y;

return sum;

}

}

}

运行结果如下

线程同步 阻塞 调用 两个线程工作

Main() invokee on thread

Main()方法工作中

调用求和方法 Add()的线程ID是

Main()方法工作中

Main()方法工作中

Main()方法工作中

Main()方法工作中

+ =

BackgroundWorker组件

BackgroundWorker组件位于工具箱中 用于方便的创建线程异步的程序 新建一个WindowsForms应用程序 界面如下

代码如下 解释参见注释

private void button _Click(object sender EventArgs e)

{

try

{

//获得输入的数字

int numOne = int Parse(this textBox Text);

int numTwo = int Parse(this textBox Text);

//实例化参数类

AddParams args = new AddParams(numOne numTwo);

//调用RunWorkerAsync()生成后台线程 同时传入参数

this backgroundWorker RunWorkerAsync(args);

}

catch (Exception ex)

{

MessageBox Show(ex Message);

}

}

//backgroundWorker新生成的线程开始工作

private void backgroundWorker _DoWork(object sender DoWorkEventArgs e)

{

//获取传入的AddParams对象

AddParams args = (AddParams)e Argument;

//停留 秒 模拟耗时任务

Thread Sleep( );

//返回值

e Result = args a + args b;

}

//当backgroundWorker 的DoWork中的代码执行完后会触发该事件

//同时 其执行的结果会包含在RunWorkerCompletedEventArgs参数中

private void backgroundWorker _RunWorkerCompleted(object sender RunWorkerCompletedEventArgs e)

{

//显示运算结果

MessageBox Show( 运行结果为 + e Result ToString() 结果 );

}

}

//参数类 这个类仅仅起到一个记录并传递参数的作用

class AddParams

{

public int a b;

public AddParams(int numb int numb )

{

a = numb ;

b = numb ;

}

}

注意 在计算结果的同时 窗体可以随意移动 也可以重新在文本框中输入信息 这就说明主线程与backgroundWorker组件生成的线程是异步的

总结

lishixinzhi/Article/program/net/201311/11400

线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,

即不同的线程可以执行同样的函数。

什么是多线程?

多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,

也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程的好处:

可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,

这样就大大提高了程序的效率。

多线程与单线程的区别

生活举例

你早上上班,正要打卡的时候,手机响了。你如果先接了电话,等接完了,在打卡,就是单线程。

如果你一手接电话,一手打卡。就是多线程。

2件事的结果是一样的。你接了电话且打了卡。

thread_creationc

gcc thread_creationc

会在当前目录下,生成可执行的aout文件

/aout

这个应该不难吧,对已经释放的地址double free程序都会出问题的,下面写了个简单例子你看下:

#include <iostream>

#include <Windowsh>

const static int ThreadCount = 10;

LPVOID param = "hello";

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

Sleep(100);

delete lpParam;

return 0;

}

int main()

{

HANDLE hThread[ThreadCount];

for (int i = 0; i < ThreadCount; i++)

{

hThread[i] = CreateThread(NULL, 0, ThreadProc, param, 0, 0);

}

WaitForMultipleObjects(ThreadCount, hThread, true, INFINITE);

for (int i = 0; i < ThreadCount; i++)

{

CloseHandle(hThread[i]);

}

return 0;

}

线程就是一个程序里面不同的执行路径,你的程序开始执行之后,必须有一条路径,开始做什么,中间做什么,最后做什么,然后程序执行结束,这条路线就是一个线程。

多线程,就是多个线程,不是一个,多线程机制指的是一个程序里面可以有多个路径同时执行,也就是有多个线程,并行的执行,可以在同一个时间点上,一起执行,每一个线程都是一个独立的,都有各自独立的执行空间。

但是需要注意一点,只有你的计算机上有多个处理器(CPU),那么才是真正的多线程,如果只有一个CPU那么其实是执行你这个线程一会儿,执行他这个线程一会儿,是交替的执行,只不过CPU很快,给我们程序员看到的效果就好像是同一个时间一起执行多个线程一样,其实是轮流着执行的,因为就一个CPU,就一个大脑,能左手画方,右手画圆么?不能,一个大脑只能同一个时间点上运算和控制一件事,有人做到了,说d钢琴的容易做到,其实他们那是熟练了,手成了无意识的机械行为,不是真的就同一时间做两件事。

不过要是多个CPU,多个大脑,那确实是多线程。

在我看来你非要用java了,因为我也只了解java,不了解别的,做个什么小游戏好,那只要把多线程用上了就行,不过用java做,你会GUI么?否则只能命令行了。比如你做一个射击游戏,一个线程主线程是你的q,其他多个分支的独立的线程,可以是鸟什么的,它们飞他们的,你打你的,其实太多了,这个自己想把太多了

作者 natrium 一 理解多线程多线程是这样一种机制 它允许在程序中并发执行多个指令流 每个指令流都称为一个线程 彼此间互相独立 线程又称为轻量级进程 它和进程一样拥有独立的执行控制 由 *** 作系统负责调度 区别在于线程没有独立的存储空间 而是和所属进程中的其它线程共享一个存储空间 这使得线程间的通信远较进程简单 多个线程的执行是并发的 也就是在逻辑上 同时 而不管是否是物理上的 同时 如果系统只有一个CPU 那么真正的 同时 是不可能的 但是由于CPU的速度非常快 用户感觉不到其中的区别 因此我们也不用关心它 只需要设想各个线程是同时执行即可 多线程和传统的单线程在程序设计上最大的区别在于 由于各个线程的控制流彼此独立 使得各个线程之间的代码是乱序执行的 由此带来的线程调度 同步等问题 将在以后探讨 二 在Java中实现多线程我们不妨设想 为了创建一个新的线程 我们需要做些什么?很显然 我们必须指明这个线程所要执行的代码 而这就是在Java中实现多线程我们所需要做的一切!真是神奇!Java是如何做到这一点的?通过类!作为一个完全面向对象的语言 Java提供了类 java lang Thread 来方便多线程编程 这个类提供了大量的方法来方便我们控制自己的各个线程 我们以后的讨论都将围绕这个类进行 那么如何提供给 Java 我们要线程执行的代码呢?让我们来看一看 Thread 类 Thread 类最重要的方法是 run() 它为Thread 类的方法 start() 所调用 提供我们的线程所要执行的代码 为了指定我们自己的代码 只需要覆盖它!方法一 继承 Thread 类 覆盖方法 run() 我们在创建的 Thread 类的子类中重写 run() 加入线程所要执行的代码即可 下面是一个例子 public class MyThread extends Thread {int count= number;public MyThread(int num) {number = num;System out println( 创建线程 + number);}public void run() {while(true) {System out println( 线程 + number + :计数 + count);if(++count== ) return;}}public static void main(String args[]) {for(int i = ; i < 5; i++) new MyThread(i+1)start();}}这种方法简单明了,符合大家的习惯,但是,它也有一个很大的缺点,那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类,这时如果我们又不想建立一个新的类,应该怎么办呢?我们不妨来探索一种新的方法:我们不创建 Thread 类的子类,而是直接使用它,那么我们只能将我们的方法作为参数传递给 Thread 类的实例,有点类似回调函数。WINgWIT但是 Java 没有指针,我们只能传递一个包含这个方法的类的实例。那么如何限制这个类必须包含这一方法呢?当然是使用接口!(虽然抽象类也可满足,但是需要继承,而我们之所以要采用这种新方法,不就是为了避免继承带来的限制吗?)Java 提供了接口 javalangRunnable 来支持这种方法。方法二:实现 Runnable 接口Runnable 接口只有一个方法 run(),我们声明自己的类实现 Runnable 接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。但是 Runnable 接口并没有任何对线程的支持,我们还必须创建 Thread 类的实例,这一点通过 Thread 类的构造函数public Thread(Runnable target);来实现。下面是一个例子:public class MyThread implements Runnable {int count= 1, number;public MyThread(int num) {number = num;Systemoutprintln("创建线程 " + number);}public void run() {while(true) {Systemoutprintln("线程 " + number + ":计数 " + count);if(++count== 6) return;} }public static void main(String args[]) {for(int i = 0; i < 5; i++) new Thread(new MyThread(i+1))start();}}严格地说,创建 Thread 子类的实例也是可行的,但是必须注意的是,该子类必须没有覆盖 Thread 类的 run 方法,否则该线程执行的将是子类的 run 方法,而不是我们用以实现Runnable 接口的类的 run 方法,对此大家不妨试验一下。使用 Runnable 接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。综上所述,两种方法各有千秋,大家可以灵活运用。下面让我们一起来研究一下多线程使用中的一些问题。三:线程的四种状态1 新状态:线程已被创建但尚未执行(start() 尚未被调用)。2 可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。3 死亡状态:正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。4 阻塞状态:线程不会被分配 CPU 时间,无法执行。四:线程的优先级 线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得 CPU 时间时,线程调度系统根据各个线程的优先级来决定给谁分配 CPU 时间,优先级高的线程有更大的机会获得 CPU 时间,优先级低的线程也不是没有机会,只是机会要小一些罢了。你可以调用 Thread 类的方法 getPriority() 和 setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。五:线程的同步由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。1 synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:public synchronized void accessVal(int newVal);synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。2 synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下: synchronized(syncObject) {//允许访问控制的代码}synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。六:线程的阻塞为了解决对共享存储区的访问冲突,Java 引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题,Java 引入了对阻塞机制的支持。阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过 *** 作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。1 sleep() 方法:sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。2 suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后 lishixinzhi/Article/program/Java/gj/201311/27622

我们不妨设想,为了创建一个新的线程,我们需要做些什么?很显然,我们必须指明这个线程所要执行的代码,而这就是在Java中实现多线程我们所需要做的一切!

真是神奇!Java是如何做到这一点的?通过类!作为一个完全面向对象的语言,Java提供了类 javalangThread 来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程,我们以后的讨论都将围绕这个类进行。

那么如何提供给 Java 我们要线程执行的代码呢?让我们来看一看 Thread 类。Thread 类最重要的方法是 run() ,它为Thread 类的方法 start() 所调用,提供我们的线程所要执行的代码。为了指定我们自己的代码,只需要覆盖它!

方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可。下面是一个例子:

public class MyThread extends Thread {

int count= 1, number;

public MyThread(int num) {

number = num;

Systemoutprintln("创建线程 " + number);

}

public void run() {

while(true) {

Systemoutprintln("线程 " + number + ":计数 " + count);

if(++count== 6) return;

}

}

public static void main(String args[]) {

for(int i = 0; i 〈 5; i++) new MyThread(i+1)start();

}

}

以上就是关于Java多线程程序设计初步入门全部的内容,包括:Java多线程程序设计初步入门、C#中构建多线程应用程序、多线程是什么意思等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/10207674.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-06
下一篇 2023-05-06

发表评论

登录后才能评论

评论列表(0条)

保存