黑马程序员-java 多线程
2013-04-21 15:35
218 查看
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
由于以前工作不曾使用过多线程技术,而通过最近一段时间的学习,可能学的东西还很片面。使用起来也很生疏,为了能更好的记忆和理解,我来把我最近一段时间学习的东西做一些整理。
首先,对于多线程技术,我们要明白如下的定义:
1.什么是进程?
进程就是正在进行中的程序,也就是对应一个应用程序在内存中所使用的空间。
2. 什么是线程?
线程就是进程中用于控制程序执行的控制单元(执行路径,执行情景),而一个进程中至少要有一个线程。
3.什么是多线程
多线程就是一个进程中有多个(两个或两个以上)执行路径(控制单元)。
注意:对于JVM,启动时,就有两个线程:
1.jvm的主线程,也就是执行mian函数的线程。
2.jvm的垃圾回收线程。
创建线程的目的:就是开启一条线程,去运行指定的代码(线程要执行的代码),并且和其他代码同时运行,以达到多个线程来实现多任务并发,提高程序的执行效率。
那么,我们怎么来创建线程呢?在Java中提供了两种线程的方式:
一.继承Thread类
注:java提供的对线程描述的类是Thread类, 创建线程对象的方法是构造函数,提供了要被线程执行的代码存储的位置是在run()里面。
步骤:
1.继承Thread类。
2.覆盖run方法,将线程要运行的代码定义其中。(要覆盖run方法的原因是,定义线程要运行的代码存储在run()里面)
3.创建Thread类的子类对象,其实就是在创建线程,调用start方法(其中,start()有两个功能:1.开启线程 2.执行run()方法)。
二.实现Runnable接口
注:下面的3种情况,就不可以再使用继承Thread类。
1.自定义的类中有多线程要运行的代码。但是该类有自己的父类。
2.要求程序具有功能的可扩展性。
3.多个功能使用一个共享数据。
那么,程序就提供了一个规则。通过实现Runnable接口。并将多线程要运行的代码存放在实现了Runnable类的子类的run方法中,然后调用对应的线程,进行处理。
步骤:
1,定义了实现Runnable接口。
2,覆盖接口的run方法。将多线程要运行的代码存入其中。
3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。
4,调用Thread对象的start方法。开启线程。
通过上面的知识点,我们可以知道:
1. 线程都有自己默认的名称,格式是:Thread-编号,该编号是从0开始。
2. 使用多线程的好处是:解决了多部分同时运行的问题。
3. 继承Thred类和实现Runnable接口这两种方式,都能创建线程,但是综合比较,创建线程建议使用实现Runnable接口这种方式,原因如下:
A:通过Thread继承的类,有局限性,不能再继承其他类.,这是java单继承的特性。
B:实现Runnable接口实现多线程,可以实现数据共享,根据接口可以多个实现,并且可以继承其他类,所以功能的扩展性能好。
线程的生命周期
线程可以划分为5个状态:创建、运行、阻塞(临时状态)、冻结、消亡。线程启动后,并不是一直占用cpu独自运行,而是多个线程在cpu中进行切换运行,所以多线程具备随机性。
临时状态的特点:具备了执行资格,但不具备执行权。
冻结状态的特点:放弃了执行资格。
注:启动线程使用的start方法,而不是run方法,调用start方法启动线程,系统会把该run方法当成线程执行体来处理。如果直接调用线程对象的run方法,就相当在执行普通的方法。
多线程运行有什么问题么?
由于多线程的运行具备随机性,就有可能会产生多线程的安全问题。
问题的产生的原因:
几个关键点:
1,多线程代码中有操作共享数据。
2,多条语句操作该共享数据。
当出现上面的情况时,就有可能出现下面问题:
有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。
解决的办法:
当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。
根据上面的思路,那么可以使用Java中提供的同步的原理:就是将部分操作功能数据的代码进行加锁,就可以保证在同一时间只允许一个线程进行操作。
使用同步的前提是:
1,必须是两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
同步可以分为两种表现形式:
一.同步代码块
函数类型前+synchronized关键字,如下:
1,同步代码块使用的锁是任意对象。
2,同步函数使用的锁是this。
注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。
同步的优点和缺点:
优点:解决了线程的安全问题
缺点:要判断同步锁,较为消耗资源;并且同步嵌套后,容易死锁。
死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
注:同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行
线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
为了让线程协调运行,Object类提供了如下三个方法:
wait():导致当前线程处于冻结状态,被wait的线程,会被存储到线程池中,可以通过其他线程使用notify()方法或notifyAll()方法来唤醒该线程。
notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
notifAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。
注意:wait()和notify()等待和唤醒的锁,必须是同一个锁。
为什么这些操作线程的方法要定义Object类中呢?
上面的方法只能在持有监视器(锁)的线程中操作,而监视器(锁)可以是任意对象,所以可以被任意对象调用的方法,要定义在Object类中。
其中上面的三个方法都使用在同步中,因为要在持有监视器(锁)的线程操作,但是同步才具有锁,所以只能使用在同步中。
其他常用方法:
interrupt():强制让线程恢复到运行状态中,清除冻结状态。
setDaemon():当正在运行的线程都是守护线程时,虚拟机退出,如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。
join():哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态。
setPriority(级别):设置线程的优先级,默认优先级是5。其中:线程优先级在1到10之间10个数。优先级越大被执行到的几率越大。
yield():可以使线程暂时释放执行权。
由于以前工作不曾使用过多线程技术,而通过最近一段时间的学习,可能学的东西还很片面。使用起来也很生疏,为了能更好的记忆和理解,我来把我最近一段时间学习的东西做一些整理。
首先,对于多线程技术,我们要明白如下的定义:
1.什么是进程?
进程就是正在进行中的程序,也就是对应一个应用程序在内存中所使用的空间。
2. 什么是线程?
线程就是进程中用于控制程序执行的控制单元(执行路径,执行情景),而一个进程中至少要有一个线程。
3.什么是多线程
多线程就是一个进程中有多个(两个或两个以上)执行路径(控制单元)。
注意:对于JVM,启动时,就有两个线程:
1.jvm的主线程,也就是执行mian函数的线程。
2.jvm的垃圾回收线程。
创建线程的目的:就是开启一条线程,去运行指定的代码(线程要执行的代码),并且和其他代码同时运行,以达到多个线程来实现多任务并发,提高程序的执行效率。
那么,我们怎么来创建线程呢?在Java中提供了两种线程的方式:
一.继承Thread类
注:java提供的对线程描述的类是Thread类, 创建线程对象的方法是构造函数,提供了要被线程执行的代码存储的位置是在run()里面。
步骤:
1.继承Thread类。
2.覆盖run方法,将线程要运行的代码定义其中。(要覆盖run方法的原因是,定义线程要运行的代码存储在run()里面)
3.创建Thread类的子类对象,其实就是在创建线程,调用start方法(其中,start()有两个功能:1.开启线程 2.执行run()方法)。
class ThreadDemo extends Thread// 方法局限性,不能再继承其他父类. { private String name; ThreadDemo(String name){ //super(name);Thread中的构造函数,可以给线程自定义名字。 this.name=name; } publicvoid run() { System.out.println(this.name+" Thread run..."); } publicstaticvoid main(String[]args){ ThreadDemo t1=new ThreadDemo("线程一"); ThreadDemo t2=new ThreadDemo("线程二"); t1.start(); //开启线程,调用run方法 t2.start(); } }
二.实现Runnable接口
注:下面的3种情况,就不可以再使用继承Thread类。
1.自定义的类中有多线程要运行的代码。但是该类有自己的父类。
2.要求程序具有功能的可扩展性。
3.多个功能使用一个共享数据。
那么,程序就提供了一个规则。通过实现Runnable接口。并将多线程要运行的代码存放在实现了Runnable类的子类的run方法中,然后调用对应的线程,进行处理。
步骤:
1,定义了实现Runnable接口。
2,覆盖接口的run方法。将多线程要运行的代码存入其中。
3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。
4,调用Thread对象的start方法。开启线程。
class ThreadDemo implements Runnable//实现Runnable接口 { publicvoid run(){ //Thread.currentThread() 获取当前线程的对象,相当于this //Thread类的getName() 获取当前线程的名称 System.out.println(Thread.currentThread().getName()+" Runnablerun..."); } publicstaticvoid main(String[] args){ ThreadDemo demo=new ThreadDemo(); Thread t1=new Thread(demo); Thread t2=new Thread(demo); t1.start(); //开启线程,调用run方法 t2.start(); } }
通过上面的知识点,我们可以知道:
1. 线程都有自己默认的名称,格式是:Thread-编号,该编号是从0开始。
2. 使用多线程的好处是:解决了多部分同时运行的问题。
3. 继承Thred类和实现Runnable接口这两种方式,都能创建线程,但是综合比较,创建线程建议使用实现Runnable接口这种方式,原因如下:
A:通过Thread继承的类,有局限性,不能再继承其他类.,这是java单继承的特性。
B:实现Runnable接口实现多线程,可以实现数据共享,根据接口可以多个实现,并且可以继承其他类,所以功能的扩展性能好。
线程的生命周期
线程可以划分为5个状态:创建、运行、阻塞(临时状态)、冻结、消亡。线程启动后,并不是一直占用cpu独自运行,而是多个线程在cpu中进行切换运行,所以多线程具备随机性。
临时状态的特点:具备了执行资格,但不具备执行权。
冻结状态的特点:放弃了执行资格。
注:启动线程使用的start方法,而不是run方法,调用start方法启动线程,系统会把该run方法当成线程执行体来处理。如果直接调用线程对象的run方法,就相当在执行普通的方法。
多线程运行有什么问题么?
由于多线程的运行具备随机性,就有可能会产生多线程的安全问题。
问题的产生的原因:
几个关键点:
1,多线程代码中有操作共享数据。
2,多条语句操作该共享数据。
当出现上面的情况时,就有可能出现下面问题:
有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。
解决的办法:
当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。
根据上面的思路,那么可以使用Java中提供的同步的原理:就是将部分操作功能数据的代码进行加锁,就可以保证在同一时间只允许一个线程进行操作。
使用同步的前提是:
1,必须是两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
同步可以分为两种表现形式:
一.同步代码块
synchronized(对象) //该对象可以是任何类的对象.如Object类的对象 { //...操作共享数据的语句 }二.同步函数
函数类型前+synchronized关键字,如下:
publicstaticsynchronizedvoid func(){ //...方法体 }它们的区别是:
1,同步代码块使用的锁是任意对象。
2,同步函数使用的锁是this。
注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。
同步的优点和缺点:
优点:解决了线程的安全问题
缺点:要判断同步锁,较为消耗资源;并且同步嵌套后,容易死锁。
死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
注:同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行
class Ticket implements Runnable { private inttick = 100; Object obj = new Object(); publicvoid run() { //show(); //同步方法 while(tick<=100) { synchronized(obj) //同步代码块 { if(tick>0) { System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); } } } } publicsynchronizedvoid show(){//同步方法 for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+"....同步函数_"+i); } } } class TicketDemo2 { publicstaticvoid main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); t1.start(); t2.start(); t3.start(); } }
线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
为了让线程协调运行,Object类提供了如下三个方法:
wait():导致当前线程处于冻结状态,被wait的线程,会被存储到线程池中,可以通过其他线程使用notify()方法或notifyAll()方法来唤醒该线程。
notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
notifAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。
注意:wait()和notify()等待和唤醒的锁,必须是同一个锁。
为什么这些操作线程的方法要定义Object类中呢?
上面的方法只能在持有监视器(锁)的线程中操作,而监视器(锁)可以是任意对象,所以可以被任意对象调用的方法,要定义在Object类中。
其中上面的三个方法都使用在同步中,因为要在持有监视器(锁)的线程操作,但是同步才具有锁,所以只能使用在同步中。
/* 对于多个生产者和消费者。 */ class Resource{ private String name; privateintcount = 1; privatebooleanflag = false; publicsynchronizedvoid set(String name){ /*为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。*/ while(flag) try{this.wait();}catch(Exception e){} this.name = name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); flag = true; /*为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。*/ this.notifyAll(); } publicsynchronizedvoid out(){ while(!flag) try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name); flag = false; this.notifyAll(); } } class Producer implements Runnable{ private Resource res; Producer(Resource res) { this.res = res; } publicvoid run() { while(true) { res.set("+商品+"); } } } class Consumer implements Runnable{ private Resource res; Consumer(Resource res){ this.res = res; } publicvoid run(){ while(true){ res.out(); } } } class ProducerConsumerDemo { publicstaticvoid main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con= new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); t1.start(); t2.start(); } }
其他常用方法:
interrupt():强制让线程恢复到运行状态中,清除冻结状态。
setDaemon():当正在运行的线程都是守护线程时,虚拟机退出,如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。
join():哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态。
setPriority(级别):设置线程的优先级,默认优先级是5。其中:线程优先级在1到10之间10个数。优先级越大被执行到的几率越大。
yield():可以使线程暂时释放执行权。
相关文章推荐
- 黑马程序员——Java基础——多线程的同步、死锁和等待唤醒机制
- 黑马程序员-java多线程
- 黑马程序员java笔记之二-----多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员----Java基础之多线程
- <黑马程序员>---java基础---多线程知识
- 黑马程序员__java之多线程下
- 黑马程序员-----java多线程总结*
- 黑马程序员_java多线程 单例设计
- 黑马程序员_java_多线程
- 黑马程序员_日记18_Java多线程(八)--生产者消费者问题JDK1.5特性
- 黑马程序员-----Java基础-----多线程的详解
- 黑马程序员-Java基础-多线程
- 黑马程序员_java08_多线程
- 黑马程序员——Java基础--- 多线程
- 黑马程序员——java基础——多线程(1)
- 黑马程序员_Java_多线程
- [黑马程序员]--Java语言基础-多线程
- 黑马程序员——java基础拾遗之多线程(一) 多线程的两种实现
- 黑马程序员__JAVA基础__多线程