JAVA基础知识点:线程知识点_第1页
JAVA基础知识点:线程知识点_第2页
JAVA基础知识点:线程知识点_第3页
JAVA基础知识点:线程知识点_第4页
JAVA基础知识点:线程知识点_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、线程是JAVA的高级部分,我们的重点是理解JAVA中线程的概念,原理为今后的的学习高级部分,以及面试做准备。然后是多线程的实现方式以及简单的启动,停止线程方法,为游戏做准备。多任务处理:什么是多任务呢?多任务就是几个任务同时运行。在我们上网的时候,即可以聊天,也可以听音乐,也可以浏览网页。这就是多任务。它有个特点:就是在一个任务还没有做完的的时候,又去做另外的任务。在程序里的多任务处理是怎么样的呢?它也是指在同一个时候执行多个任务,它不必等到某个任务执行完之后,再去做其他的任务。CPU在处理的时候,先是执行一个任务的一段代码,然后接着执行另一个任务的一段代码。交替执行直到任务结束。现实生活中这

2、样的例子是很多的。如:有个柜台只有一个营业员。这时候来了二个顾客。那么这个营业员应该怎么样去应对呢?她问第一个顾客要买什么商品,然后把商品给第一个顾客看;在第一个顾客看的时候,又去问第二个顾客要买什么商品,然后把商品给第二个顾客看。在第二个顾客看时候,又去问第一个顾客对商品满不满意。如果顾客说再看看,那么营业员又去问第二个顾客满不满意。如果第二个顾客说满意,营业员就叫顾客付款,然后结束第二个顾客的营业任务,然后再去问第一个顾客满不满意。如果第一个顾客也满意付款。那么整个任务就结束了。多任务处理的2种类型:u多任务处理有两种类型:- 基于进程- 基于线程u进程是指一种“自包容”的运行程序,有自己

3、的地址空间;当我们打开windows任务管理器的时候,就有一个进程的选项卡。在这个选项卡里,我们可以发现有很多当前计算机正在运行的应用程序。这些正在运行的每一个应用程序都叫一个进程。它们都会占用一定的内存,都有自己的地址空间。u基于进程的特点是允许计算机同时运行两个或更多的程序。在JAVA中是可以启动一个进程的。就是说通过JAVA代码,我们可以打开另一个应用程序代码示例:/创建一个操作系统进程,用指定的的应用程序去打开指定的文件ProcessBuilder process=new ProcessBuilder("C:/WINDOWS/NOTEPAD.EXE",/应用程序地址

4、"c:/2.txt");/文件地址try process.start();/启动进程 catch (IOException e) e.printStackTrace();在上面的示例中,我们是开启了一个进程,用指定地址的应用程序(这里是记事本),去打开指定的文件(这里是C盘下的2.txt)线程和多进程不同,什么是多线程呢?线程是进程内部单一的一个顺序控制流。所以一个进程包含很多线程。基于线程的多任务处理环境中,线程是最小的处理单位。我们在使用word文档时,打开一个文件菜单,这就是开启了一线程,把一行字选中,加粗,这也是一个线程。线程的概念其实很简单,线程就是程序运行时的路

5、径,它决定将要执行什么代码。所以当我们运行main方法时,其实就已经开启了一个线程了。基于线程的多任务处理的优点u基于线程所需的开销更少在多任务中,各个进程需要分配它们自己独立的地址空间多个线程可共享相同的地址空间并且共同分享同一个进程网站是个很典型的多任务处理的例子,当不同的用户同时去访问一个网站时,网站就需要进行多任务处理。以前使用CGI(通用网关接口技术),CGI是一个多进程的技术,当一个用户去访问网站时,是开启一个进程,这样的话,当有多个用户同时访问时,那么就得开启多个进程。每一个进程都有自己独立的地址空间,所以这样的网站内存很快就消耗掉了。不但可连接的用户少,而且很容易受到恶意攻击。

6、在JAVA里,采用的servlet技术,是一个基于多线程的技术,共享同一个进程,共享相同的地址空间,这样的话,当有多个用户同时访问时,性能不会下降多少。u进程间调用涉及的开销比线程间通信多u线程间的切换成本比进程间切换成本低那么什么又是单线程,什么又是多线程呢?代码示例:public static void main(String args) a(); b(); c();代码执行的顺序图如下:在上面的例子里,大家可以看出,在main方法里执行时,必须要等到a()方法执行完了之后,才能执行b()方法。b()方法执行完以后才能执行C()方法。这样在等到一个方法执行完以后再执行另一个方法的模式叫单线

