java基础第13天

来源:互联网 发布:网络发展趋势 编辑:程序博客网 时间:2024/04/28 20:48



1:多线程
(1)多线程
  进程: 当前正在运行的程序!
  线程:进程在执行过程中,可能需要多个任务同时执行,每个任务的执行者就是线程。线程就是进程中的一个执行控制单元。

  单线程:
     多线程:如果一个进程中有多个执行的轨迹,那么就是同时有多个线程在执行代码。
  这样的程序叫多线程程序!
线程是部分,进程是整体
  线程是部分,而进程整体。
  部分不能单独存在,线程不能单独启动,只有进程启动后线程才可以启动。
  一个进程中的多个线程,可以共享这个进程的数据。进程之间不能共享数据。
多线程的用途
  同时执行多个任务,可以提高效率。
  可以完成多个任务并发的效果。
  


(2)jvm启动是多线程的。
(3)如果实现多线程呢?
A:继承Thread类
步骤:
**定义一个类,继承Thread类
**重写run方法
**调用start方法.
**启动线程
**调用run方法


Thread类中的常用方法:
**Thread(String name):通过构造给线程起名字
**setName(String name):通过set方法给线程起名字
**getName():获取线程的名称
**currentThread():获取当前线程的一个对象引用
***获取哪些没有直接继续Thread类的线程名称


B:实现Runnable接口
步骤:
**定义一个类,实现Runnable接口
**重写run方法
**创建实现了Runnable接口的子类对象,并把该对象作为参数传递给
 Thread类的对象.这个时候,调用Thread类的start方法.

(4)


多线程的真实情况(CPU很忙)


1 多个进程同时运行是假的!多个线程同时运行也是假的!
  理解:多个线程就好比需要通电才能运行的多个机器人!但我们只能一个电源,这个电源就是CUP。


2 线程的随机性,以及时间分片
  线程的运行时间分派由 CPU来决定,如果多有多个线程,那么CPU也不会有顺序的去执行它们,而是随机的。
  当一个线程运行一段时间之后,应该主动阻塞,这样CPU就会把资源分派给其他线程。如果线程不合作,自己不主动阻塞,那么CPU会在一段时间之后强行停止这个线程的运行,把资源分派给其他线程。
  CPU会为每个线程都指定一个时间分片,当时间片用完时,线程还不阻塞,那么就强行停止它。


3 多核CPU的不同
  多核CPU就相当于是多个CPU,那么与上面说的效果就不太一样了。
  


4 同一个多线程程序多次运行,结果可能不一样
  因为线程的随机性。


5 主线程结束时,JVM是否结束
   No,只有所有线程都结束了,JVM才会线程。


6 多线程程序的调用栈
  每个线程都有自己的调用栈,都有自己的执行轨迹!


7 当某个线程出现异常时
  异常不能跨线程。
  如果是main抛出了异常,那么主线程会终止,但其他线程不会终止。
  如果t1线程抛出了异常,那么t1终止,而main不会终止。


Thread类一般性方法


1 设置线程的名字
   Thread(String name):这个构造器可以用来设置线程名称
  Thread#setName(String name):这个方法可以用来设置线程名称
  Thread#getName():获取当前线程名称


2 获取主线程对象
  static Thread Thread.currentThread():这个方法用来获取当前线程。
  
  该线程,当前线程。






Runnable接口


1 当一个类需要继承另一个类后,就不能再去继承Thread类了
  一个类只能有一个父类,这时如果我们继承了Thread,那么就不能再去继承别的类了。那么我们就不能实现多线程程序了。
  使用Runnable接口。
  Runnable接口中只有一个方法run(),其实Thread类也是实现了Runnable接口才有的run()方法。


2 Thread是完成任务的人,而Runnable就是任务
  Runnable是任务,而Thread是完成任务的人,这样我们的程序也会清晰一些!


3 实现Runnable
  Runnable r = new Runnable() {public void run() {…}};


4 在Runnable的run()里获取当前线程
  Thread th = Thread.currentThread();






练习


1 售票
  实现多窗口同时售票!
  多个窗口共享同一票库,当票库无票时,程序结束。
  分析:
