多线程



一、什么是多线程?
1.进程和线程的区别
进程:就是正在运行中的程序,如:QQ,360卫士。进程对应的就是应用程序在内存中所处的空间。
线程:就是进程中一个负责程序执行的控制单元(也叫执行路径)。程序的执行方式如果从头到尾只有一条执行路径,
就叫单线程;如果程序除了一条叫主线程外,还有很多子线程在同时工作,就叫多线程。
  *数据共享
     程是一个实体,每个进程都有自己独立的状态和自己的专用数据段。线程则共享数据,同一个程序中的所有线程
  只有一个数据段,线程间可能互相影响,比如数据访问的互斥和同步。
  *存储位置
     线程本身的数据通常只有i寄存器数据和程序执行时的堆栈段,所以线程的切换比进程的负担要小。线程不能自
  动运行,必须跻身在某一进程中,由进程触发。一个进程可以有多个线程且进程间不共用线程。
  *执行过程
     进程是不直接执行的,它只是分配该程序的内存空间;而线程负责执行,负责进程中内容执行的控制单元,也
  称为执行路径。(执行)


二、线程的创建方式
1.继承Thread类
    1).继承Thread类,并覆写类中的run()方法。
    2).创建子类的对象,调用start()方法启动线程。
    *子类继承了Thread类后,这个类就可以用于多线程的控制,并可以调用Thread类中的方法
    *此时子类创建的每一个对象,都是一个线程,创建多个对象就是创建多个线程
    *如果要对线程进行访问,可以通过创建的对象,调用start()方法
    *一个线程被重复启动的时候就会抛出此异常:IllegalThreadStateException


    1.1调用run和调用start有什么区别?
    调用run方法,其实是调用父类Thread类中的run方法,只会执行单线程即主线程。只有调用start方法开启,
才能调用继承的线程类所复写的run方法来执行自定义的线程的任务代码,而且主线程也会启动。也就是说启动
start方法,就是启动多线程,多个线程彼此之间并发运行。
    1.2Thread类中的方法和线程名称
    线程的命名方式
        Thread-编号(命名方式)。当我们在创建线程对象的时候就已经完成了名称的定义。一创建,就编号。
   (主线程的名字就是main)
    
    怎么区分多个线程中,当前开启的是哪个线程
        通过方法static Thread currentThread()可以返回当前线程的名称。




2.实现Runnable接口(runnable 可运行的)
A.为什么需要Runnable接口?
  在Java里面为了解决多线程的单继承局限问题,所以也提供有一个Runnable接口
B.为什么可以通过实现Runnable来创建线程?
   为什么这样问呢,因为线程的启动是通过调用Thread类中的start方法,但此时并未
继承Thread而仅仅是实现了Runnable接口,那么将没有start方法可以启动线程。
C.Thread类也实现了Runnable接口
D.除了这个基本的联系之外,还有一点不算区别的小区别:使用Runnable接口实现的
多线程要比使用Thread类实现的多线程更容易表示出数据共享的概念。


优点:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将
任务封装成对象。
2.避免了java单继承的局限性
3. 如果继承了Thread类,那么会受到单继承局限,而且不方便表示出数据共享的概念,
Thread类是Runnable接口的子类;
所以创建线程的第二种方式较为常见
Runnable:它的出现仅仅是将线程的任务进行了对象的封装。
为什么不需要创建Thread的子类?
    此外,Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个 Thread 实例
并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。大多数情
况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这
很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
    因为我们继承Thread,覆写的run()方法实际上是来自于runnable,所以即使不想创建子类
也可以,


三、线程的状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程
    池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才
有机会转到运行状态。阻塞的情况分三种:
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()
          状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程的run方法执行完了或者因异常退出了run()方法,该线程结束生命周期。


四、线程的操作--优先级、线程睡眠、等待、让步、合并、唤醒
 2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平    台移植性好。
  3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
  4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
  5、线程合并:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
  6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实  现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方  式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
  类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
  注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。


五、线程安全分析
多个线程并发执行会带来安全问题,就好比搬家这项工作,虽然人多了干活会比较快,但是人多很容易损坏物件。在线程安全问题中,非常有名的问题就是银行取钱问题。
  银行的取钱流程是这样的:
  1.验证用户名和密码
  2. 用户输入取款金额
  3.系统判断余额是否大于取款金额,如果大于则取款成功;否则,取款失败。
  4. 系统更新账户余额


   这个流程看起来没有任何问题,但是如果放到多线程并发情况下,就有可能出现问题。比如:
   假设你的账户有1000元,当你在柜台成功取出1000元时(注意此时系统还没有将账户余额改为0,账户余额还是
为1000 )。同时你的女朋友在取款机也正在取款,取款机去查询账户余额,发现是1000元,于是又吐给你女朋
友1000元。然后柜台将你的账户余额更新为0,取款机随后也将你的账户余额更新为0。就这样,我们成功的在1000元
的账户里,取出了2000元。这显然是不合理的。
    之所以出现这种错误, 原因就是银行取钱这个操作是原子性的,不应该被打断。两个线程共同修改共享资
源--Account对象,造成了非同步的安全性问题。就像搬家一样,有几个哥们居然将冰箱拆开搬走了…… 与其
告诉他们,冰箱应该整个搬走,不如我们将冰箱整个儿锁起来。
   这个很好理解,synchronized好比是一个厕所,obj就是一把锁。每个上厕所的人办事之前,先把门儿锁上。
办完事把门儿打开,以便别人上厕所。
   同步监视器的作用:防止多个线程对共享资源进行并发访问。因为通常选取共享资源作为同步监视器。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值