7、程。我以前用到的程序大部分都是单线程的。那么多线程是怎么一回事呢?在上面的例子,可以看出,多线程是多个方法同时执行,在a()方法执行时,同时执行b()方法,换句话说,b()方法不必等到a()方法执行完以后再去执行。这就是多线程。就是说在一个程序中,同时有多个执行路径存在。西游记里孙语空去打妖精,这时候他一个人去打三个妖精,它要把一个妖精打死之后,才能去打第二个妖精,第二个妖精打死之后,再打第三个妖精。这时候它是单线程的。一个打妖精很累,它就拔三根毫毛,变成三个小孙悟空,一个小孙悟空打一个妖精,就是说它不必等到第一个妖精打死之后再去打第二个妖精,而是同时去打。这就叫多线程。u在Java程序启动时

8、,一个线程立刻运行,该线程通常称为程序的主线程。也就是main方法运行时就产生一个主线程。u主线程的特点: 最先开始最后结束由他产生其他子线程通常它必须最后完成执行,因为它执行各种关闭动作。这在上例中,孙悟空就好比是主线程,它变出的三个小猴子是子线程。小猴子是由孙悟空这个主线程产生的,小猴子打死妖精之后,由孙悟空再把小猴子回收成毫毛。代码示例:public static void main(String args)/获得当前线程对象,即主线程Thread t= Thread.currentThread(); System.out.println("当前线程是: "+t);

9、/输出结果:"当前线程是: Threadmain,5,main" /*在中括号中第一个main指的是线程的名字, * 5指的是线程的优先级, * 第二个main是线程所在的线程组 */ t.setName("主线程"); /设置线程名字 System.out.println("当前线程名是: "+t);/ 输出结果是:"当前线程名是: Thread主线程,5,main" try for(int i=0;i<3;i+) System.out.println(i); /* * 线程休眠1500毫秒 * 就是线程暂

10、时停止运行1500毫秒 * * sleep是线程类Thread的静态方法, * 指的是当前线程休眠1500毫秒(这里是主线程) */ Thread.sleep(1500); catch(InterruptedException e) System.out.println("主线程被中断"); 在这个例子中,是单线程的程序,而main方法所在的线程就是主线程。那么如何去通过这个主线程去启动一个子线程呢?u通过以下两种方法创建 Thread 对象:1、- 声明一个 Thread 类的子类,并覆盖 run() 方法。 public class Threads public sta

11、tic void main(String args) One one=new One();/创建线程对象one.start();/启动线程for(int i=65;i<75;i+)/打印字母System.out.println(char)i);class One extends Thread/继承Thread线程对象public void run()/启动线程后自动调用子类重写父类的run()方法for(int i=0;i<10;i+)System.out.println(i);在上面的例子中定义了一个线程,并且重写了父类的run()方法。然后在main方法这个主线程中启动这个线程

12、,启动一个线程使用线程对象的start()方法。这时候就用自动的调用父类的run()方法,循环打出一串数字。在main主线程里也有一个循环,是打出字母。因为是两个线程同时候运行,这时候打印的结果是数字和字线交替打印。说明主线程不必等到子线程结束以后,就继续执行下面的语句。有些同学可能会说了,既然启动线程是调用run()方法。那么我直接调用run()方法不就行了,为什么要调用start()方法呢?上面例子中,main()方法改为One one=new One();/创建线程对象one.run();/启动线程for(int i=65;i<75;i+)/打印字母System.out.print

13、ln(char)i);不也一样吗?这样的想法是错误的。因为启动线程是使用start()方法。这时候在主线程之外另外开启了一个线程,就是说主线程不必等到子线程结束以后再进行其他的动作。所以这时候字母和数字循环打印。而如果直接调用run()方法,其实只是普通方法的调用,并没有开启一个线程,所以是单线程的程序。上面例子中,就不会是数字和字母交替打印,而是先把数字打印完毕,再打印字母。2、声明一个实现 Runnable 接口的类,并实现 run() 方法。 public class Threads public static void main(String args) Thread two=new