票库类:
票数属性
构造器:指定票数;
查看当前票数方法;
出票方法:返回当前票码,把票数减1。
窗口类(多个窗口需要并发出票,所以需要是线程类):
构造器:指定票库
run(死循环出票):
判断当前票数是否小于等于0,如果是就跳出循环;
调用票库出票方法,打印当前票码!
测试类:
创建票库对象;
创建窗口对象;
启动窗口线程。
卖票的问题
A:继承Thread实现卖票
这个时候,我们的票需要定义为静态的.但是呢,我们不建议这样做.
B:实现Runnable接口卖票
在这里,我们就发现卖票除了问题.(线程安全问题)


线程安全问题怎么产生的?
**多个线程延迟访问
**线程的随机性


重点:
多线程的两种实现.
线程的生命周期.理解
用同步实现卖票.


线程的状态


1 线程的生命周期包含状态
  新状态、就绪状态、阻塞状态、死亡状态




2 新状态
  刚刚new出来的线程对象,还没有调用start()方法。如:Thread t = new Thread();
3 就绪状态(可运行状态)
  t.start()之后中,线程进入就绪状态!
  在某一个时间点上,只有一个线程是运行着的,其它的线程都没有运行,所以我们说 start()之后不是“运行状态”,而是“就绪状态”。
  线程什么时候从就绪运行,这由CPU来决定, CPU会给运行的线程一个时间片,这 个时间用完,如果线程还没有主动阻塞,那么CPU会强行停止运行,给其他线程运行机会。
4 运行状态
  由 CPU决定, CPU会在就绪状态的线程中随机选择一个,给其运行的时间片。运行的 线程,应该主动进入阻塞状态,这样给其他线程运行的时间。


5 阻塞状态
  
休眠:Thread.sleep(1000),当线程执行了sleep()方法,那么这个线程就进入了休眠 状态。休眠的线程必须要等到指定的毫秒过完,才能返回到就绪状态。
  等待:当前线程执行了wait()方法,进入了对象的等待列表中。只能期 待其他线程调用notify()或notifyAll()来唤醒这个等待的线程。
挂起:调用了该线程的suspend()方法,那么这个线程就挂起了。这时就期待该线程 的resume()被调用,才能回到就绪状态。但是,这两个方法都被作废了,你不应该使用它们!
IO阻塞:当线程正在完成IO操作,那么这个线程也就阻塞了。直到IO操作完成 了!
锁定: 当线程要使用的对象被其他线程使用时,那么这个线程进入了锁定状态。 直到线程得到了要使用的对象后,那么就回到就绪状态。


6 死亡状态
run()方法结束,正常死亡!                       (正常死)
run()中抛出了异常,因为而死亡!                 (异常死)
run()被杀了,有人调用这个线程的stop()方法。stop()方法被作废了!被打死(作废了)


同步互斥
  同步的目的:用来处理多个线程共享同一数据时,所造成的错误。
1 多线程相互影响,出现错误数据
  两个人同时过一扇门
  两个人同时用一个电话


2 synchronized块的语法格式
  synchronized(监视器对象) {
      ……
  }
  
   共享数据不可能是局部变量,因为局部变量,都是每个线程都有一份自己的拷贝!共享数据是属性。


3 synchronized块的作用
  理解:
把监视器对象理解为导游;
把同步块中的代码理解为景区内容;
把线程理解为游客!
  当游客想进入景区游玩时,需要有导游陪同,如果a游客来到景区时,导游空闲,那么导游会陪同a游客进入景区,在a游客没有离开景区之前,b游客也要进入景区,那么这时导游不在,所以b游客需要等待导游空闲。当a游客离开了景区,那么导游就空闲了,这时b游客就可以由导游陪同进入景区了。
  a和b两个线程在同步块内容上同步互斥了,当a进入同步块后,b需要等待a退出后才能进入同步块。


synchronized方法
1 使用synchronized关键字来修饰方法
public synchronized void fun() {
}


  调用同步方法同样需要钥匙,钥匙在保安手里。保安就是当前对象!


2 注意,synchronized方法与synchronized块是相同的道理
  同步块的共享数据(监视器对象)是在圆括号里
  同步方法的共享数据(监视器对象)是 this,即当前对象。
  同步方法:当a线程访问了o对象的fun1()方法时,同时b线程也要访问o对象的fun2()方法,这时就需要等待a线程退出o对象的fun1()方法,即不在使用o对象时,那么b线程才能进入o对象的fun2()方法。
  但非同步方法,其他线程还是可以访问的!


