目录 :
1 ) . 多线程(概述)
2 ) . 多线程(创建线程-继承Therea类)
3 ) . 多线程(创建线程-run和start特点)
4 ) . 多线程(线程练习)
5 ) . 多线程(线程运行状态)
6 ) . 多线程(获取线程对象以及名称)
7 ) . 多线程(售票的例子)
8 ) . 多线程(创建线程-实现Runnable接口)
9 ). 多线程(多线程的安全问题)
10 ) . 多线程(多线程同步代码块)
11 ) . 多线程(多线程--同步函数)
12 ) . 多线程(多线程--同步函数的锁是this)
13 ) . 多线程(多线程--静态同步函数的锁是Class对象)
14 ). 多线程(多线程--单例设计模式--懒汉模式)
15 ). 多线程(多线程--死锁)
一
. 多线程(概述)
1 ) . 简述 :
1.1 进程 --> 指 正在进行中的程序
[1] 每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫控制单元
1.2 线程 --> 指 进程中的一个控制单元 或 执行路径
[1] 线程控制着进程的执行
[2]进程与线程关系 : 一个进程中至少有一个线程 :
例如 : JVM启动时至少有一个进程java.exe,该进程中至少有一个线程负责java程序执行,该线程中必须得有一个主线程控制执行,也就是main方法
扩展 : JVM中深究不止只有一个负责java程序运行的线程,至少还有一个负责GC(垃圾回收机制)的线程
1.3 多线程存在的意义--> 有些需要分工协作而且必须同时执行的任务可用到多线程
1.4 线程的创建方式 --> 见第二第三章节
1.5 多线程的特性 -->随机性 -->谁抢占到CPU资源,谁执行,至于执行多长时间,CPU说了算
小结 :
1. 多线程可使多部分代码同时执行
二. 多线程(创建线程-继承Therea类)
1 ) . 如何在自定义类中 自定义一个线程?
引入 : java已提供对线程这类事物的描述,通过javaAPI查看,可发现是Th read类
1.1 第一种方式 :继承Thread类,重写run方法
[1] 第一步 : 定义类继承Thread
[2] 第二步 : 复写Thread类中的run方法
目的 : 将将自定义代码存储在run方法中,让线程运行
[3] 第三步 : 调用线程的start方法-->该方法有两个作用:启动线程,调用run方法
1.2 第二种方式 : 实现Thread接口,实现run方法
2 ) . Demo :
class Demo extends Thread{ //这是我自定义的一个线程public void run(){for(int i=0;i<50;i++)System.out.println("ThreadDemo.........."+i);}}class ThreadDemo{//这是JVM的一个线程public static void main(String args[]){Demo demo = new Demo();//创建线程demo.start(); //启动线程;调用run方法for(int i=0;i<50;i++)System.out.println("Hello World"+i);}}
3 ) . 为何每次运行结果每一次都不相同? 为何电脑看上去是在多个软件同时运行?
3.1 因为多个线程都在获取CPU的执行权,CPU采用随机分配的方式分配运行
3.2 在单核情况下,CPU在某一时刻,只能有一个程序在运行,CPU在快速的做着切换,让我们看上去达到同时运行的效果
小结 :
1. 线程-->多线程-->进程-->多进程-->核-->双核 : 假如一个核至少可同时运行五个程序,需要100MB内存,则双核至少可同时运行10个程序,则内存需要200MB内存,因此核数越多处理的程序也就越多,需要分配的内存则就越大2. 我们可形象的把多线程的运行行为在互相抢占cpu的执行权
三. 多线程(创建线程-run和start特点)
1 ) .特点 :
1.1 run方法就是用来存储线程要运行的代码的地方,是一个具备存储功能的方法
1.2 Start方法就是用来开启线程,来调用run方法的运行的
2 ) . 区分 :
1.1 实例化对象的变量.run(); -->一般方法调用run()方法,运行方式是单进程运行
1.1 实例化对象的变量.start(); --> 指先开启线程,在调用run()方法,运行方式是多线程运行
3 ).Demo
class Demo extends Thread{ //这是我自定义的一个线程public void run(){for(int i=0;i<50;i++)System.out.println("ThreadDemo.........."+i);}}class ThreadDemo{//这是JVM的一个线程public static void main(String args[]){Demo demo = new Demo();//创建线程demo.run(); //指单纯调用运行run()方法demo.start(); //启动线程 ;调用run方法for(int i=0;i<50;i++)System.out.println("Hello World"+i);}}
小结 :
1. Thread类用于描述线程,run方法用于存放线程运行的代码,start方法用于开启线程,运行run方法内的代码2. 通过延习父类功能,复写父类代码来完成使用父类方法,也就是extends和重写
四. 多线程(线程练习)
1 ) . Demo :
/*
练习:创建两个线程,让其和主线程交替运行*/class Test extends Thread{private String name;Test(String name){this.name=name;}public void run(){for(int i=0;i<=60;i++)System.out.println(name+"run.......");}}class ThreadDemo1{public static void main(String args[]){//创建一个名为One的为一个线程Test t = new Test("One");//创建一个名为Two的为二个线程Test t1 = new Test("Two");t.start();t1.start();for(int i=0;i<=60;i++)System.out.println("Hello World!");}}
小结 :
1. 一个对象代表一个线程,我们可通过创建多个对象实现多个线程,也就是多线程
五
. 多线程(线程运行状态)
1 ) . 图解 :
2 ) .理解 :
2.1 临时状态是具备运行资格,但未抢占到CPU执行权
2.2 冻结状态是放弃运行资格,也放弃了CPU执行权
2.3 运行状态是具备运行资格,也抢占到了CPU执行权
2.4 消亡状态是无状态存在,已执行结束
小结 :
1. 没有执行权的情况下是冻结状态,有执行权的是临时状态,又有执行权又有运行资格的是运行状态
六. 多线程(获取线程对象以及名称)
1 ) . Demo :
/*练习:创建两个线程,让其和主线程交替运行Thread.currentThread().getName() 与 this.getName() 两者是相同的,都是获取当前运行线程的名字*/class Test extends Thread{// private String name;Test(String name){//this.name=name;super(name);}public void run(){for(int i=0;i<=60;i++)System.out.println(Thread.currentThread().getName()+"........."+this.getName()+"run.......");}}class ThreadDemo2{public static void main(String args[]){//创建一个名为One的为一个线程Test t = new Test("One");//创建一个名为Two的为二个线程Test t1 = new Test("Two");t.start();t1.start();for(int i=0;i<=60;i++)System.out.println("Hello World!");}}
2 ) . 方法解析 :
2.1 static Thread currentThread(): 获取当前线程对象;
2.2 getName ; 获取线程名称
2.3 setName或者构造函数 : 用来设置线程名称
小结 :
1. Thread.currentThread().getName()与this.getName()都是用来获取当前执行线程对象的名称的--->
[1]一个是完整版(父类线程对象方法调用当前执行线程对象方法再调用方法名方法)
[2] 一个是缩略版(this替代了直接调用当前执行的线程对象的名字)
七. 多线程(售票的例子)
1 ) . Text : -->简单的买票系统
/*需求:简单的买票系统分析:需要有窗口,需要有票以下是通过静态的方式共享票从而不同窗口卖同一数量的票,但真实开发不是这样*/class Wicket extends Thread{private static int ticket =100;Wicket(String name){super(name);}public void run(){while(true){if(ticket>0)System.out.println(Thread.currentThread().getName()+"ticket....."+ticket--);}}}class ThreadDemo3{public static void main(String args[]){Wicket w = new Wicket("小明");Wicket w1 = new Wicket("小王");Wicket w2 = new Wicket("小李");Wicket w3 = new Wicket("小张");w.start();w1.start();w2.start();w3.start();}}
小结 :
1. Static -->共享数据,也同时可理解你用了别人用不能用了-->零和博弈
八
. 多线程(创建线程-实现Runnable接口)
1 ) . 代码块 :
1.1 定义理解 :
[1]普通代码块 : -->指类中方法的方法体
[2]构造代码块 : -->指在创建对象时被调用,每次创建都会被调用,优于够韩寒数执行,
[3]静态代码块 : -->指用static{}包裹起来的代码片段,只会执行一次,静态代码块由于构造块执行
[4]同步代码块 : -->指使用synchronized(){}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致,同步代码块需要卸载方法中
1.2 优先级 :
[1] 静态代码块 优于 构造代码块 优于 普通代码块 优于 同步代码块
2 ) . 创建线程的第二种方式 : 实现Runable接口
2.1 步骤:
第一步 : 定义类实现Runable接口
第二步 : 覆盖Runnable接口中的run方法
将线程要运行的代码存放在该run方法中
第三步 : 通过Thread类建立线程对象
第四步 : 将Runnable接口的子类镀锡i昂作为实际参数传递给Thread类的构造函数
将线程要运行的代码以参数的方式放入thread中,Thread对象中有runnable类型的相关属性的方法,这样自定义线程类就与线程Thread产生了关系
第五步 : 调用Thread类的start方法开启线程并调用Runbale接口子类的run方法
3 ) . 实现方式和继承方式有什么区别吗?
3.1 实现方式的区别 :
[1] Thread是依靠类继承,Runnable是依靠接口实现
[2]Thread继承具有局限性,A继承了B,则无法继承Thread,因此常用接口实现方式runnable
3.2 线程代码存放位置的区别 :
[1]继承Thread则线程代码存放在Thread子类run方法中
[2]实现Runnable,线程代码存在接口的子类的run方法中
3.3 使用场景的区别 :
[1] 在需要线程控制的代码的类不再需要其他父类的情况下使用继承Thread
[2]在需要线程控制的代码的类又也需要拓展其他功能的情况下使用实现Runnable-->一般都用这个
4 ) . Text案例-->窗口卖票
/*需求:简单的买票系统分析:需要有窗口,需要有票以下是通过第二种方式(Runnable)从而不同窗口卖同一数量的票*/class Wicket implements Runnable //extends Thread{private int ticket =100;public void run(){while(true){if(ticket>0)System.out.println(Thread.currentThread().getName()+"....ticket....."+ticket--);}}}class ThreadDemo3{public static void main(String args[]){Wicket w = new Wicket();Thread t1 = new Thread(w,"summer");Thread t2 = new Thread(w,"spring");Thread t3 = new Thread(w,"autumn");Thread t4 = new Thread(w,"winter");t1.start();t2.start();t3.start();t4.start();}}
小结 :
1. Thread类本身也实现了Runnable,为了确立Runnable的run()方法是用来存放代码的2. 继承某类中的某功能只是重载,而实现某接口中的某功能则是重写
九
.
多线程(多线程的安全问题)
1 ) . 问题的原因 :
1.1 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还未执行完,亮一个线程参与进来执行,导致共享数据的错误
2 ) . 问题的解决办法 :
1.1 对多条操作共享数据的语句,只能让一个线程都执行,在执行过程中,其他线程不参与执行
1.2 java对线程安全提供了专业的解决方式 : -->同步代码块
synchronized(对象){需要被同步的代码}
3 ).问题代码 :
/*需求:卖票系统分析:需要4窗口*/class Wicket implements Runnable{private int ticket=100;public void run(){while(true){if(ticket>0){try{Thread.sleep(100);}catch(InterruptedException e){}//假如程序在快速切换时,在这里停住,则会出现错票,引发安全问题System.out.println(Thread.currentThread().getName()+"TicketRun..."+ticket--);}}}}class ThreadDemo4{public static void main(String args[]){Wicket w = new Wicket();Thread t = new Thread(w);Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t.start();t1.start();t2.start();t3.start();}}
4 ).解决问题后的代码 :
/*需求:卖票系统分析:需要4窗口//通过synchronized同步代码块即可解决多线程的安全问题*/class Wicket implements Runnable{private int ticket=100;//此处创建object对象是为了下边的同步代码块的对象,不是特定的,只是拿来用用Object o=new Object();public void run(){while(true){synchronized(o){if(ticket>0){try{Thread.sleep(100);}catch(InterruptedException e){}//假如程序在快速切换时,在这里停住,则会出现错票,引发安全问题System.out.println(Thread.currentThread().getName()+"TicketRun..."+ticket--);}}}}}class ThreadDemo4{public static void main(String args[]){Wicket w = new Wicket();Thread t = new Thread(w);Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t.start();t1.start();t2.start();t3.start();}}
小结 :
1. 判断哪些代码需要同步可通过观察哪些代码操作了共享数据
十. 多线程(多线程同步代码块)
1 ) . 何为同步代码块 :
1.1 同步代码块如同卫生间上加了锁,作用是用来解决多线程在某种原因下出现数据同步错误问题的安全问题的,例子 : 火车上的卫生间1.2 格式:
synchronized(对象){需要被同步的代码}
2 ) . 同步代码块的前提:
2.1 必须要有两个或者两个以上的线程
2.2 必须是多个线程使用的同一个锁,锁就是对象
3 ) . 同步代码块的好处和弊端 :
3.1 好处: --> 解决了多线程的安全问题
3.2 弊端:--> 多个线程需要判断锁,因此较为消耗资源
小结 :
1. 给代码上加上同步代码块(同步锁)就跟上厕所把门反锁了一个道理,一次只进一个人,上个人出去下个人才能进来,这把锁在java中专业名词叫"锁旗标"或"监视器"2. 必须保证同步当中只有一个线程在运行就解决了多线程的安全问题
3. 安全与性能是两个极端,是我们应该去不断平衡的极点
十一. 多线程(多线程--同步函数)
1 ) . 同步的两种表现形式 : -->功能是一致的,用来加锁的-->在多个线程同时访问同一块数据的情况下加的锁
1.1 同步代码块
1.2 同步函数
2 ) . Text : -->俩人来银行充钱
/*需求:充钱;有两位顾客每人向银行同时冲300元,冲了3次*///银行负责充钱class Bank{private int TotalAmount;// Object o= new Object();public synchronized void add(int money){//synchronized(o)//{try{Thread.sleep(100);}catch(InterruptedException e){}TotalAmount=TotalAmount+money;System.out.println("TotelAmount总额度:"+TotalAmount);//}}}//充钱,冲三次class Client implements Runnable{Bank bank = new Bank();public void run(){for(int i=0;i<3;i++){bank.add(100);}}}class ThreadDemo5{public static void main(String args[]){Client c = new Client();//俩进程代表俩人Thread t = new Thread(c,"summer");Thread t1 = new Thread(c,"autumn");t.start();t1.start();}}
3 ) . 如何发现程序中的安全问题并解决?
3.1 如何找问题 :
[1] 明确哪些代码是多线程运行代码
[2] 明确共享数据
[3] 明确多线程运行代码中那些语句是操作共享数据的
3.2 如何解决多线程安全问题?
[1] 定义同步代码块
[2] 定义同步函数 -->也就是在修饰符前加个synchronized
小结 :
1. 办事多少次的时候想到for循环,功能定义那个类的时候想到职责,多人同时干的时候想到多线程,数据被多人共享时想到同步代码块
十二
. 多线程(多线程--同步函数的锁是this)
1 ) . 同步函数用的是哪一个锁呢?
1.1 函数需要被对象调用,那么函数都有一个对象引用,就是this,所以同步函数使用的锁是this
1.2 验证方式 :
[1] 使用俩线程买票,一个线程在同步代码块中,一个线程在同步函数中,都在执行买票动作,产生安全问题
2 ) . Demo :
/*使一段程序在在两个线程中交替运行,验证this的确是同步锁的对象引用验证过程 : 通过创建了两个线程,让其中一个线程运行同步代码块内的传的是object对象,另一个运行同步函数内的,从而产生了多个线程使用的不是同一个锁,从而产生了线程不安全的问题然后让其同步代码块内传的对象改为this解决了问题*/class Demo implements Runnable{private int ticket=1000;boolean flag=true;// Object o = new Object();public void run(){if(flag){while(true){synchronized(this){if(ticket>0){ try{Thread.sleep(100);}catch(InterruptedException e){}//等待只是为了测试System.out.println(Thread.currentThread().getName()+"benDemo....."+ticket--);}}}}else{while(true){show();}}}public synchronized void show(){if(ticket>0)System.out.println(Thread.currentThread().getName()+"runDemo....."+ticket--);}}class ThreadDemo6{public static void main(String args[]){Demo d = new Demo();Thread t = new Thread(d,"summer");Thread t1 = new Thread(d,"autumu");t.start();try{Thread.sleep(100);}catch(InterruptedException e){} //等待只是为了测试d.flag=false;t1.start();}}
小结 :
1. 多线程的前提一是有两个或两个以上的程序,二是多个线程使用同一个锁,锁就是对象
十三
. 多线程(多线程--静态同步函数的锁是Class对象)
1 ) . 若同步函数被静态修饰后,使用的锁是什么呢?
1.1 通过思考,静态是不可以用this调用的,因此也就不是this,静态产生时,内存中没有本类对象,但是有对应的字节码文件对象,因此使用的锁是类名.class
1.2 结论 : --> 静态的同步方法,使用的锁是该方法所在类的字节码文件对象,也就是类名.class
2 ) . Demo-->验证结论是否正确
/*使一段程序在在两个线程中交替运行,验证class的确是静态同步锁的对象引用*/class Demo implements Runnable{private static int ticket=100;boolean flag=true;// Object o = new Object();public void run(){if(flag){while(true){synchronized(Demo.class){if(ticket>0){ try{Thread.sleep(100);}catch(InterruptedException e){}//等待只是为了测试System.out.println(Thread.currentThread().getName()+"benDemo....."+ticket--);}}}}else{while(true){show();}}}public static synchronized void show(){if(ticket>0)System.out.println(Thread.currentThread().getName()+"runDemo....."+ticket--);}}class ThreadDemo7{public static void main(String args[]){Demo d = new Demo();Thread t = new Thread(d,"summer");Thread t1 = new Thread(d,"autumu");t.start();try{Thread.sleep(100);}catch(InterruptedException e){} //等待只是为了测试d.flag=false;t1.start();}}
小结 :
1. 多线程同步函数的锁是this,多线程静态同步函数的锁是 -->类名.class
十四. 多线程(多线程--单例设计模式--懒汉模式)
1 ) . 懒汉式和饿汉式都是用来实例化对象的,饿汉式是直接创建直接返回,懒汉式是先为空,再判断是否为空,再创建
2 ) . 常用的单例模式 : -->普通饿汉模式
//饿汉模式class SingleDemo{private static final Single s =new Single ;private Single(){}public void getInstance(){return s;}}
3 ) . 写一个延迟加载的单例模式示例 -->面试考点 -->写多线程下的懒汉模式class Single{private static Single s =null;private Single(){}public void getInstance(){if(s==null){synchronized(Single.class){if(s==null){s=new Single;return s;}}}}}
懒汉式经典回答 :
[1] 懒汉式是的特点在于延迟加载,在多线程访问时会出现安全问题,可通过加同步锁解决安全问题,但低效
[2] 而后通过双重判定来再解决低效问题 , 在同步锁上加的锁是字节码文件对象,因为该方法是静态的
十五. 多线程(多线程--死锁)
1 ) . 死锁出现的情况 :
1.1 同步中嵌套同步
1.2 锁不是一个锁,A锁套B锁,B锁套A锁,出现了死锁
2 ) . Demo :
/*写一个死锁好不好 ? 好分析:出现死锁是在多线程的情况下同步代码块使用了不同对象的锁,两者僵持不下而产生的;*/class Demo implements Runnable{private boolean flag =true;Demo(boolean flag){this.flag=flag;}public void run(){while(true){if(flag){synchronized(MyLock.o2){System.out.println("if MyLock.o2 ");synchronized(MyLock.o1){System.out.println("if MyLock.o1 ");}}}else{synchronized(MyLock.o1){System.out.println("else MyLock.o1 ");synchronized(MyLock.o2){System.out.println("else MyLock.o2 ");}}}}}}class MyLock{static Object o1 = new Object();static Object o2 = new Object();}class DeadLockDemo{public static void main(String args[]){Thread t = new Thread(new Demo(false));Thread t1 = new Thread(new Demo(true));t.start();t1.start();}}
小结 :
1. 在同步代码块嵌套同步代码块时切忌锁使用同一个锁,复习下,静态同步代码块使用的锁是类加载文件,同步代码块使用的锁是this