黑马程序员--多线程
来源:互联网 发布:二维数组变成一维数组 编辑:程序博客网 时间:2024/05/18 01:10
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
注:视频来源,毕向东老师的 JAVA 基础视频。
一、线程和进程
1)进程:是一个在执行中的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
2)线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。
3) 理解:一个进程中,至少有一个线程。在JVM 启动的时候会有一个进程叫java.exe该进程中,至少有一个线程,在负责 java 程序的执行,而且这个线程,运行的代码存在于 main 方法中,该线程称之为主线程。其实,JVM 中更细节的说明了虚拟机不止一个线程,包含了一个主线程和一个负责控制垃圾回收机制的线程。通过API的查找,java已经提供了对线程这类事物的描述,就是 Thread 类。
4)多线程的随机性:因为多个线程都获取 cpu 的执行权。 cpu 执行到谁,谁就运行。但是要明确的是:在某一时刻,只能有一个程序在运行。(多核除外。)CPU 在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行形容为在互相抢夺 CPU 的资源(执行权)我们可以形象的把多线程的运行形容为在互相抢夺 CPU 的资源(执行权)这就是多线程的特性,谁抢到谁执行。至于执行多长时间。CPU 说的算。
5)覆盖 run 方法的说明:Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run 方法。也就是说 Thread 类中 run 方法,用于存储线程需要运行的代码。所以说自定义线程,就是把代码放在run方法中。
6)线程都有自己的默认名称。Thread-编号,该编号从0开始。
currentThread:获取当前线程对象,这是一个静态的线程可以直接调用。
Ps:static Thread currentThread()
getName():获取线程的名称。
setName():设置线程名称,setName或者通过构造函数。
二、线程的生命周期
1)运行过程:
被创建: start()–>运行– sleep(time) –> 冻结(:放弃执行资格)
运行– sleep时间到 <– 冻结(:放弃执行资格)
运行– wait() –> 冻结(:放弃执行资格)
运行– notify() <– 冻结(:放弃执行资格)
2)消亡:stop() ,就是run方法中的结束。
3) 说明:没有资格执行的状态,冻结状态。当wait状态的时候,线程冻结了,没办法自动重启,这时候可以使用notify方法唤醒线程。
4)临时阻塞:具备运行资格,但是没有执行权。
线程的图解
三、创建线程的两种方式
总结两种方式的区别:
继承 Thread :线程代码存放在 Thread 子类 的 run 方法中。
实现 Runnable:线程代码存放在接口的子类的 run 方法中。
方式一:继承 Thread 类。
步骤:
1、定义类继承 Thread 。
2、复写 Thread 类中的 run 方法。目的:将自定义的代码存储在 run 方法中,让线程运行。
3、调用线程的 start 方法。该方法有两个作用,启动线程,调用run方法。
方法二:实现 Runnable 接口。
步骤:
1、定义类实现 Runnable 接口。
2、覆盖 Runnable 接口中的 Run 方法。将线程要运行的方法存放在该 run 方法中。
3、通过 Thread 类建立线程对象。
4、将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
Ps:为什么要将 Runnable接口的子类对象传递给 Thread 的构造函数。因为,自定义的 run 方法所属的对象是Runnable 接口的子类对象,所以要让线程去执行指定对象的 run 方法。就必须明确该 run 方法所属的对象。
5、调用 Thread 的 start 方法开启线程,并调用 Runnable接口子类的 run 方法。
简单的卖票程序:
package fuxi;/** * *@author XiaLei */public class Day11Test { public static void main(String[] args) { ThreadTest t1 = new ThreadTest();// ThreadTest t2 = new ThreadTest();// ThreadTest t3 = new ThreadTest();// ThreadTest t4 = new ThreadTest();// t1.start();// t2.start();// t3.start();// t4.start();被注释的是第一种方法,创建ThreadTest类继承Thread用其子类创建对象 /* 直接调用 run 方法,就没有开启线程。 start方法有两个作用:1.调用run方法2.开启线程 t1.run(); t2.run(); */ new Thread(t1).start(); new Thread(t1).start(); new Thread(t1).start(); new Thread(t1).start(); //实现线程第二种方法,实现Runnable接口。 }}//class ThreadTest extends Thread{// private int tickets = 100;// public void run(){// // while(true){// if (tickets>0){// System.out.println(currentThread().getName()+"sale"+tickets--); //显示线程名及余票数 // // }// }// }//}class ThreadTest implements Runnable{ private int tickets = 100; public void run(){ while(true){ synchronized(this){ //给程序加同步,即锁 。 if (tickets>0){ try{Thread.sleep(10);}catch(Exception e){}//因为sleep方法有异常声明,所以这里要对其进行处理 System.out.println(Thread.currentThread().getName()+"sale"+tickets--); } } } }}
四、多线程的安全问题
java 对于多线程的安全问题,提供了专业的解决方式。就是同步代码块。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
1)最常用的线程使用方法:implements,就是用实现的方法写线程Thread。
2)运行时出现安全问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。另一个线程参与了进来执行,导致了共享
数据的错误。
3)解决方法:在对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其它线程不可以参与执行。java 对于多线程的安全问题,提供了专业的解决方式。就是同步代码块。
4)同步的前提:
1、必须要有两个或者两个以上的线程才需要synchroized锁上。
2、必须是多个线程同时使用一个锁。
5)实现方式:
synchronized(对象){
需要被同步的代码//同步带共享操作的数据,如if的判断tick开始。
}
对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程,即使获取了 CPU 的执行权,也进不去,因为没有获取锁。
package fuxi;/** * 需求:银行有一个金库。有两个储户分别存300员,每次存一百。存3次。 * 目的:该程序是否有安全问题:有的 * 如何找问题: * 1、明确那些代码是多线程运行的代码。 * 2、明确共享数据。 * 3、明确多线程代码中哪些语句是操作共享数据的。 * 同步函数用的是哪一个锁呢?函数需要被对象调用。那么函数都有一个属性对象引用。就是this * 所以同步函数使用的锁是this 。 *@author XiaLei */public class Day11Test2 { public static void main(String[] args) { Cuz c1 = new Cuz(); new Thread(c1).start(); new Thread(c1).start(); }}class Bank{ private int sum; public void add(int x){ synchronized(this){//也可以在函数上直接加锁 sum+=x; System.out.println(Thread.currentThread().getName()+"=="+sum); } }}class Cuz implements Runnable{ private Bank b = new Bank(); public void run(){ for (int x = 0;x<3;x++){ b.add(100); } }}
显示结果:
6)静态函数的同步方式
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
经典示例:
package fuxi;/** * *@author XiaLei */public class day11Test11 { public static void main(String[] args) { }}//懒汉式单例设计模式class Single{ private static Single s = null; private Single(){} public static Single getInstance(){ if (s==null) synchronized(Single.class){ if (s==null){ s = new Single(); } } return s; } }
7)线程的死锁,同步中嵌套同步
package fuxi;/** * 思路:设计一个死锁程序,死锁就是同步里面嵌套同步,但是两个同步用的锁不同。 *@author XiaLei */public class Day11Test3 { public static void main(String[] args) { DeadLock dl = new DeadLock(true); DeadLock d2 = new DeadLock(false); new Thread(dl).start(); new Thread(d2).start(); }}class Lock{ static Lock locka = new Lock(); static Lock lockb = new Lock();}class DeadLock implements Runnable{ boolean flag = true; DeadLock (boolean flag){ this.flag = flag; } public void run(){ if (flag){ synchronized(Lock.locka){ System.out.println("if=="+"Lock.locka"); synchronized(Lock.lockb){ System.out.println("if=="+"Lock.lockb"); } } } else{ synchronized(Lock.lockb){ System.out.println("else=="+"Lock.lockb"); synchronized(Lock.locka){ System.out.println("else=="+"Lock.locka"); } } } }}
打印结果:程序卡住,不能继续执行
五、线程间的通信之等待唤醒机制
1)线程间的通信:其实就是多个线程在同时操作一个资源,但是操作的动作不同。
2)等待唤醒机制:其实就是 wait 方法和 notify 方法的使用。
使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
3)为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步线程时,都必须要标识他们所操作线程的锁。只有同一个锁上的被等待线程,可以被同一个锁 notify 唤醒,不可以对不同锁中的线程唤醒。也就是说,等待和唤醒必须是同一个锁。而锁,可以是任意对象,所以可以被任意对象调用的方法定义在 Object 中。
等待唤醒机制示例:
package fuxi;/** * *@author XiaLei */public class Day12Test { public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); }}class Res{ private String name; private String sex; private boolean flag = false; public synchronized void set(String name,String sex){ if (flag) try{this.wait();}catch(Exception e){} this.name = name; this.sex = sex; flag = true; this.notify(); } public synchronized void out(){ if (!flag) try{this.wait();}catch(Exception e){} System.out.println(name+">>>"+sex); flag = false; this.notify();//唤醒等待 }}class Input implements Runnable{ private Res r; Input(Res r){ this.r = r; } public void run(){ int x = 0; while (true){ if (x==0) r.set("jack", "man"); else r.set("丽丽", "女女女女"); x=(x+1)%2;//让x变成1; } }}class Output implements Runnable{ private Res r; Output(Res r){ this.r = r; } public void run(){ while(true){ r.out(); } }}
部分打印结果:
4)全部唤醒
对于多个生产者和消费者,就必须用 while + notifyAll(唤醒全部)。
1)为什么要用 while 判断标记呢?
原因:让被唤醒的线程再一次判断标记。
2)为什么定义 notifyAll ?
因为唤醒自己的同时还要唤醒对方的线程。只用 notify ,容易出现只唤醒本方线程的情况。导致程序中的所有线程都在等待。这也是比较有用的方法。
示例:
package fuxi;/** * *@author XiaLei */public class Day12Test1 { public static void main(String[] args) { Resouce r = new Resouce(); new Thread(new Producer(r)).start(); new Thread(new Producer(r)).start(); new Thread(new Consumer(r)).start(); new Thread(new Consumer(r)).start(); }}class Resouce{ private String name; private int count = 1; private boolean flag = false; public synchronized void set(String name){ while(flag) try{ wait(); } catch(Exception e){ throw new RuntimeException(); } this.name = name + count++; System.out.println(Thread.currentThread().getName()+"...生产者......"+this.name); flag = true; notifyAll(); } public synchronized void out(){ while(!flag) try{ wait(); } catch(Exception e){ throw new RuntimeException(); } System.out.println(Thread.currentThread().getName()+"+消费者+"+this.name); flag = false; notifyAll();//全部唤醒 }}class Producer implements Runnable{ private Resouce r; Producer(Resouce r){ this.r = r; } public void run(){ while(true){ r.set("汉堡"); } }}class Consumer implements Runnable{ private Resouce r; Consumer(Resouce r){ this.r = r; } public void run(){ while(true){ r.out(); } }}
部分打印结果:
5)JDK1.5中提供了多线程升级解决方案
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
升级解决方案的示例:
package fuxi;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;/** * *@author XiaLei */public class Day12Test2 { public static void main(String[] args) { Resouce r = new Resouce(); new Thread(new Producer(r)).start(); new Thread(new Producer(r)).start(); new Thread(new Consumer(r)).start(); new Thread(new Consumer(r)).start(); } } class Resouce{ private String name; private int count = 1; private boolean flag = false; private ReentrantLock lock = new ReentrantLock(); //创建两Condition对象,分别来控制等待或唤醒本方和对方线程 private Condition condition_pro = lock.newCondition(); private Condition condition_con = lock.newCondition(); public void set(String name) throws InterruptedException{ lock.lock(); try{ while(flag) condition_con.await(); this.name = name + count++; System.out.println(Thread.currentThread().getName()+"...生产者......"+this.name); flag = true; condition_pro.signal(); } finally{ lock.unlock();//解锁动作一定要执行 } } public void out() throws InterruptedException{ lock.lock(); try{ while(!flag) condition_pro.await(); System.out.println(Thread.currentThread().getName()+"+消费者+"+this.name); flag = false; condition_con.signal(); } finally{ lock.unlock(); } } } class Producer implements Runnable{ private Resouce r; Producer(Resouce r){ this.r = r; } public void run(){ while(true){ try { r.set("汉堡"); } catch (InterruptedException e) { } } } } class Consumer implements Runnable{ private Resouce r; Consumer(Resouce r){ this.r = r; } public void run(){ while(true){ try { r.out(); } catch (InterruptedException e) { } } } }
部分结果:
六、停止线程
1) JDK 1.5 后,停止线程的 stop 方法已经过时,那如何停止线程呢?
只有一种 run 方法结束。
2) 开启多线程运行,运行代码通常是循环结构。只要控制循环,就可以让 run 方法结束,也就是线程结束。
3)特殊情况:当线程处于冻结状态。就不会读取标记,那么线程就不会结束。
处理方法:强制中断线程:interrupt
当没有指定的方式让冻结的线程回复到运行的状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态来,实际上就是获取运行资格。这样就可以操作标记,让线程结束。
守护线程,setDaemon(boolean b);
4) 扩展知识:
1、join方法
当A线程执行到了b线程的join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。
2、setPriority()方法用来设置优先级
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级
3、yield()方法可以暂停当前线程,让其他线程执行。
- 黑马程序员 多线程
- 黑马程序员:多线程
- 黑马程序员-java多线程
- 黑马程序员--java 多线程
- 黑马程序员_java多线程
- 黑马程序员-java多线程
- 黑马程序员_多线程
- 黑马程序员 多线程
- 黑马程序员_JAVA多线程
- 黑马程序员—多线程
- 黑马程序员- 多线程
- 黑马程序员_多线程
- 黑马程序员--多线程
- 黑马程序员_多线程
- 黑马程序员--Java多线程
- 黑马程序员---多线程
- 黑马程序员__多线程
- 黑马程序员_多线程
- hdu3123actorial in GCC求阶乘
- Thrift 連線至 Hbase 使用Python - ImportError: No module named Thrift
- 单链表的一系列操作
- Java中异常小结
- Content-disposition
- 黑马程序员--多线程
- SIFT局部特征算法
- linux程序设计——select调用和多客户(第十五章)
- Key-value存储简介
- 博客有感
- Thinkphp3.2 上传详解
- Java中多线程小结
- Python request第三方库的安装
- 数据表操作(一)