3 使用同步块还是同步方法
  同步块!
  灵活啊~


静态同步方法


1 静态同步方法是可以存在的
  静态同步方法是可以存在的,同一个类中的多个静态同步方法之间可以同步互斥!


2 静态同步方法的监视器对象
  共享数据是静态同步方法所在类的class对象,例如:
  public class A {
    public static synchronized void fun() {}
  }
  对于fun()方法来说,共享数据是A.class


懒汉式
1 使用synchronized关键字来修饰方法
public synchronized void fun() {
}


  调用同步方法同样需要钥匙,钥匙在保安手里。保安就是当前对象!


2 注意,synchronized方法与synchronized块是相同的道理
  同步块的共享数据(监视器对象)是在圆括号里
  同步方法的共享数据(监视器对象)是 this,即当前对象。
  同步方法:当a线程访问了o对象的fun1()方法时,同时b线程也要访问o对象的fun2()方法,这时就需要等待a线程退出o对象的fun1()方法,即不在使用o对象时,那么b线程才能进入o对象的fun2()方法。
  但非同步方法,其他线程还是可以访问的!


3 使用同步块还是同步方法
  同步块!
  灵活啊~


静态同步方法


1 静态同步方法是可以存在的
  静态同步方法是可以存在的,同一个类中的多个静态同步方法之间可以同步互斥!


2 静态同步方法的监视器对象
  共享数据是静态同步方法所在类的class对象,例如:
  public class A {
    public static synchronized void fun() {}
  }
  对于fun()方法来说,共享数据是A.class


懒汉式
1 使用synchronized关键字来修饰方法
public synchronized void fun() {
}


  调用同步方法同样需要钥匙,钥匙在保安手里。保安就是当前对象!


2 注意,synchronized方法与synchronized块是相同的道理
  同步块的共享数据(监视器对象)是在圆括号里
  同步方法的共享数据(监视器对象)是 this,即当前对象。
  同步方法:当a线程访问了o对象的fun1()方法时,同时b线程也要访问o对象的fun2()方法,这时就需要等待a线程退出o对象的fun1()方法,即不在使用o对象时,那么b线程才能进入o对象的fun2()方法。
  但非同步方法,其他线程还是可以访问的!


3 使用同步块还是同步方法
  同步块!
  灵活啊~


静态同步方法


1 静态同步方法是可以存在的
  静态同步方法是可以存在的,同一个类中的多个静态同步方法之间可以同步互斥!


2 静态同步方法的监视器对象
  共享数据是静态同步方法所在类的class对象,例如:
  public class A {
    public static synchronized void fun() {}
  }
  对于fun()方法来说,共享数据是A.class


懒汉式
死锁


1 什么叫死锁
  张三在A电话亭想去B电话亭
  李四在B电话亭想去A电话亭


2 理解死锁
  a线程锁定一个资源,同时想获取b线程的资源
  b线程锁定一个资源,同时想获取a线程的资源。


2 通常死锁是怎么造成的
  同步嵌套调用造成的。


3 死锁的代码
  数据类
A类两个同步方法fun1()和fun2();
fun1()需要一个本类型的参数,例如void fun1(A a);
fun1()中使用参数调用fun2()方法,例如:a.fun2();
fun2()的内容无关紧要,甚至可以没有内容。
线程类:
创建一个线程类,需要两个A类的对象:from和to;
在run()中使用from调用fun1()方法,参数为to;
测试类:
创建两个数据对象:zhangSan和liSi;
创建两个线程对象:一个from是zhangSan,to是liSi;另一个from是liSi,to是zhangSan;
启动两个线程。


 为了看到效果,可以在fun1()方法中调用fun2()之前,添加Thread.sleep()方法。
解决线程安全问题
A:同步代码块
格式: synchronized(对象)
     {
//被同步的代码,也就是可能出现安全问题的代码
     }
B:同步方法
就是在方法上加synchronized关键字即可.
C:同步的前提:
**同步需要两个或者两个以上的线程
**多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
D:同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,
无形中会降低程序的运行效率。