14、Thread(new Two();/创建线程对象,而其中的参数是实现了Runnable接口的类对象。two.start();/启动线程class Two implements Runnable/实现Runnable接口,并实现这个接口定义的run()方法public void run() System.out.println("线程启动");上面的例子是由一个类去实现Runnable接口,然后实现Runnable里定义的run()方法。然后再定义一个Thread线程类对象,将这个实现了Runnable接口的类对象作为参数传过去。这样的话,就是把线程对象和这个实现了Runna

15、ble接口的类对象作了一个关联,在启动线程的时候,就是去执行实现了Runnable接口的类对象里重写的run()方法。上面两种方法都可以实现线程的启动,但通常采用第二种方法启动线程,原因是如果一个类继承了另一个类的时候,那么就不能再继承其他类了,但接口可以多实现,所以实现接口的方式更加灵活一些。线程启动之后,会有不同的状态,它有什么样的状态呢?从上面的图可以看出线程经常处于暂时停止执行的状态,那么什么时候线程暂时停止执行呢?u线程:-线程优先级比较低,因此它不能获得 CPU 时间。-使用 sleep( ) 方法使线程睡眠。-通过调用 wait( ) 方法,使线程等待。 -通过调用 yield(

16、 ) 方法,线程已显式出让CPU控制权。(挂起)-线程由于等待一个文件I/O事件被阻塞。线程优先级线程是有优先级的,就是说当两个线程同时启动或同时去访问一个对象时,优先级高的线程会拥有更高的访问权。线程的优先级有110级,数字越大,优先级越高。uJava 中的线程优先级是在 Thread 类中定义的常量-NORM_PRIORITY : 值为 5-MAX_PRIORITY : 值为 10-MIN_PRIORITY : 值为 1u缺省优先级为 NORM_PRIORITY 5级u有关优先级的方法有两个:-final void setPriority(int newp) : 修改线程的当前优先级-fi

17、nal int getPriority() : 返回线程的优先级代码示例:public class Threads public static void main(String args) One one=new One();Thread two=new Thread(new Two();one.setPriority(1);/设置第一个线程优先级为1级two.setPriority(10); /设置第二个线程优先级为10级one.start();/启动第一个线程two.start();/启动第二个线程class One extends Threadpublic void run()Syste

18、m.out.println("第一个线程");class Two implements Runnablepublic void run() System.out.println("第二个线程");上面例子运行的结果是“第二个线程 第一个线程”。虽然第一个线程比第二个线程先调用start()方法,但由于第一个线程的线程优先级比第二个线程低,所以优先启动第二个线程。线程同步西游记里孙悟空拔出两根毫毛,变出两个小猴子,一个小猴子去把妖精往死里打,而另一个小猴子去问妖精话。这样的话就有问题了,如果第一个小猴子把妖精打死了,那么第二个猴子就没得问了。在网站访问的时

19、候,如果两个用户同时去访问同一个数据库的同一张表,如果一个用户去查询,而另一个用户去修改。那么第一个用户访问到的数据是修改之前的数据,还是修改之后的数据呢?所以,为了解决上面的问题,线程采用了同步的概念。为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”。就是说当两个线程同时访问同一个对象的时候,只允许一个线程访问。所以使用同步可以保证任何时候只有一个线程访问一个方法或对象u线程同步,使用同步关键字synchronized来进行标识代码示例:需求:模拟银行取款的业务。有两个顾客去取钱的业务。public class BankTest public static void mai

20、n(String args) Bank bank=new Bank();/产生银行对象Man man1=new Man(bank,1500);/产生第一个顾客对象Man man2=new Man(bank,1300);/产生第二个顾客对象try Thread.sleep(3000);/主线程休眠3000毫秒 catch (InterruptedException e) e.printStackTrace();System.out.println("银行最后余额"+bank.getMoney();/获得当前银行帐户最后余额/* * 银行帐户类 * */class Bank/*

21、 * 当前帐户余额 */private int money=2000;/* * 返回当前金额 */public int getMoney()return money;/* * 设置当前金额 * param pressMoney */public void setMoney(int pressMoney)if(pressMoney<=money)/当取走的钱小于当前金额时候,允许操作try Thread.sleep(1000);/线程休眠1000毫秒 catch (InterruptedException e) / TODO 自动生成 catch 块e.printStackTrace();

22、money-=pressMoney;/存款减少System.out.println("取走"+pressMoney);else/当取的钱大于帐户余额,拒绝操作System.out.println("帐户已超支,操作失败");System.out.println("最后"+money);/打印最后金额/* * 顾客类 * author Administrator * */class Man extends ThreadBank bank;/银行帐户int money;/顾客要取金额Man1(Bank bank,int money)thi

23、s.bank=bank;this.money=money;this.start();/调用帐户取钱方法public void putMoney()bank.setMoney(money);/* * 线程run方法 */public void run()putMoney();以上代码运行结果:取走1300取走1500最后-800最后-800银行最后余额-800上面例子中,对银行而言,是不允许帐户余额为负数的,所以在帐户类的setMoney()方法中,先判断取的钱是否大于帐户余额,如果大于帐户余额则操作失败。但这时候,有两个顾客(Man类对象)线程同时对同一个帐户进行操作。因为它们取的钱都小于帐户

24、余额,这样的话,两个顾客都允许对帐户操作,这样帐户余额都会减少,因为两个顾客取钱的数目总和大于帐户余额,所以最后的结果帐户余额就是负数。那么怎么防止这种情况呢?解决办法就是在setMoney()方法前面加上一个同步的关键字synchronized。使当前帐户类对象同步,这样在一线程在访问帐户时,另一个线程就不能再对帐户进行操作了。上面代码改为:/* * 设置当前金额 * param pressMoney */public synchronized void setMoney(int pressMoney)再运行时,结果就变成:取走1500最后500帐户已超支,操作失败最后500银行最后余额50

25、0上面的方法叫同步方法,使用同步方法的时候,锁住的是本身这个类对象,所以一旦这个类对象被一个线程使用,那个这个类对象就被锁住,那么其他的方法(即使不是同步方法)也不能被其他线程所访问了。和同步方法对应的,还有同步块。就是在一个方法里不要求全部同步,而只是在调用对象时,对这个对象同步,(锁住的是这个对象)这时候就得用同步块。同步块写在方法里。代码示例:class One void display(int num) System.out.print("" + num);try Thread.sleep(1000); catch (InterruptedException e)

26、System.out.println("中断");System.out.println(" 完成");class Two implements Runnable int number;One one;Thread t;public Two(One one_num, int n) one = one_num;number = n;t = new Thread(this);t.start();public void run() synchronized (one) /同步块,对one这个对象同步,当前线程对one这个对象进行访问时,就将这个对象锁住。one.

27、display(number);死锁u当两个线程循环依赖于一对同步对象时将发生死锁。例如: 一个线程进入对象ObjA上的监视器,而另一个线程进入对象ObjB上的监视器。如果ObjA中的线程试图调用ObjB上的任何 synchronized 方法,就将发生死锁。u死锁很少发生,但一旦发生就很难调试线程之间的通信:在上面西游记里孙悟空拔出两根毫毛,变出两个小猴子,一个小猴子去把妖精往死里打,而另一个小猴子去问妖精话。这时候就是两个线程去访问同一个对象,如果妖精这个对象加了同步的话,那么当问话的小猴子访问的时候,那么打妖精的小猴子就只有暂时停止运行,进行等待。等到问话的小猴子问完了以后,就会通知打妖

28、精的小猴子我问完了,你可以把它打死了,这时候打妖精的小猴子就继续进行打妖精的任务。象这样两个线程访问同一个对象,当一个线程访问时,其他线程进行等待,当访问线程放弃当前对象使用权时,通知其他线程这样的机制就叫线程之间的通信。u Java提供了一个精心设计的线程间通信机制,使用wait()、notify()和notifyAll()方法 。u这些方法是作为 Object 类中的 final 方法实现的。所以不能被子类重写u这三个方法仅在 synchronized 方法中才能被调用。wait、notify、notifyAll都是Object类定义的方法,不要理解为线程类定义的方法,因为所有的类都是直接

29、或间接继承于Object类的,所以所有的类都会拥有这三个方法。uwait()方法告知被调用的线程退出监视器并进入等待状态,直到其他线程进入相同的监视器并调用 notify( ) 方法。 unotify( ) 方法通知同一对象上第一个调用 wait( )线程。 unotifyAll() 方法通知调用 wait() 的所有线程,具有最高优先级的线程将先运行。线程间通信最典型的例子就是生产者、消费者和仓库的关系。代码示例:public class Creates public static void main(String args) 仓库 depot=new 仓库();/创建仓库对象/* * 生产

30、者和消费者共享一个仓库资源 */new 生产者 (depot).start();new 消费者 (depot).start();class 仓库boolean isfull;/判断当前仓库是否为空class 生产者 extends Thread仓库 depot;生产者(仓库 depot)this.depot= depot;/* *生产方法 */private void create()synchronized(depot)/线程同步块,对仓库对象同步if(depot.isfull=false)/当仓库isfull为假时,可以进行生产try Thread.sleep(1000);/休眠1000毫

31、秒System.out.println("生产完毕");depot.isfull=true;/仓库状态设置为满/唤醒在仓库x对象上等待的下一个线程depot.notify(); catch (InterruptedException e) e.printStackTrace();elsetry /等待方法,当前线程放弃对depot对象的使用权depot.wait(); catch (InterruptedException e) e.printStackTrace();/* * 线程run方法 */public void run()for(int i=0;i<10;i

32、+)create();class 消费者 extends Thread仓库 depot;消费者(仓库 depot)this. depot = depot;/* * 消费方法 * */private void 消费()synchronized(depot)/线程同步块,对仓库对象同步if(depot.isfull=true)/当仓库isfull为真时,可以进行消费try Thread.sleep(1000);/休眠1000毫秒System.out.println("消费完毕");depot.isfull=false;/仓库状态设置为空/唤醒在仓库x对象上等待的下一个线程dep

33、ot.notify(); catch (InterruptedException e) e.printStackTrace();elsetry /等待方法,当前线程放弃对depot对象的使用权depot.wait(); catch (InterruptedException e) e.printStackTrace();/* * 线程run方法 */public void run()for(int i=0;i<10;i+)消费();代码分析:生产者负责生产,生产的东西放入仓库。当仓库空的时候,进行生产;当仓库满的时候,进行等待。消费者负责消费,当仓库满的时候,进行消费,当仓库空的时候,进

34、行等待。这时候,当仓库空的时候,生产者生产。生产完毕,仓库满了,就通知消费者,可以进行消费了,同时自己暂时停止当前线程的执行,进行等待,并放弃仓库这个资源的使用权。消费者接到这个通知后,就进行消费,当消费到仓库空的时候,就通知生产者可以进行生产了,同时自己暂时停止当前线程的执行,进行等待,并放弃仓库这个资源的使用权。这样两个线程互相唤醒,相互等待。sleep和wait的区别sleep和wait都是使线程暂时停止执行的方法,但它们有很大的不同,sleep是线程类Thread 的方法,它是使当前线程暂时睡眠,可以放在任何位置。而wait是Object类的方法,它是使当前线程暂时放弃对象的使用权进行

35、等待,必须放在同步方法或同步块里。Sleep使用的时候,线程并不会放弃对象的使用权,即不会释放对象锁,所以在同步方法或同步块中使用sleep,一个线程访问时,其他的线程也是无法访问的。而wait是会释放对象锁的,就是当前线程放弃对象的使用权,让其他的线程可以访问。线程执行wait方法时,需要另一个线程调用notify进行唤醒。而sleep只是暂时休眠一定时间,时间到了之后,自动恢复运行,不需另外的线程唤醒。下面的例子是模拟厨师、营业员和仓库的关系。加深同学们对线程同步的理解。 需求:货架上有三个面包,而营业员要卖出六个。厨师不断的生产,营业员不断销售。public class 多线程练习 pu

36、blic static void main(String args) 仓库 仓库1 = new 仓库();/* * 营业员和厨师共享一个仓库对象 */营业员 营业员1 = new 营业员(仓库1);厨师 厨师1 = new 厨师(仓库1);营业员1.start();/启动营业员线程厨师1.start();/启动厨师线程class 仓库 Object 正在生产的面包=new Object();int 已完成面包 = 3;int 要生产面包 = 6;int 库存 = 3;int 已销售面包 = 0;boolean isSell = true;/营业员是否能够销售class 营业员 extends

37、Thread 仓库 仓库1;营业员(仓库 仓库1) this.仓库1 = 仓库1;public void run() while (仓库1.isSell) /当isSell为真的时候,可以进行销售.销售();System.out.println("营业员工作完成");public void 销售() System.out.println("现有库存"+仓库1.库存);if (仓库1.已销售面包 < 仓库1.要生产面包) /未销售完if (仓库1.库存 != 0) / 仓库里还有面包,可以销售仓库1.已销售面包+;System.out.println("销售第" + 仓库1.已销售面包 + "面包");try Thread.sleep(2000);/销售一个面包要2000毫秒 catch

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论