使用Java实现面向对象编程——第七章 多线程
1、进程:是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间);
Eg:用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。当用户再次点击左面的IE浏览器,又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。目前操作系统都支持多进程。
◆注;用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。
◆进程的特点:进程是系统运行程序的基本单位;
每一个程序都有自己独立的一块内存空间、一组系统资源;
每一个进程的内部数据和状态都是完全独立的;
2、线程:是进程中执行元算的最小单位;可以完成一个独立的顺序控制流程;每个进程中,必须至少建立一个线程(主线程)来作为程序运行的入口点;
◆附加:
线程:是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源, 但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程, 同一进程中的多个线程之间可以并发执行。 |
|
线程: 1、线程是轻量级的进程 2、线程没有独立的地址空间(内存空间) 3、线程是由进程创建的(寄生在进程) 4、一个进程可以拥有多个线程-->这就是我们常说的多线程编程 |
线程的几种状态: a、创建状态(new) b、就绪状态(Runnable) c、运行状态(Running) d、阻塞状态(Blocked) e、死亡状态(Dead) |
3、多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”;多个线程交替占用CPU资源,而非真正的并行执行
●多线程好处:
a) 充分利用CPU的资源
b) 简化编程模型
c) 带来良好的用户体验
4、JAVA中实现多线程:
●Thread类:Java提供了java.lang.Thread类支持多线程编程
◆ Thread类常用的方法:
构造方法 |
说 明 |
Thread() |
分配新的Thread()对象 |
Thread(Runnable target) |
分配新的Thread()对象。tarage为run()方法被调用的对象; |
Thread(Runnable target,String nasme) |
分配新的Thread()对象。tarage为run()方法被调用的对象;name为新线程的名称 |
void run() |
执行任务操作的方法 |
void start() |
使该线程开始执行,JAVA虚拟机调用线程的run()方法 |
static void sleep(long millis) |
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
String getName() |
返回线程的名字 |
Int getPriority() |
返回线程的优先级 |
void setPriority(int newPriority) |
更改线程的优先级 |
static Thread currentThread() |
返回当前正在执行的线程对象的引用 |
void join() |
等待该线程终止 |
static void yield() |
暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() |
中断线程 |
boolean isAlive() |
测试线程是否处于活动状态 |
5、主线程:在JAVA线程启动时,一个线程立即运行该线程称为程序的主线程;每个线程至少有一个主线程;他是程序开始时就执行;
◆ main()方法即为主线程入口
◆产生其他子线程的线程
◆必须最后完成执行,因为它执行各种关闭动作
★主线程可以由一个Thread对象控制,需要调用方法Thread.currentThread()获得他的一个引用,
语法:static Thread currentThread()
Eg:
public static void main(String args[]) {
Thread t= Thread.currentThread(); //获得主线程对象
System.out.println("当前线程是: "+t.getName());
t.setName("MyJavaThread"); //设置线程名
System.out.println("当前线程名是: "+t.getName()); //获取线程名
}
◆在Java中创建线程的两种方式:
★继承java.lang.Thread类:继承Thread类,并重写run函数
★实现java.lang.Runnable接口:实现Runnable接口,并重写run函数
◆使用线程的步骤:
★定义一个线程:同时指明这个线程所要执行的代码;
★创建线程对象;
★启动线程
★终止线程
6、继承Thread类创建线程:
●步骤: ★定义MyThread类继承Thread类 ★重写run()方法,编写线程执行体 ★创建线程对象,调用start()方法启动线程 |
Eg:
public class MyThread extends Thread{ //继承Thread类 //重写run()方法 public void run(){ //run()方法中编写线程执行的代码 for(int i=1;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); //启动线程 } |
多个线程交替执行,不是真正的“并行” 线程每次执行时长由分配的CPU时间片长度决定 Eg:MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); |
|
直接调用run()和start()区别: start():启动线程; run():调用实例方法; |
|
|
7、实现Runnable接口创建线程:
●步骤: ★定义MyRunnable类实现Runnable接口,并实现Runnable接口的run()方法在run()方法中实现输出数据; ★创建MyRunnable类的对象myRunnable; ★创建一个Thread类的对象myThread,将myRunnable对象作为Thread类构造方法的参数传入; ★调用myThread对象的start()方法启动线程; |
|
Eg:
public class MyRunnable implements Runnable{ //实现Runnable接口 public void run(){ for(int i=1;i<100;i++){ // run()方法中编写线程执行的代码 System.out.println(Thread.currentThread().getName()+":"+i); } } }
public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); //创建线程对象 Thread myThread = new Thread(myRunnable); thread.start(); //启动线程 } |
|
■用实现Runnable接口的特点: 1、用实现Runnable接口的方法创建对象可以避免java单继承机制带来的局限; 2、用实现Runnable接口的方法,可以实现多个线程共享同一段代码(数据);因此建议大家如果你的程序有同步逻辑需求,则使用Runnable的方法来创建线程。 |
|
比较两种创建线程的方式:推荐使用实现Runnable接口方式创建线程 |
|
★继承Thread类: 编写简单,可直接操作线程 适用于单继承 |
★实现Runnable接口: 避免单继承局限性 便于共享资源 |
附加:从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别, ★从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口,区别如下: 1、尽可能使用实现Runnable接口的方式来创建线程 2、在使用Thread的时候只需要new一个实例出来,调用start()方法即可以启动一个线程, 如: Thread test=new Thread(); test.start(); 3、在使用Runnable的时候需要先new一个实现Runnable的实例,之后用Thread调用,如: Test implements Runnable Test t=new Test(); Thread test=new Thread(t); tset.start(); |
|
注意:不管是通过继承Thread,还是通过实现Runnable接口创建线程,它们的一个对象只能启动(即:start())一次。否则就会有(IllegaiIThreadStateException)异常抛出。 |
8、线程的状态:
1. 创建状态:在程序中用构造方法创建了一个线程对象后,新的线程对象就处于创建状态,此时,它已经获取了相应的资源,但还没有处于可运行状态,这时可以通过Thread类的方法来设置线程对象的属性,
如:设置线程名(setName())、设置线程优先级(setPriority())等。
2. 就绪状态:线程创建之后,就可以通过调用start()方法启动线程,即进入就绪状态。此时,线程将进入线程队列排队,等待CPU资源,这表明它已经具备了运行条件,在未获得CPU资源时,仍不能真正执行。
◆举例来说,去医院看病,某主任的专家号每天只有20个,挂上号的病人还需在分诊处等待叫号。这里每个挂到专家号的病人可以看成一个就绪状态的线程。
3.运行状态:当就绪状态的线程获得CPU资源时,即可转入运行状态,执行的run()方法。对于只有一个CPU的机器而言,任何时刻只能有一个处于运行状态的线程占用CPU,即获得CPU资源。
◆延续上面医院看病的例子,被叫到的病人才能真正就诊,而每个主任专家在一个时刻只能为一个病人看病。
4. 阻塞状态:一个正在运行的线程因某种原因不能继承运行时,进入阻塞状态。阻塞状态是一种“不可运行”的状态,而处于这种状态的线程在得到一个特定的事件之后会转回可运行状态。
◆举例来说,轮到小张看病了,医生为查明原因要求他去做个化验,医生得到化验结果后才能继续诊断,如果把医生给小张看病看作一个线程,该线程此时即处于阻塞状态。
●可能使线程暂停执行的条件:
★线程优先级比较低,因此它不能获得CPU资源。
★使用sleep()方法使线程休眠。
★通过调用wait()方法,使线程等待。
★通过调用yield()方法,线程显式出让CPU控制权。
★线程由于等待一个文件I/O事件被阻塞。
5.死亡状态:一个线程的run()方法运行完毕,线程则进入死亡状态。处于死亡状态的线程不具有继承运行的能力。
9、线程调度:线程调度指按照特定机制为多个线程分配CPU的使用权;
●线程优先级:线程靠抢CPU时间片而执行,谁抢的多谁利用CPU的时间就多也就执行得快。而决定这个争抢能力的就是线程的优先级;
●线程优先级由1~10表示,1最低,10代表优先级最高,默认优先级为5。
这些优先级对应一个Thread类的公用静态常量;优先级高的线程获得CPU资源的概率较大;
线程的优先级可以通过getPriority()方法获取,setPriority(int grade)方法更改,参数表示要设置的优先级,他必须是1~10的整数;
MAX_PRORITY(最大优先级),MIN_PRORITY(最小优先级),NORM_PRORITY(默认优先级);
●线程调度的方法:
方法 |
说 明 |
setPriority(int newPriority) |
更改线程的优先级 |
static void sleep(long millis) |
在指定的毫秒数内让当前正在执行的线程休眠 |
void join() |
等待该线程终止 |
static void yield() |
暂停当前正在执行的线程对象,并行其他线程 |
void interrupt() |
中断线程 |
boolean isAlive() |
测试线程是否处于活动状态 |
●线程休眠:在一个程序中一个线程允许进行暂时休眠,直接调用Thread sleep()方法即可实现线程的休眠;
sleep()方法语法定义:public static void sleep(long millis)
◆让线程暂时睡眠指定时长,线程进入阻塞状态;
◆睡眠时间过后线程会再进入可运行状态;
Eg: public class Wait {
public static void bySec(long s) {
for (int i = 0; i < s; i++) {
System.out.println(i + 1 + "秒");
try {
Thread.sleep(1000); //线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}}}}
★millis为休眠时长,以毫秒为单位
★调用sleep()方法需处理InterruptedException异常
●线程的强制运行:使当前线程暂停执行,等待其他线程结束后再继续执行本线程:
它的三个重载语法语法:
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
◆millis:以毫秒为单位的等待时长
◆nanos:要等待的附加纳秒时长
◆需处理InterruptedException异常
Eg:
public static void main(String[] args) { Thread temp = new Thread(new MyThread()); temp.start(); for(int i=0;i<20;i++){ if(i==5){ try { temp.join(); //阻塞主线程,子线程强制执行 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"运行:"+i); } //省略代码…… } |
结果:
|
●线程的礼让:
◆yield()方法可以暂停当前线程,允许其他具有相同优先级的线程获得运行机会;
◆该线程处于就绪状态,不转为阻塞状态;此时系统选择其他想相同或更高优先级线程允许,若无其他相同或更高优先级线程,则该线程继续运行;
语法:public static void yield()
注:使用yield()的线程礼让只是提供一种可能,但是不能保证一定会实现礼让;
Eg:
|
10、线程的同步:当多个线程共同操纵同一个共享资源的时候,会导致数据不一致的情况;当多个线程共同操纵同一个共享资源的时候,一个线程未完成操作的时候,其他线程修改的数据,会导致数据不安全的情况;
●多线程共享数据引发的问题:
●同步方法:
◆使用synchronized修饰的方法控制对类成员变量的访问 语法:
◆synchronized(同步关键字)就是为当前的线程声明一个锁
|
◆ syncObject为需同步的对象,通常为this,效果与同步方法相同
|
◆多个并发线程访问同一资源的同步代码块时注意: 1、当多个并发线程访问同一个对象object的synchronized(this)同步代码块时,同一时刻只能有一个线程得到执行,其他线程必须等待当前线程执行完毕之后才能执行该代码块; 2、当一个线程访问一个object的synchronized(this)同步代码块时,其他线程对object中所有其他synchronized(this)同步代码块的访问被阻塞,即该线程获得这个object的对象锁,其他线程对该object对象所有同步代码部分的访问被暂时阻塞; 3、当一个线程访问一个object的synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码; 综上所述:synchronized就是为当前线程声明一个锁,获得这个锁的线程可以执行代码块里的指令,其他的线程只能等待解锁,然后才能执行相同的操作; |
◆同步代码块的应用场景:假设某线程是非线程安全的,而且该类是第三方创建的或者是从内置库导入的,所以不能获取他的源代码,这样,无法在相关方法前面加synchronized修饰符; 怎样使该类的一个对象同步化? 解决方案:只需将对调用该方法的代码放入一个synchronized块内就可以了; ◆关键代码: synchronized(同步对象){ //调用第三方非同步方法的代码 } |
11、线程安全的类型:
查看ArrayList类的add()方法定义: public boolean add(E e) { ensureCapacityInternal(size + 1);// 集合扩容,确保能新增数据 elementData[size++] = e; //在新增位置存放数据 return true; } |
||||||||||||||||
ArrayList类的add()方法为非同步方法; 当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题 |
||||||||||||||||
ArrayList为非线程安全的类型 |
||||||||||||||||
|
12、常用类型对比:
◆Hashtable && HashMap
◆Hashtable:继承关系,实现了Map接口,Hashtable继承Dictionary类
特点:线程安全,效率较低
键和值都不允许为null
◆HashMap:继承关系:实现了Map接口,继承AbstractMap类
特点: 非线程安全,效率较高
键和值都允许为null
◆ StringBuffer && StringBuilder:前者线程安全,后者非线程安全
13、附加1:死锁问题:
* 死锁:两个线程都在等待对方先完成,造成程序的停滞; * 产生死锁的条件: * 两个或两个以上的线程在活动; * 某个线程拿到一个锁以后,还想拿第二个锁,造成锁的嵌套? *解决方法: * 当前线程先释放自己的锁? * 尽量减少同步方法或同步代码块的嵌套? |
|
Eg:两个小孩互换玩具; |
|
Eg:此示例不会产生死锁; /** * 模拟死锁 */ public class Test { public static void main(String[] args) { Thread tang=new Thread(new Tang()); Thread dou=new Thread(new Dou()); tang.start(); dou.start(); } } /** * 堂堂 */ class Tang implements Runnable{ Object bobby=new Object();//芭比娃娃 Object duck=new Object();//玩具 @Override public void run() { synchronized (bobby) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (duck) { } System.out.println("堂堂把芭比娃娃给豆豆玩!"); } } } /** * 豆豆 */ class Dou implements Runnable{ Object bobby=new Object();//芭比娃娃 Object duck=new Object();//玩具 @Override public void run() { synchronized (duck) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (bobby) { } System.out.println("豆豆把玩具鸭给堂堂玩"); } } } |
Eg:此示例产生了死锁 /** * 模拟死锁(产生了死锁) */ public class Test1 { public static void main(String[] args) { Object bobby=new Object(); Object duck=new Object(); Thread tang=new Thread(new Tangtang(bobby,duck)); Thread dou=new Thread(new Doudou(bobby,duck)); tang.start(); dou.start(); } } /** * 堂堂 */ class Tangtang implements Runnable{ Object bobby;//芭比娃娃 Object duck;//玩具 //构造函数 public Tangtang(Object bobby,Object duck) { super(); this.bobby=bobby; this.duck=duck; } @Override public void run() { synchronized (bobby) { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (duck) { } System.out.println("堂堂把芭比娃娃给豆豆玩!"); } } } /** * 豆豆 */ class Doudou implements Runnable{ Object bobby;//芭比娃娃 Object duck;//玩具 //构造函数 public Doudou(Object bobby,Object duck) { super(); this.bobby=bobby; this.duck=duck; } @Override public void run() { synchronized (duck) { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (bobby) { } System.out.println("豆豆把玩具鸭给堂堂玩"); } } } |
14、附加2:生产者和消费者的问题: 生产者不断生产,消费者不断取走生产者的产品;生产者生产出信息之后将其放到一个区域中,之后消费者从此区域取走数据;
Eg:一个轮流录入电影和读取电影信息的程序 变形金刚——一部科幻电影 神偷奶爸——一部3D动画片 分析: 生产者:录入信息; 消费者:读取信息; |
|
/** * 电影类 */ public class Movie { private String name;//电影的名称 private String info;//电影的描述
public String getName() { return name; } public void setName(String name) { this.name = name; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } }
|
/** * 生产者 */ public class Producer implements Runnable { private Movie movie=null; private boolean flag=false; //构造函数 public Producer(Movie movie) { super(); this.movie=movie; } @Override public void run() { //循环录入50遍电影数据,两部电影交替录入 for (int i = 0; i < 50; i++) { if(flag) { this.movie.setName("变形金刚"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.movie.setInfo("一部科幻电影!"); flag=false; }else { this.movie.setName("神偷奶爸"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.movie.setInfo("一部3D动画电影"); flag=true; } } } } |
/** * 测试类 */ public class Test { public static void main(String[] args) { Movie movie=new Movie(); //生产者线程对象 Thread producer=new Thread(new Producer(movie)); //消费者线程对象 Thread consumer=new Thread(new Consumer(movie)); producer.start(); consumer.start(); } } |
|
/** * 消费者 */ public class Consumer implements Runnable { private Movie movie=null; //构造函数 public Consumer(Movie movie) { super(); this.movie=movie; } @Override public void run() { //循环显示50次电影信息 for (int i = 0; i <50; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.movie.getName()+"— "+this.movie.getInfo()); } } } |
输出结果:(会出现电影名与类型匹配错误的情况(生产与消费不同步))
|
/** * 生产者 */ public class Producer implements Runnable { private Movie movie=null; private boolean flag=false; //构造函数 public Producer(Movie movie) { super(); this.movie=movie; } @Override public void run() { //循环录入50遍电影数据,两部电影交替录入 for (int i = 0; i < 50; i++) { if(flag) { this.movie.set("变形金刚", "一部科幻电影"); flag=false; }else { this.movie.set("神偷奶爸", "一部3D动画电影"); flag=true; } } } } |
/** * 消费者 */ public class Consumer implements Runnable { private Movie movie=null; //构造函数 public Consumer(Movie movie) { super(); this.movie=movie; } @Override public void run() { //循环显示50次电影信息 for (int i = 0; i <50; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } this.movie.get(); } } }
|
package lianxi0Producer; /** * 电影类 */ public class Movie { private String name;//电影的名称 private String info;//电影的描述
public String getName() { return name; } public String getInfo() { return info; } //同步写入 public synchronized void set(String name,String info) { this.name=name; &nbs
赞 (0)
打赏
微信扫一扫
相关文章:版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。 |
推荐阅读
-
使用Java实现面向对象编程——JAVA关键字与保留字说明及使用
-
java 使用面向对象方式实现录入学生信息,取出成绩最大值、最小值、平均值、对其进行排序
-
使用Java实现面向对象编程——第七章 多线程
-
超级玛丽 Super Mario java基础小游戏:基于JAVA面向对象实现的超级马里奥(Super Mario)游戏(简单小游戏,仅仅使用Java面向对象基础实现(附上源码))
-
面向对象编程 —— java实现函数求导
-
阿里Java学习路线:阶段 1:Java语言基础-Java面向对象编程:第30章:链表的定义与使用:课时134:链表实现简介
-
使用Java实现面向对象编程——第七章 多线程
-
超级玛丽 Super Mario java基础小游戏:基于JAVA面向对象实现的超级马里奥(Super Mario)游戏(简单小游戏,仅仅使用Java面向对象基础实现(附上源码))
-
面向对象编程 —— java实现函数求导
发表评论