同步可能造成死锁问题:
死锁的产生:
你拿着我们的锁,我拿着你的锁,谁都不先对方用.
举例:吃饭,西餐(刀和叉)
A:有两个刀
B:有两个叉
针对程序来说,就是我想用你的锁,而你也想用我的锁.都不放手.导致了死锁.
用代码实现一下死锁.


解决死锁的方式:不写这样的代码就可以.
线程间的通讯
  wait()、notify()、notifyAll()
  通讯方法,这三个方法都是Object类的方法。
  通讯方法被调用的前提是:同步环境!
1 什么叫线程间的通讯
  多个线程之间需要协调工作。
  例如:
  
  1 女:我们结婚吧;
  2 男:我还没有钱;你等我,我去赚钱!
  3 女:ok,那我wait了,你赚了钱回来,不要忘了notify一下我。
  
  1 老师:我们开始上课;
  2 学生A:老师等一下,我要去厕所;
  3 老师:OK,你快点,那我wait()了,等你回来notify我一下。
  
  1 坏人:你爷爷在我手里
  2 好人:不要伤害我的爷爷
  3 坏人:给我钱,我去公安局边上的垃圾筒中去取,我先wait了,你把钱放好了,再notify()我
  4 好人:好吧,我去借钱!


2 实现绑架1(需要使用相同的垃圾筒)
  好人类(Runnable) -- 把钱放到垃圾筒
  坏人类(Runnable) -- 从垃圾筒取钱
  垃圾筒(钱的数量,存钱、取钱、当前钱数)


  因为好人与坏人使用的是不同的垃圾筒,所以会导致好人存钱了,但坏人也取不到钱。


3 实现绑架2(坏人可能先取钱)
  修改上例,让好人、坏人共用同一个垃圾筒
  因为坏人可能会在好人存钱之前去取钱,所以会取不到!


4 实现绑架3(同步中使用不同的监视器)
  修改上例,让垃圾筒同步。确保在好人已经使用垃圾筒时,坏人需要等待好人使用之后才能使用。
  因为使用了不同的监视器,没有实现同步效果。好人和坏人共用的是垃圾筒,所以应该使用垃圾筒来做监视器。
  如果坏人先使用了垃圾筒,而好人需要等待坏人使用完才能进入同步块,那么就失败了。
5 实现绑架4(当坏人发现没钱时,等待好人存钱)
  如果坏人先得到垃圾筒,发现没有钱,可以等待好人来存钱,而坏人停止运行。
  好人存钱之后会唤醒坏人。
  坏人再去取钱。


通讯原理


1 使用wait()、notify()、notifyAll()完成通讯
  这三个方法我们称之为通讯地方法


2 同步与线程间通讯方法
  没有同步环境,就不能完成线程间的通讯!
  如果你的通讯方法没有在同步块或同步方法中,那么会抛出异常!
  wait():声明了InterruptedException异常!在使用wait()时,还要使用try/catch。


3 监视器对象与线程通讯方法
  只能使用监视器对象来调用线程通讯方法。


4 监视器对象的监狱
  每个监视器对象都有一个线程监狱,当执行了wait()时,例如:a.wait(),那么就是把当前线程关到a的监狱中。
  当其他线程执行a.nofity()时,那么就是把a监狱中任意一个线程释放出来。
  当其他线程执行a.notifyAll时,那么就会把a监狱中所有线程都放出来。


5 等待状态的线程会释放对象锁
  当一个线程进入了等待状态后,那么它会释放对象锁,让其他线程可以进入同步环境。


6 被唤醒的线程会在醒来的位置向下运行
  当一个线程,从wait()后,被唤醒,并不代表马上就能执行。这需要等到重新获取到对象锁!


6 线程间通讯小结
使用wait()、notify()、notifyAll()方法可以完成线程间的通讯,可叫它们通讯方法;
只能在同步环境下调用通讯方法;
只能使用监视器对象调用通讯方法;
每个监视器对象都有一个线程监狱:执行a.wait()的线程会被关押到a对象的线程监狱中;
若想释放出a对象的线程监狱中的线程,那么需要调用a.notify()文法,该方法只能保证在a对象的线程监狱中释放出一个线程,但不能保证释放的是哪一个;
还可以使用a.notifyAll()方法释放出a对象的监狱中关押的所有线程。
被wait()了的线程不能自己恢复到就绪状态,只能等待其他线程调用同一监视器对象上的notify()或notifyAll()方法来唤醒。
被wait()了的线程会释放监视器对象的对象锁,这样其他线程就可以进入他占用的同步环境。
被唤醒的线程恢复到了就绪状态,当再次获取监听器对象的锁后会在wait()处向下运行。
(1)线程间通讯就是:想把数据你一个,我一个的输出.  重点
wait:让线程等待
notify:唤醒线程池中的第一个线程
notifyAll:唤醒线程池中的所有线程
(2)wait(),sleep()有什么区别?面试问题
A:wait()可有无参数的调用.
  而sleep必须指定睡眠时间.但是有些时候,睡眠时间不好确定.这个时候就可以使用wait.
