多线程
来源:互联网 发布:linux mkdir创建目录 编辑:程序博客网 时间:2024/05/22 04:52
- 多线程概述
- 进程:正在进行中的程序。
- 线程:进程中的独立控制单元。线程控制着进程的执行。
- 一个进程中至少有一个线程。
- jvm启动时有一个java.exe进程,该进程至少有一个主线程负责程序的执行,这个线程运行的代码存在于main函数中,该线程称之为主线程。
- jvm启动时,除了主线程,还有负责垃圾回收的线程。
- 多线程程序:有不止一个线程(执行路径)的程序就是多线程程序,如迅雷。
- 多线程存在的意义:
- 使程序中不同部分的代码产生“同时运行”的效果。
- 线程的创建方式
- 继承Thread类
- 创建线程的步骤:
- 定义类继承Thread。
- 覆写Thread类中的run方法。
- 目的:将自定义代码存储在run()方法中,让线程运行。
- 调用线程的start方法
- 该方法有两个作用:启动线程,调用run方法。
- 如果直接调用run方法,相当于仅仅是对象调用普通方法。线程虽然创建了,但是并没有运行。
- Thread类
- Thread类用于描述线程。
- Thread类中的run()方法用于存储线程要运行的代码。
- start()方法用于开启线程,并执行该线程的run()方法。
- 练习
- 需求:创建两个线程,和主线程交替运行。
- 代码:
package cn.itcast.heima;public class Test {public static void main(String[] args) {SubThread st1 = new SubThread();SubThread st2 = new SubThread();st1.start();st2.start();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "run..." + i);}}}class SubThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "run..." + i);}}}
打印结果:
- 实现Runnable接口
- 创建线程的步骤:
- 定义类实现Runnable接口。
- 覆盖Runnable接口中的run()方法。
- 创建类Thread的线程对象。
- 将Runnable子类对象作为实际参数传递给Thread类的构造函数。
- 通过Thread对象的start()方法开启线程并调用Runnable接口子类的run()方法。
- 定义类实现Runnable接口。
- 优势:
- 避免了单继承的局限性。
- 可以创建多个线程共享Runnable子类对象的数据,避免了共享数据定义为静态。
- 例子:
- 需求:简单的卖票程序。实现多个窗口同时卖票。
- 如果按照继承Thread类的方式实现多线程,代码如下:
package cn.itcast.heima;public class Test {public static void main(String[] args) {//开放四个窗口售票Ticket t1 = new Ticket();Ticket t2 = new Ticket();Ticket t3 = new Ticket();Ticket t4 = new Ticket();t1.start();t2.start();t3.start();t4.start();}}class Ticket extends Thread{private static int ticket = 20;//总共20张票@Overridepublic void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + "...sale:" + ticket--);}}}
打印结果:
这种方式需要将票数定义为静态成员。
缺陷:ticket成员变量生命周期过长,且Ticket类不能继承其它类。 - 可以将其改为实现Runnable接口的方式实现多线程,代码如下:
package cn.itcast.heima;public class Test {public static void main(String[] args) {Ticket t = new Ticket();//开放四个窗口售票Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();t2.start();t3.start();t4.start();}}class Ticket implements Runnable{private int ticket = 20;//总共20张票@Overridepublic void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + "...sale:" + ticket--);}}}
打印结果:
这种方式避免了将ticket成员变量定义为static,并且避免了单继承的局限性。 - 实际开发中,建议使用实现Runnable接口的方式实现多线程。
- 两种方式区别:
- 继承Thread类:线程代码存放在Thread子类的run()方法中。
- 实现Runnable接口:线程代码存放在Runnable接口子类的run()方法中。
- 多线程的特性:随机性
- 对于多线程程序,每一次的运行结果都不相同。
- 因为多个线程都需要获取CPU的执行权,CPU执行到哪个线程,哪个线程就运行。
- 在某一时刻,只能有一个线程在运行,CPU在做着快速的切换,以达到看上去是同时运行的效果。
- 线程的五种状态
- 线程的五种状态:被创建,运行,冻结,临时(阻塞),消亡。关系如下图:
- 获取线程对象以及名称
- 线程有默认的名称:Thread-编号(从0开始)。
- currentThread():获取当前线程对象(静态)。
- getName():获取线程名称。
- 设置线程名称:setName()或者构造函数。
- 多线程的安全问题:同步
- 问题原因:
- 多个线程在操作同一个共享数据时,一个线程对多条语句只执行了一部分,另一个线程就参与进来,导致共享数据出现问题。
- 多个线程在操作同一个共享数据时,一个线程对多条语句只执行了一部分,另一个线程就参与进来,导致共享数据出现问题。
- 解决办法:
- 一个线程在执行过程中,其它线程不可以参与执行。
- 一个线程在执行过程中,其它线程不可以参与执行。
- Java对于多线程的安全问题提供了专业的解决方案:同步代码块。
- 同步代码块格式:
- 判断需要被同步的代码:
- 操作共享数据的代码就是需要被同步的代码。
- 操作共享数据的代码就是需要被同步的代码。
- 同步代码块的原理:
- 对象如同锁。持有锁的线程才可以进入同步代码块执行语句。不持有锁的线程即使获得了CPU的执行权,也不能进入同步代码块执行语句。
- 火车上的卫生间的例子。
- 对象如同锁。持有锁的线程才可以进入同步代码块执行语句。不持有锁的线程即使获得了CPU的执行权,也不能进入同步代码块执行语句。
- 同步的前提:
- 同步需要两个或者两个以上的线程。
- 多个线程使用的是同一个锁。
- 同步需要两个或者两个以上的线程。
- 同步的好处:
- 解决了多线程的安全问题。
- 同步的弊端:
- 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形 中会降低程序的运行效率。
- 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形 中会降低程序的运行效率。
- 同步函数
- 格式:
在函数上加上synchronized修饰符即可。 - 同步函数使用的锁是this。要保证同步代码块和同步函数用的是同一个锁,同步代码块的参数必须是this。
- 格式:
- 静态同步函数
- 静态同步函数的锁是该方法所在类的字节码文件对象:类名.class。
- 静态同步函数的锁是该方法所在类的字节码文件对象:类名.class。
- 死锁
- 死锁的出现:同步中嵌套同步。
- 实际开发中,要避免出现死锁问题。
- 线程间通信
- 概念
- 线程间通信就是多个线程在操作同一个资源,但是操作的动作不同。
- 等待唤醒机制
- 多线程操作共享数据时要考虑使用等待唤醒机制。
- 等待线程存放在线程池中,notify()通常唤醒线程池中的最先等待的线程,notifyAll()唤醒线程池中的所有线程。
- wait(),notify(),notifyAll()都要用在同步中,因为要对持有监视器(锁)的线程操作。
- wait(),notify(),notifyAll()都是Object类中的方法,因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁。
一个被等待的线程,只能被持有同一个锁的线程唤醒,也就是说,等待和唤醒必须是同一个锁。
为锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。 - wait和sleep比较:
- wait():释放cpu执行权,释放锁。
- sleep():释放cpu执行权,不释放锁。到达休眠时间后线程将继续执行,直到完成。若在休眠期另一线程中断该线程,则该线程退出。
- wait():释放cpu执行权,释放锁。
- 生产者消费者例子
- 需求:生产者和消费者分别创建两个线程,保证生产者进程和消费者进程能够交替执行。
- 代码:
package cn.itcast.heima;public class Test {public static void 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(pro);//生产线程Thread t3 = new Thread(con);//消费线程Thread t4 = new Thread(con);//消费线程t1.start();t2.start();t3.start();t4.start();}}//商品类class Resource{private String name;//商品名称private int count = 1;//商品编号private boolean flag = false;//判断商品是否还在//生产商品public synchronized void set(String name){//如果商品还在,生产线程等待while (flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}this.name = name + "---" + count++;System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);flag = true;//商品生产出来了notifyAll();//唤醒所有等待线程}//消费商品public synchronized void out(){//如果商品没了,消费线程等待while (!flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "...消费者......" + this.name);flag = false;//商品被拿走了notifyAll();//唤醒所有等待线程}}//生产者类class Producer implements Runnable{private Resource res;public Producer(Resource res) {this.res = res;}@Overridepublic void run() {while (true) {res.set("商品");}}}//消费者类class Consumer implements Runnable{private Resource res;public Consumer(Resource res) {this.res = res;}@Overridepublic void run() {while (true) {res.out();}}}
打印结果: - 需要定义while判断标记flag:
- 原因:让被唤醒的线程再一次判断标记,否则会导致所有线程都在等待。
- 并且为了避免线程全部冻结,需要在解锁之前唤醒所有线程:
- 原因:需要唤醒对方线程。因为只用notify()容易出现只唤醒本方线程的情况,导致程序中所有线程都挂起。
- 原因:需要唤醒对方线程。因为只用notify()容易出现只唤醒本方线程的情况,导致程序中所有线程都挂起。
- JDK5.0升级版
- 定义了Lock接口,代替了同步函数和同步代码块。
- 定义了Condition接口,代替了Object中的wait(),notify(),notifyAll()方法。Condition对象可以通过Lock接口子类的newCondition()方法获取。
- 一个Lock可以对应多个Condition对象,该方式可以对指定的某个Condition对象对应的线程进行等待和唤醒处理,而不需要唤醒所有线程。
- 生产者消费者例子的JDK5.0升级版代码:
package cn.itcast.heima;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Test {public static void 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(pro);//生产线程Thread t3 = new Thread(con);//消费线程Thread t4 = new Thread(con);//消费线程t1.start();t2.start();t3.start();t4.start();}}//商品类class Resource{private String name;//商品名称private int count = 1;//商品编号private boolean flag = false;//判断商品是否还在private Lock lock = new ReentrantLock();//定义锁对象private Condition condition_pro = lock.newCondition();//定义生产线程的Condition对象private Condition condition_con = lock.newCondition();//定义消费线程的Condition对象//生产商品public void set(String name){try{lock.lock();//上锁//如果商品还在,生产线程等待while (flag) {try {condition_pro.await();//生产线程等待} catch (InterruptedException e) {e.printStackTrace();}}this.name = name + "---" + count++;System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);flag = true;//商品生产出来了condition_con.signal();//唤醒消费线程}finally{lock.unlock();//解锁}}//消费商品public void out(){try {lock.lock();//上锁//如果商品没了,消费线程等待while (!flag) {try {condition_con.await();//消费线程等待} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ "...消费者......" + this.name);flag = false;//商品被拿走了condition_pro.signal();//唤醒生产线程} finally {lock.unlock();//解锁}}}//生产者类class Producer implements Runnable{private Resource res;public Producer(Resource res) {this.res = res;}@Overridepublic void run() {while (true) {res.set("商品");}}}//消费者类class Consumer implements Runnable{private Resource res;public Consumer(Resource res) {this.res = res;}@Overridepublic void run() {while (true) {res.out();}}}
打印结果:
- 线程生命控制
- 停止线程
- stop()方法已经过时。
- 如何停止线程:
- 只有一种方法,run()方法结束。
- 开启多线程运行,运行代码通常都是循环结构。只要控制住循环,就可以结束run()方法,从而结束线程。
- 只有一种方法,run()方法结束。
- Thread类提供的interrupt()方法,可以中断线程的冻结状态。
当没有指定的方式让冻结的线程恢复到运行状态时,就需要对冻结状态进行清除。强制线程恢复到运行状态,并在异常处理中通过改变标记让线程结束。 - 守护线程
- 守护线程相当于后台线程,当前台线程全部结束后,后台线程自动结束,JVM退出。
- 如果一个线程的运行依赖于另一个线程的运行,当第一个线程结束时,希望第二个线程也随之结束,则第二个线程可以标记为守护线程。
- 设置守护线程的方法:
- 在线程开启前调用setDaemon(true)方法。
- 在线程开启前调用setDaemon(true)方法。
- 联合线程
- 当A线程执行到了B线程的join()方法时,A就会等待,直到B线程都执行完,A线程才会继续执行。
- join()方法可以用来临时加入线程执行。
- 优先级和yield方法
- 优先级
- 线程被哪个线程开启,就属于哪个线程组。
- 线程默认优先级为5。优先级取值范围1-10。
- Thread类的toString()方法返回线程名称、优先级和线程组。
- 优先级代表抢占cpu的频率。
- setPriority方法:改变线程优先级。
- yield方法
- yield方法:暂停当前正在执行的线程对象,并执行其他线程。
- 可以实现当前线程执行完后让出CPU执行权,达到不同线程交替执行的效果。
- yield方法:暂停当前正在执行的线程对象,并执行其他线程。
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- VC开发支持ActiveX控件工程
- Devexpress xtraGrid 右键菜单位置控制
- 心态决定成败2
- 如何实现线程的开启、暂停和停止
- Wince内存泄露检测工具Application Verifier的使用和如何快速定位泄露语句(二)
- 多线程
- java反射详解
- 一道逻辑题(关于张村李村说谎的)java实现
- java.lang.NoClassDefFoundError: javax/el/ExpressionFactory异常
- QT常用的类和函数总结
- 14周项目二:成绩处理
- 冒泡法排序
- JS中常用的正则表达式分享
- 项目1 - 数组大折腾