java多线程

Java多线程: 

什么是进程?{ 
当前正在运行的程序。 
代表一个应用程序在内存中的执行区域。 



什么是线程(FlashGet)?{ 
是进程中的一个执行控制单元,执行路径。 
(执行空间代码的执行者) 


一个进程中至少有一个线程在负责控制程序的执行。 
一个进程中如果只有一个执行路径,这个程序成为单线程程序。 
一个进程中如果有多个执行路径时,这个程序成为多线程程序。 

多线程的出现:可以有多条执行路径。让多部分代码可以完成同时执行。以提高效率。本身也是对问题的一种解决方案,比如图形界面的多个小程序同时执行。比如360,迅雷 

Jvm启动是单线程还是多线程?{ 

Jvm的启动就是多线程程序。 
其中有一个程序负责从主函数开始执行,并控制程序运行的流程。 
同时为了提高效率,还启动了另一个控制单元(执行路径)专门负责堆内存中的垃圾回收,在程序正常执行的过程中,如果出现了垃圾,这时另一个负责收垃圾的线程会在不定时的时间内进行垃圾的处理。这两个程序是同时执行的。 



负责执行正常代码的线程,称为主线程,该线程执行的代码都存放于主函数中。 
负责收垃圾代码的线程,称为垃圾回收线程。该线程要执行的代码在finallize中。 

如何在java中创建一个线程?{ 

其实java中对线程这类事物已经进行了描述,并提供了相对应的对象。 
这个对象就是java.lang.Thread。 

创建线程的两种方式 
1.继承Thread类。 
  步骤: 
  1.定义类继承Thread 
  2.覆盖Thread类中run方法() 
  3.创建Thread类中的子类对象创建线程。 
  4.调用Thread类中的start方法开启线程,并执行子类中的run方法 
  特点: 
  1,当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程 所执,行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线 程对象。可是这样会导致每一个对象中都会存储一份属性数据。 
  无法在多个线程中共享数据,加上静态,虽然实现了共享,但是生命周期过长。 
2.如果一个类明确了自己的父类,那么就不可以继承Thread。因为java不允许类的多继承。 

2.实现runnable接口。 
  步骤: 
1.定义类实现Runnable接口。 
2.覆盖接口中的run方法。(将多线程要运行的代码定义在方法中) 
3.通过Thread类创建线程对象,并将实现了runnable接口的子类对象作为实际参数传递给Thread类的构造函数。 
(为什么非要被Runnable接口的子类对象传递给你Thread类的构造函数呢? 
  是因为线程对象在建立时,必须要明确自己要运行的run方法,而这个run方 法定义在了Runnable接口的子类中,所以要将该run方法所属的对象传递给 Thread类的构造函数。让线程对象建立,就知道运行那个run方法。 
) 
4.调用Thread类中的start方法,开启线程,并执行Runnable接口子类中的run方法。 
特点: 
  1.描述事物的类中封装了属性和行为,如果有部分代码需要被多线程所执行, 
  同时还在操作属性,那么可以通过实现Runnable接口的方式。因为该方式是定义一个Runnable接口的子类对象,可以被多个线程所操作。实现了数据共享。 
2.实现了Runnable接口的好处,避免了单继承的局限性。一个类如果已经有了自己的父类,不可以继承Thread类。但是该类中还有需要被多线程执行的代码。这时就可以通过在该类上功能扩展的形式。实现一个Runnable接口。 

  所以创建线程时,尽量使用第二种方法。 
   
创建线程的两种方式区别? 



(为什么要覆盖run? 
直接建立Thread类对象即可,并开启线程执行就可以了,但是虽然线程执行了,可是执行的代码是该线程默认的代码,该代码就存放在run方法中。 
可是定义线程的目的是为了执行自定义的代码。 
而线程的运行代码都存储在run方法中,所以只有覆盖了run方法,才可以运行自定义的内容。想要覆盖,必须要先继承。 

主线程运行的代码都在main函数中,自定义的代码都在run方法中。 



** 
直接创建Thread类的子类对象就是创建了一个线程,如果直接调用该对象的方法。这时,底层资源并没有完成线程的创建和执行,仅仅是简单的调用对象的方法。 
在内存中其实:1.堆内存中产生了一个对象,2.需要调用底层资源去创建执行路径 

如果想要开启线程:需要调用start方法,该方法做了两件事 
1.开启线程 
2.调用线程的run方法 




当创建了两个线程后的d1,d2后,这时程序就有了三个线程在同时执行。 
当主函数执行完d1.start(),d2.start()后,这时三个线程同时打印,结果比较杂乱。 
这是因为线程的随机性造成的。 
随机性的原理是:windows中的多任务同时执行,其实就是多个应用程序在同时执行。 
而每一个应用程序都有线程来负责控制的。所以window就是一个多线程的操作系统,那么cpu是负责提供程序运算的设备。cpu在某一时刻只能执行一个程序,所谓的多任务执行就多个应用程序在执行,其实是cpu通过快速切换来完成的。 只是感觉上是同时而已。 

能不能真正意义上的同时执行?{ 
可以,多个cpu,就是现在的多核。 


线程中的方法: 
多线程的创建,为了对各个线程进行标示,系统会自动对各个线程起一个名字(thread--) 
Static Thread currentThread() 获取到当前线程 
String getName() 得到当前线程的名称 
Vodi setName() 重新设置线程的名称 

如何获取到主线程? 
可以通过Thread类中的一个方法:currentThread返回当前正在执行的线程对象。 
主线程的名字叫main-- 


在线程被创建后,可以分为临时阻塞状态,运行状态,冻结状态,消亡状态。 
如果有三个线程A,B,C;当三个线程都调用start方法之后,三个线程都具备了执行资格, 
处于临时阻塞状态。 
当某一个线程A正在被CPU处理,说明A处于运行状态,即具备了资格,也具备了CPU的执行权。 
而B,C处于临时阻塞状态,当CPU切换到B线程时,B就具备执行权,这时,A,C就处于临时阻塞状态,只具备执行资格,不具备执行权。 

当一个线程被cpu执行具备了执行权,就到了运行状态,当cpu切换到其他线程开始执行的时候,刚才的线程就失去了执行权,回到了临时阻塞状态。 
当一个线程调用了sleep,wait方法后,就释放了cpu的执行权,并释放了执行资格。 
冻结状态结束后,表示线程重新具备了执行资格,但是不一定立刻具备执行权。 
比如睡眠时间到,或者用notify方法唤醒线程。 

当一个线程执行完就到了消亡状态,也可以调用stop方法让线程进行消亡。 




线程安全问题:(因为线程的随机性,有可能会导致多线程在操作数据时发生数据错误的情况) 
线程安全问题产生的原因: 
当线程多条代码在操作同一个共享数据时,一个线程将部分代码执行完,还没有继续其他代码时,被另一个线程获取cpu执行权,这时,共享数据操作就有可能出现数据错误。 
简单说:多条操作功能数据的代码被多个线程分开执行造成的。 
安全问题涉及的内容: 
共享数据。 
是否被多条语句操作。 
这也是判断多线程程序是否存在安全隐患的依据。 

解决安全问题的方式: 
  Java中提供了一个同步机制。 
  解决原理:让多条操作共享数据的代码在某一时间段,被一个线程执行完,在执行过程中,其他线程不可以参与运算。 
   
  同步格式: 
  同步代码块: 
  Synchronized(对象){ 
  需要被同步的代码 
  } 
   
  同步的原理:通过一个对象锁,将多条操作共享数据的代码进行了封装并加锁。只有持有这个锁的线程才有机会进入同步中的去执行,在执行期间,即使其他线程获取到执行权。因为没有获取到锁,所以只能在外面等。只有同步中的线程执行完同步代码块中的代码, 
  出同步代码块时,才会释放这个锁,那么其他程序线程才有机会去获取这个锁,并只能有一个获取到而且进入到同步中。 
   
  同步的好处: 
  同步的出现解决了多线程的安全问题 
  同步的弊端: 
  因为多个线程每次都要判断这个锁,所以效率会降低。 
   
  加入了同步,安全问题有可能依然存在, 
  因为同步是有前提的: 
  1,同步必须是有两个或两个以上的线程才需要同步 
  2,必须要保证多个线程使用的是同一个锁,才可以实现多个线程被同步。 
  如果出现加上同步,安全问题依然存在,就按照两个前提来排查问题。 

如何查看程序中有无线程安全隐患?{ 
1,查看线程代码中是否有共享数据。 
2,这个共享数据有没有被多条语句所操作。 


同步代码块时用于封装代码的,函数也用于封装代码,不同之处是同步带有 
锁机制。那么如果让函数这个封装体具备同步的特性。就可以取代同步代码块。 
同步函数:就是在函数上加上synchronized关键字。Public synchronized void add(); 

那么同步函数用的哪个锁?{ 
  在同步代码块中用的是任意一个对象用来加锁,而同步函数中的锁用的是当前对象 
  ,也就是this 


同步函数和同步代码块的区别:{ 
同步代码块使用的锁可以是任意对象, 
同步函数使用的锁是固定对象,this 
所以一般定义同步时,建议使用同步代码块。 


静态同步函数用的是哪个锁?{ 
  静态同步函数使用的锁不是this, 
  因为静态函数中不可以定义this。 
   
  静态随着类的加载而加载,这时有可能内容还没有该类的对象。 
  但是一个类加载进内存,会先将这个类对应的字节码文件封装成对象, 
  该对象的表示方式  类名.class 代表一个类字节码文件对象, 
  该对象在内存是唯一的。 
   
  静态同步函数使用的锁就是,该类对应的字节码文件对象,也就是类名.class 


死锁: 
死锁经常出现的状况为:同步嵌套。 

class Test implements Runnable 

private boolean flag; 
Test(boolean flag) 

this.flag = flag; 

public void run() 

if(flag) 

while(true) 

synchronized(MyLock.locka) 

System.out.println(Thread.currentThread().getName()+"...if......locka"); 
synchronized(MyLock.lockb) 

System.out.println(Thread.currentThread().getName()+"...if......lockb"); 





else 

while(true) 

synchronized(MyLock.lockb) 

System.out.println(Thread.currentThread().getName()+"...else..........lockb"); 
synchronized(MyLock.locka) 

System.out.println(Thread.currentThread().getName()+"...else..........locka"); 









class MyLock 

public static Object locka = new Object(); 
public static Object lockb = new Object(); 



class DeadLockTest 

public static void main(String[] args) 

Test t1 = new Test(true); 
Test t2 = new Test(false); 

Thread th1 = new Thread(t1,"小强"); 
Thread th2 = new Thread(t2,"旺财"); 

th1.start(); 
th2.start(); 



线程间通信: 
Wait():让线程等待,将线程存储到一个线程池中。 
Notify():唤醒被等待的线程,通常都唤醒线程池中的第一个,让被唤醒的线程处于临时阻塞状态。 
notifyAll():唤醒所有的等待线程,将线程池中的所有线程都唤醒,让他们从冻结状态转到临时阻塞状态。 

这三个方法用于操作线程,但是都定义在Object类中: 
因为,这三个方法在使用的时候,都需要定义在同步中,要明确这些方法所操作的线程所属于锁。例如A锁被wait的线程,只能被A锁的notify方法唤醒。 
所以必须要表示wait,notify方法所属的锁对象,而锁对象可以是任意的对象。可以被任意的对象调用的方法肯定定义在Object类中。 

**等待唤醒机制,通常都用在同步中,因为需要锁的支持。而且必须要明确wait,notify锁作用的锁对象。 

在java.util.concurren.locks包中提供了一个接口Lock。代替了synchronized。 
Synchronized使用的隐式锁 
Lock使用的是显示的锁。 
Lock()获取锁 
Unlock()释放锁 

还有一个对象Condition。 
该对象的出现替代了Object中的wait,notify,notifyAll这些操作监视器的方法 
替代后await,signal,signalAll。 

**新功能最大的好处就是在一个lock锁上可以添加多组监视器对象,这样就可以实现 
本方只唤醒对方的现象。 

Sleep和wait的区别?{ 
1,sleep方法必须指定时间 
2,wait方法有重载形式,可以指定时间,也可以不指定 

对于执行权和锁的操作: 
1,sleep():释放执行权,不释放锁,因为肯定能恢复到临时阻塞状态 
2,wait():释放执行权,释放锁,因为wait不释放锁,如果没有时间指定,那么其他线程都进行不了同步中,无法将其唤醒。 

(同步中可以有多个存活的线程,但是只能有一个执行同步的代码,因为只有一个线程会持有同步锁,只有当该线程释放了锁,其他线程才有机会获取到锁,而且只能用一个线程获取到锁,继续执行。) 


如何让线程停止? 
1,使用Thread类中的stop方法。(强制结束,已过时) 
2,线程执行的代码结束。 

通常定义线程代码都有循环,因为需要单独开辟一个执行路径去重复很多事情。既然有循环,控制住循环,就可以结束run方法。 
定义标记可以结束线程,但是如果线程在运行过程中处于了冻结状态,没有执行到标记,这时,可以通过Thread类中的interrupt方法中断线程的冻结状态。强制让其恢复到运行状态中来,就可以有机会执行标记,但是这种强制动作会发生异常。 

setDeamo(boolean):可以将线程标记为后台线程。 
线程分前台和后台两种。运行方式都一样都会获取cpu的执行权执行 
不同的在于,结束的方式不同。 
前台线程只有run方法结束,才结束。 
后台线程,run方法结束,结束,还有,如果run方法没结束,而前台线程都结束了,后台线程也结束。 
所以一个进程是否结束参考的是,是否还有前台线程存活,如果前台线程都结束了,那么进程也就结束了。


当有了join方法以后,该t1线程会全部执行完,其他线程才可以执行。 
Join:临时加入一个线程进行执行。 
例如,当主线程获取到了cpu的执行权,执行时,执行到了A线程的join方法。 
这时就知道A线程要加入进来执行,那么A执行就需要cpu的执行权。而这时cpu的执行权在主线程持有,主线程会释放自己的执行权。让A线程进行执行,只有等A线程执行完以后,主线程才会执行,此时主线程就处于冻结状态。 

一般使用情况:当在线程执行过程中,需要一个运算结果时,可以通过加入一个临时线程,将该结果进行运算,这时需要结果的线程处于冻结状态,当加入的线程执行完,该线程在继续执行。 
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值