B:wait:释放了执行权,释放锁.
  sleep:释放了执行权,不释放锁.
sleep有醒过来的时候,而wait可能醒不了
(3)停止线程的方式?
A:通过stop方法停止线程.但是这个方法过时,所以不推荐使用.使用 interrupt方法来中断该等待
B:线程中的代码一般都是在循环中的.可以通过控制循环的次数来让run方法结束.
后台线程(守护线程)
  JVM会在所有线程结束后结束!
  当所有的非后台线程结束后,所有后台线程集体自杀!
1 什么是后台线程
  JVM会在所有线程结束后结束!
  当所有的非后台线程结束后,所有后台线程集体自杀!
2 什么时候用后台线程
  当你的线程只是为其他线程服务的,那么当其他线程结束后,这个服务线程就没有独立存在的意义了,那么就把这个线程设置为后台线程。
  例如:垃圾回收器,在我们的main方法结束后(所有我们写的线程),垃圾回收器没有道理自己还运行,那么可以理解为垃圾回收器是个后台线程。


3 如何设置线程为后台线程
  Thread th = …
  th.setDaemon(true);//后台了,注意,必须在start()之前进行设置。
  th.start();
  
  Thread.currentThread().setDaemon(true);//无意义!


4 线程运行后是否还可以设置为后台线程
  不能!!!必须在start()之前完成设置
5 后台线程的子线程
  后台线程的子线程,也是默认为后台线程!
  什么叫子线程?A线程的任务中启动了B线程,那么B是A的子线程。
  所有的后台线程子线程默认为后台线程!


合并线程(join)


1 什么合并线程
  join()是Thread类的方法。
  Thread th = new …
  th.start();
  …
  th.join();//使当前线程等待th线程结束后,再向下运行!


2 join()方法的作用
  使当前线程等待该线程结束再向下运行。
  Thread#join()声明了一个InterruptedException,我们已经知道有三个方法声明了这个异常。  
Thread.sleep():Thread类的static方法sleep()
Thread#join:Thread类的实例方法join
Object#wait():Object类的实例方法wait()


线程让步(yield)
  在以前使用sleep()的地方,你感觉sleep会担负时间,那么可以尝试使用yield
1 yield()方法的作用
  说线程已经把最重要的工作做完了,告诉CPU可以切换给其他线程了。


2 多核CPU下yield()方法的效果
  不明显!
3 让步不同与阻塞
  让步的线程没有进入阻塞状态,只是从运行状态到就绪状态!


终止线程(比join和yield重要一点)
  线程对象都有一个中断值,它是一个boolean类型的属性。这个中断值默认是false!你可以调用一些方法来修改中断值,也可以调用一些访问获取中断值。
void interrupt():把中断值修改为true;
boolean isInterrupted():获取中断值;
static boolean interrupted():获取中断值,并把中断值修改为false。
1 stop()方法
  th.stop();//把th杀死!
  但stop()作废了!你不应该再去使用它!


2 使用interrupt()方法,isInterrupted()方法和interrupted()的作用




3 中断异常相关方法
  InterruptedException
sleep()
join()
wait()
这三个方法都会在中断值为true时,抛出异常!并且会把中断值修改为false。








 优先级(鸡肋)


1 什么是线程的优先级
  CPU会优先运行优先级高的线程!
2 Java中线程的优先级 范围(1-10); 默认是5.


3 Java中线程的优先级依赖OS
   不同的OS,支持的优先级别不同,windows是7个,Linux一些版本还有1个的。
  所以优先级不可信,如果你真想让程序有个选择执行的话,使用sleep()好一点。
4 优先级不可靠,不能依赖优先级




原创粉丝点击