多线程编程——基础篇

sapzj1984 2007-07-05 11:13:50
[写在前面]

  随着计算机技术的发展,编程模型也越来越复杂多样化。但多线程编程模型是目前计算机系统架构的最终模型。随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。事实上目前3.6G主频的CPU已经接近了顶峰。

  如果不能从根本上更新当前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分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。如果一时想不明白,不要急,但你要记得它们不是一回事就行了。

  累了吧?为不么不继续了?

  基于这种风格来介绍多线程,并不是每个人都喜欢和接受的,如果你不喜欢,正好不浪费你的时间了,而如果你接受的话,那就看下一节吧。

...全文
6320 9 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
9 条回复
切换为时间正序
请发表友善的回复…
发表回复
托尼迪 2008-10-03
  • 打赏
  • 举报
回复
刚刚开始涉及多线程方面的东东,一片迷茫啊
zengsai 2008-06-14
  • 打赏
  • 举报
回复
[code=BatchFile][/code]
  • 打赏
  • 举报
回复
操作系统分配时间片是以什么为单位?线程还是进程
线程

说明:
多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。
red_berries 2007-07-09
  • 打赏
  • 举报
回复
关于这方面一直有个问题没想明白,希望指教:操作系统分配时间片是以什么为单位?线程还是进程,如果是进程,好像通过多线程提高速度就没有意思了吧,因为再多的线程,它们总的时间片是相同的,但如果是线程,通过提高线程的优先级使操作系统为这个线程分配更多的时间片不就行了吗?为什么要通过增加线程个数这种通过改变“量”的办法来提高速度,而不使用前者改变“质”的办法呢?
注:我说的这种是在多个线程的运行目的都是一样的情况下,比如都是用来计算一批数据的,并且这些数据的计算结果对于计算没有影响。

胡说一气,还望指教
wuhuiITren 2007-07-09
  • 打赏
  • 举报
回复
Mark.....
hbdn520 2007-07-08
  • 打赏
  • 举报
回复
Mark.....
sapzj1984 2007-07-05
  • 打赏
  • 举报
回复
[wait(),notify()/notityAll()方法]
  关于这两个方法,有很多的内容需要说明.在下面的说明中可能会有很多地方不能一下子明白,但在看完本节后,即使不能完全明白,你也一定要回过头来记住下面的两句话:

  [wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法]

  [wait(),notify()/notityAll()方法只能在同步方法中调用]

[线程的互斥控制]
  多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响另一线程对该对象的真正结果.

  这个例子我们在太多的文档中可以看到,就象两个操售票员同时售出同一张票一样.

线程A 线程B
1.线程A在数据库中查询存票,发现票C可以卖出
class="left"2.线程A接受用户订票请求,准备出票.
3.这时切换到了线程B执行
4.线程B在数据库中查询存票,发现票C可以卖出
5.线程B将票卖了出去
6.切换到线程A执行,线程A卖了一张已经卖出的票

  所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能不能同时执行这一部分.

  象这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)

  在JAVA中,用synchornized关键字来实现互斥控制(暂时这样认为,JDK1.5已经发展了新的机制)

[synchornized关键字]
  把一个单元声明为synchornized,就可以让在同一时间只有一个线程操作该方法.

  有人说synchornized就是一把锁,事实上它确实存在锁,但是是谁的锁,锁谁,这是一个非常复杂的问题.

  每个对象只有一把监视锁(monitor lock),一次只能被一个线程获取.当一个线程获取了这一个锁后,其它线程就只能等待这个线程释放锁才能再获取.

那么synchornized关键字到底锁什么?得到了谁的锁?

对于同步块,synchornized获取的是参数中的对象锁:

synchornized(obj){ //............... }
  线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.

  假如:

public void test(){ Object o = new Object(); synchornized(obj){ //............... } }
  这段程序控制不了任何,多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.

  而如果是类的属性:

class Test{ Object o = new Object(); public void test(){ synchornized(o){ //............... } }}
  所有执行到Test实例的synchornized(o)的线程,只有一个线程可以获取到监视锁.

  有时我们会这样:

public void test(){ synchornized(this){ //............... } }
  那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和synchornized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchornized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).]

  而对于

synchornized(Test.class){ //............... }
  这样的同步块而言,所有调用Test多个实例的线程赐教只能有一个线程可以执行.

[synchornized方法]
  如果一个方法声明为synchornized的,则等同于把在为个方法上调用synchornized(this).

  如果一个静态方法被声明为synchornized,则等同于把在为个方法上调用synchornized(类.class).

  现在进入wait方法和notify/notifyAll方法.这两个(或叫三个)方法都是Object对象的方法,而不是线程对象的方法.如同锁一样,它们是在线程中调用某一对象上执行的.

class Test{ public synchornized void test(){ //获取条件,int x 要求大于100; if(x < 100) wait(); } }
  这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则为this.wait();

  假如:

  Test t = new Test();

  现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内.

  这时x小于100,所以线程A进入等待.

  当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),这是一个虚拟的对象,但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待.

  当一个线程进入等待时,它就会释放锁,让其它线程来获取这个锁.

  所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室.

  这两个线程只能等待其它线程调用notity[All]来唤醒.

  但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.

[为什么真正的应用都是用while(条件)而不用if(条件)]
  在实际的编程中我们看到大量的例子都是用?

  while(x < 100)

  wait();go();而不是用if,为什么呢?

  在多个线程同时执行时,if(x <100)是不安全的.因为如果线程A和线程B都在t的休息室中等待,这时另一个线程使x==100了,并调用notifyAll方法,线程A继续执行下面的go().而它执行完成后,x有可能又小于100,比如下面的程序中调用了--x,这时切换到线程B,线程B没有继续判断,直接执行go();就产生一个错误的条件,只有while才能保证线程B又继续检查一次.

[notify/notifyAll方法]
  这两个方法都是把某个对象上休息区内的线程唤醒,notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程.

  一般有为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明确知道只唤醒其中的一个线程.

  那么是否是只要调用一个对象的wait()方法,当前线程就进入了这个对象的休息室呢?事实中,要调用一个对象的wait()方法,只有当前线程获取了这个对象的锁,换句话说一定要在这个对象的同步方法或以这个对象为参数的同步块中.

class MyThread extends Thread{ Test t = new Test(); public void run(){ t.test(); System.out.println("Thread say:Hello,World!"); } } public class Test { int x = 0; public void test(){ if(x==0) try{ wait(); }catch(Exception e){} } public static void main(String[] args) throws Exception{ new MyThread().start(); } }
  这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.

  而如果改成:

public class Test { int x = 0; public synchornized void test(){ if(x==0) try{ wait(); }catch(Exception e){} } public static void main(String[] args) throws Exception{ new MyThread().start(); } }
  我们就可以看到线程一直等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出JVM环境,否则它一直等待.

  所以请记住:

  [线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁]

  以上是对线程基础知识的简单介绍,不进入实例,我们无法真正了解它的真实意义.下节我们就会以实例来进入多线程编程的 实战篇

转载自dev2dev网友axman的go deep into java专栏。

原文出处:http://blog.csdn.net/axman/archive/2005/07/22/431800.aspx

sapzj1984 2007-07-05
  • 打赏
  • 举报
回复
线程对象的几个重要的方法

  尽管线程对象的常用方法可以通过API文档来了解,但是有很多方法仅仅从API说明是无法详细了解的。

  本来打算用一节的篇幅来把线程方法中一些重要的知识说完,但这样下来估计要很常的篇幅,可能要用好几节才能说把和线程方法相关的一些重要的知识说完。

  首先我们接基础篇(二)来说明start()方法。

  一个线程对象生成后,如果要产生一个执行的线程,就一定要调用它的start()方法.在介绍这个方法时不得不同时说明run方法.其实线程对象的run方法完全是一个接口回调方法,它是你这个线程对象要完成的具体逻辑.简单说你要做什么就你在run中完成,而如何做,什么时候做就不需要你控制了,你只要调用start()方法,JVM就会管理这个线程对象让它产生一个线程并注册到线程处理系统中。

  从表面上看,start()方法调用了run()方法,事实上,start()方法并没有直接调用run方法.在JDK1.5以前start()方法是本地方法,它如何最终调用run方法已经不是JAVA程序员所能了解的.而在JDK1.5中,原来的那个本地start()方法被start0()代替,另个一个纯JAVA的start()中调用本地方法start0(),而在start()方法中做了一个验证,就是对一个全局变量(对象变量)started做检验,如果为true,则start()抛出异常,不会调用本地方法start0(),否则,先将该变量设有true,然后调用start0()。

  从中我们可以看到这个为了控制一个线程对象只能运行成功一次start()方法.这是因为线程的运行要获取当前环境,包括安全,父线程的权限,优先级等条件,如果一个线程对象可以运行多次,那么定义一个static 的线程在一个环境中获取相应权限和优先级,运行完成后它在另一个环境中利用原来的权限和优先级等属性在当前环境中运行,这样就造成无法预知的结果.简单说来,让一个线程对象只能成功运行一次,是基于对线程管理的需要。

start()方法最本质的功能是从CPU中申请另一个线程空间来执行run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的run()方法,当然也会执行,但那是在当前线程中执行,run()方法执行完成后继续执行下面的代码.而调用start()方法后,run()方法的代码会和当前线程并发(单CPU)或并行(多CPU)执行。

  所以请记住一句话[调用线程对象的run方法不会产生一个新的线程],虽然可以达到相同的执行结果,但执行过程和执行效率不同。

  [线程的interrupt()方法,interrupted()和isInterrupted()]

  这三个方法是关系非常密切而且又比较复杂的,虽然它们各自的功能很清楚,但它们之间的关系有大多数人不是真正的了解。

  先说interrupt()方法,它是实例方法,而它也是最奇怪的方法,在java语言中,线程最初被设计为"隐晦难懂"的东西,直到现在它的语义不没有象它的名字那样准确。大多数人以为,一个线程象调用了interrupt()方法,那它对应的线程就应该被中断而抛出异常,事实中,当一个线程对象调用interrupt()方法,它对应的线程并没有被中断,只是改变了它的中断状态。

  使当前线程的状态变以中断状态,如果没有其它影响,线程还会自己继续执行。

  只有当线程执行到sleep,wait,join等方法时,或者自己检查中断状态而抛出异常的情况下,线程才会抛出异常。

如果线程对象调用interrupt()后它对应的线程就立即中断,那么interrupted()方法就不可能执行。

   因为interrupted()方法是一个static方法,就是说只能在当前线程上调用,而如果一个线程interrupt()后它已经中断了,那它又如何让自己interrupted()?

  正因为一个线程调用interrupt()后只是改变了中断状态,它可以继续执行下去,在没有调用sleep,wait,join等法或自己抛出异常之前,它就可以调用interrupted()来清除中断状态(还会原状)interrupted()方法会检查当前线程的中断状态,如果为 "被中断状态"则改变当前线程为"非中断状态"并返回true,如果为"非中断状态"则返回false,它不仅检查当前线程是否为中断状态,而且在保证当前线程回来非中断状态,所以它叫"interrupted",是说中断的状态已经结束(到非中断状态了)isInterrupted()方法则仅仅检查线程对象对应的线程是否是中断状态,并不改变它的状态。

  目前大家只能先记住这三个方法的功能,只有真正深入到多线程编程实践中,才会体会到它们为什么是对象方法,为什么是类方法。

  线程到底什么时候才被中断抛出InterruptedException异常,我们将在提高篇中详细讨论。

  [sleep(),join(),yield()方法]

  在现在的环节中,我只能先说明这些方法的作用和调用原则,至于为什么,在基础篇中无法深入,只能在提高篇中详细说明。

  sleep()方法中是类方法,也就是对当前线程而言的,程序员不能指定某个线程去sleep,只能是当前线程执行到sleep()方法时,睡眠指定的时间(让其它线程运行).事实上也只能是类方法,在当前线程上调用.试想如果你调用一个线程对象的sleep()方法,那么这个对象对应的线程如果不是正在运行,它如何sleep()?所以只有当前线程,因为它正在执行,你才能保证它可以调用sleep()方法。

  原则:[在同步方法中尽量不要调用线程的sleep()方法],或者简单说,对于一般水平的程序员你基本不应该调用sleep()方法。

  join()方法,正如第一节所言,在一个线程对象上调用join方法,是当前线程等待这个线程对象对应的线程结束,比如有两个工作,工作A要耗时10秒钟,工作B要耗时10秒或更多。我们在程序中先生成一个线程去做工作B,然后做工作A。

  new?B().start();//做工作B

  A();//做工作A

  工作A完成后,下面要等待工作B的结果来进行处理.如果工作B还没有完成我就不能进行下面的工作C,所以

  B?b?=?new?B();

  b.start();//做工作B

  A();//做工作A

  b.join();//等工作B完成。

  C();//继续工作C。

  原则:[join是测试其它工作状态的唯一正确方法],我见过很多人,甚至有的是博士生,在处理一项工作时如果另一项工作没有完成,说让当前工作线程sleep(x),我问他,你这个x是如何指定的,你怎么知道是100毫秒而不是99毫秒或是101毫秒?其实这就是OnXXX事件的实质,我们不是要等多长时间才去做什么事,而是当等待的工作正好完成的时候去做。

  yield()方法也是类方法,只在当前线程上调用,理由同上,它主是让当前线程放弃本次分配到的时间片原则:[不是非常必要的情况下,没有理由调用它].调用这个方法不会提高任何效率,只是降低了CPU的总周期上面介绍的线程一些方法,基于(基础篇)而言只能简单提及.以后具体应用中我会结合实例详细论述。

  线程本身的其它方法请参看API文档.下一节介绍非线程的方法,但和线程密切相关的两[三]个对象方法:

  [wait(),notify()/notifyAll()]

  这是在多线程中非常重要的方法。

sapzj1984 2007-07-05
  • 打赏
  • 举报
回复
在进入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实例)的多个线程。

  下节我们将研究线程对象方法,还是那句话,一般文档中可以读到的内容我不会介绍太多
请大家自己了解。

12学通csharp网络编程——第四 TCP应用编程 12学通csharp网络编程——第三 HTTP应用编程(下) 12学通csharp网络编程——第二 HTTP应用编程(上) 12学通csharp网络编程——第一 基础之进程线程 Lucene(1)lucene,你也会(7)——第一 快速入门 MongoDB(8)8天学通MongoDB——第八天 驱动实践 8天学通MongoDB——第七天 运维技术 8天学通MongoDB——第六天 分片技术 8天学通MongoDB——第五天 主从复制 8天学通MongoDB——第四天 索引操作 8天学通MongoDB——第三天 细说高级操作 8天学通MongoDB——第二天 细说增删查改 8天学通MongoDB——第一天 基础入门 UML系列(4)团队沟通利器之UML——类图 团队沟通利器之UML—— 序列图 团队沟通利器之UML——用例图 团队沟通利器之UML——活动图 wcf系列(5)wcf系列学习5天速成——第五天 服务托管 wcf系列学习5天速成——第四天 wcf之分布式架构 wcf系列学习5天速成——第三天 事务的使用 wcf系列5天速成——第二天 binding的使用(2) wcf系列5天速成——第一天 binding的使用(1) wpf系列(8)8天入门wpf—— 第八天 最后的补充 8天入门wpf—— 第七天 画刷 8天入门wpf—— 第六天 细说控件 8天入门wpf—— 第五天 数据绑定 8天入门wpf—— 第四天 模板 8天入门wpf—— 第三天 样式 8天入门wpf—— 第二天 xaml详解 8天入门wpf—— 第一天 基础概念介绍 并行开发(8)8天玩转并行开发——第八天 用VS性能向导解剖你的程序 8天玩转并行开发——第七天 简要分析任务与线程池 8天玩转并行开发——第六天 异步编程模型 8天玩转并行开发——第五天 同步机制(下) 8天玩转并行开发——第四天 同步机制(上) 8天玩转并行开发——第三天 plinq的使用 8天玩转并行开发——第二天 Task的使用 8天玩转并行开发——第一天 Parallel的使用 多线程系列(5)5天不再惧怕多线程——第五天 线程池 5天不再惧怕多线程——第四天 信号量 5天不再惧怕多线程——第三天 互斥体 5天不再惧怕多线程——第二天 锁机制 5天不再惧怕多线程——第一天 尝试Thread 经典算法专题(21)经典算法题每日演练——第二十一题 十字链表 经典算法题每日演练——第二十题 三元组 经典算法题每日演练——第十九题 双端队列 经典算法题每日演练——第十八题 外排序 经典算法题每日演练——第十七题 Dijkstra算法 经典算法题每日演练——第十六题 Kruskal算法 经典算法题每日演练——第十五题 并查集 经典算法题每日演练——第十四题 Prim算法 经典算法题每日演练——第十三题 赫夫曼树 经典算法题每日演练——第十二题 线段树 经典算法题每日演练——第十一题 Bitmap算法 经典算法题每日演练——第十题 树状数组 经典算法题每日演练——第九题 优先队列 经典算法题每日演练——第八题 AC自动机 经典算法题每日演练——第七题 KMP算法 经典算法题每日演练——第六题 协同推荐SlopeOne 算法 经典算法题每日演练——第五题 字符串相似度 经典算法题每日演练——第四题 最长公共子序列 经典算法题每日演练——第三题 猴子吃桃 经典算法题每日演练——第二题 五家共井 经典算法题每日演练——第一题 百钱买百鸡 开发利器系列(1)介绍一个小工具 Linqer 那点所谓的分布式(2)那点所谓的分布式——memcache 那点所谓的分布式——redis 树结构专题(5)6天通吃树结构—— 第五天 Trie树 6天通吃树结构—— 第四天 伸展树 6天通吃树结构—— 第三天 Treap树 6天通吃树结构—— 第二天 平衡二叉树 6天通吃树结构—— 第一天 二叉查找树 算法速成系列(15)算法系列15天速成——第十五天 图【下】(大结局) 算法系列15天速成——第十四天 图【上】 算法系列15天速成——第十三天 树操作【下】 算法系列15天速成——第十二天 树操作【中】 算法系列15天速成——第十一天 树操作(上) 算法系列15天速成——第十天 栈 算法系列15天速成——第九天 队列 算法系列15天速成——第八天 线性表【下】 算法系列15天速成——第七天 线性表【上】 算法系列15天速成——第六天 五大经典查找【下】 算法系列15天速成——第五天 五大经典查找【中】 算法系列15天速成——第四天 五大经典查找【上】 算法系列15天速成——第三天 七大经典排序【下】 算法系列15天速成——第二天 七大经典排序【中】 算法系列15天速成——第一天 七大经典排序【上】 算法洗脑系列(8)算法洗脑系列(8)——第八 概率思想 算法洗脑系列(8)——第七 动态规划 算法洗脑系列(8)——第六 回溯思想 算法洗脑系列(8)——第五 分治思想 算法洗脑系列(8)——第四 枚举思想 算法洗脑系列(8)——第三 贪心思想 算法洗脑系列(8)——第二 递归思想 算法洗脑系列(8)——第一 递推思想 天籁数学(3)天籁数学——数列(3) 天籁数学——数列(2) 天籁数学——数列(1) 图形图像(1)玩玩图形图像——第一:图片灰度化 小爬虫系列(4)玩玩小爬虫——抓取时的几个小细节 玩玩小爬虫——抓取动态页面 玩玩小爬虫——试搭小架构 玩玩小爬虫——入门
欢迎加入 Windows 编程的世界! 在这里,我将一步一步地引导你进入这个神奇而有趣的世界。在开始之前,需要注意的是本教程中所有的例子都是在 Visual C++ 6.0 中调试通过,因此最好不要使用 Visual C++ 5.0 以下的版本。 第一:Windows 编程基础 第一章 第一天课程——从实践中学习 第二章 第二天课程——建立项目,创建窗口 第三章 第三天课程——标准的 Windows 程序 第四章 第四天课程——消息、WindowsX 和绘制正文 第五章 第五天课程——鼠标和键盘 第六章 第六天课程——资  源 第七章 第七天课程——高级资源:位图和对话框 第八章 本复习——控制台应用程序和长文件名 第二:字体和窗口控制 第九章 第八天课程——Windows 动画:Snake 游戏 第十章 第九天课程——字体基础知识 第十一章 第十天课程——窗口控制 第十二章 第十一天课程——与控制对话 第十三章 第十二天课程——窗口控制和控制消息 API 第十四章 第十三天课程——窗口控制的细分类 第十五章 第十四天课程——窗口的样式 第十六章 本复习——Windows 控制 第三:高级主题——公用控制、线程和内存管理 第十七章 第十五天课程——Windows 9x 控制导论 第十八章 第十六天课程——对话框和映像方式 第十九章 第十七天课程——高级对话:设置数据和获取数据 第二十章 第十八天课程——Windows 9x 对话和控制 第二十一章 第十九天课程——Windows 9x 的高级控制 第二十二章 第二十天课程——深入学习菜单和图标 第二十三章 第二十一天课程——线程、多任务和内存管理 第二十四章 本复习——树形视图
本书详细介绍了Qt的基础知识和GUI编程应用,举例翔实,内容全面,基本涵盖了Qt编程的各个方面。全书共分321章,包括Qt GUI编程基础知识(对话框、基础窗口部件、程序主窗口、布局管理),中级编程(2D绘图、拖放操作与剪贴板、文件处理、网络编程多线程、事件机制、数据库以及Qt的模板库和工具类)和高级应用(XML应用、模型/视图结构、高级绘图、进程间通信、Qt插件和脚本应用)。同时,在相关章节也穿插了一些重要的知识点,包括元对象系统、属性系统、对象树机制、信号/槽机制等。 本书体系完整,内容实用,可以作为Qt初学者的入门进阶书籍,适合具有一定开发经验的Qt程序员作为参考书,也可以作为大中院校相关专业及培训机构的教材。 目录前言 初级 第1章 Qt初步实践 1.1 第一个Qt程序 1.2 使用Qt布局管理器 1.3 关联操作 1.4 小结 第2章 对话框——QDialog 2.1 自定义对话框 2.2 加入主程序 2.3 Qt内建(built-in)对话框 2.4 小结 第3章 基础窗口部件——QWidget 3.1 Qt设计器绘制窗口部件 3.2 程序中引入自定义窗口部件 3.3 Qt的信号和槽机制 3.4 窗口标志及几何布局 3.5 Qt样式表 3.6 Qt对象模型 3.7 小结 第4章 程序主窗口——QMainWindow 4.1 QMainWindow主窗口框架 4.2 Qt设计器绘制主窗口 4.3 代码创建主窗口 4.4 锚接部件 4.5 状态栏 4.6 实现文本编辑器功能 4.7 多文档 4.8 打印文档 4.9 小结 第5章 布局管理 5.1 Qt布局管理器——QLayout 5.2 分裂器部件Qsplitter 5.3 栈部件Qstackedwidget 5.4 工作空间部件QWorkspace 5.5 多文档区部件QMdiArea 5.6 小结 中级 第6章 2D绘图 第7章 拖放操作和剪贴机 第8章 文件处理 第9章 网络 第10章 多线程 第11章 事件处理 第12章 数据库 第13章 Qt的模板库和工具类 高级 第14章 XML 第15章 模型/视图结构 第16章 高级绘图 第17章 进程与进程间通信 第18章 Qt插件 第19章 脚本——QtScript 第20章 国际化 第21章 Qt单元测试框架 附录A Qt安装 附录B Qt集成开发环境 附录C qmake速查 附录D 深入Qt源代码

568

社区成员

发帖
与我相关
我的任务
社区描述
英特尔® 边缘计算,聚焦于边缘计算、AI、IoT等领域,为开发者提供丰富的开发资源、创新技术、解决方案与行业活动。
社区管理员
  • 英特尔技术社区
  • shere_lin
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