线程


1、计算机如何执行程序?
加载程序后,以进程和线程体现
2、什么是进程?
进程是指在操作系统中正在运行的一个应用程序。
3、什么是线程?
线程是指进程内独立执行某个任务的一个单元。
4、进程和线程的区别?
1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文 ),一个进程至少包括一个线程。
3)线程是轻量级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
4)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源

线程的几种状态:

  • 新建状态(New):新创建了一个线程对象。
  • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
  • 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

线程创建方式:4种

  • 继承Thread:通过子类继承方式创建线程,简单 new Thread
  • (推荐)实现Runnable接口:java提供单继承,如果继承Thread,没法继承业务的类,改成实现接口,还可以利用继承。run()
  • 实现Callable接口:具有返回值,和异常,call()
  • 线程池ThreadPool:事先根据赋值参数,直接创建池,把参数个数创建对应个线程,在使用时,无需创建,线程销毁时,不用直接销毁,还回池中。

线程池的好处,节约线程创建和销毁时间,提高效率。

第一种创建线程方式存在两种不足:

  • 由于java是单继承的,这就导致在实际开发中我们往往需要继承某个类复用方法,
    而当前类又需要并发运行,导致不能同时又继承复用方法的类又继承线程
  • 定义线程的同时重写run方法定义任务,这就导致了线程和任务有一个必然的耦合关系。不利于线程的 重用。

死锁?

相互等待资源而产生的一种僵持状态,如果没有外力的干预将一直持续这个状态

( 当多个线程都持有自己的锁,但是都等对方先释放锁时 就会出现"僵持"的情况,使得所有线程进入阻塞状态。这个现象称为死锁现象。)

死锁产生的原因?

  系统资源不足、相互竞争资源、请求资源顺序不当

死锁的必要条件?

  • 互斥、不可抢占、循环等待、请求与保持
  • 怎么处理死锁?
  • 因为互斥是不可改变的,所以只能破坏其他三个条件中的一个来解除死锁,、方法:剥夺资源、杀死其中一个线程

线程优先级

  • 线程之所以能得以并发运行,是靠线程调度的工作,线程调度会分配CPU时间片给某个线程,使得其可以运行。线程不能主动获取CPU时间。
  • 调整线程的优先级可以最大程度的改善某个线程获取CPU时间片的次数,理论上线程优先级越高的线程获取CPU时间片的次数越多。 3)线程的优先级由数字1-10表示。
    1是最低优先级,10为最高优 先级,5为默认值。

线程提供的sleep阻塞方法:

static void sleep(long ms)
该方法可以让运行该方法的线程处于阻塞状态指定毫秒超时后线程会重新回到RUNNABLE状态,等待分配CPU 时间片再次运行。

守护线程

守护线程又称为后台线程(精灵线程),一个线程创建出来默认
都是普通(前台线程),守护线程需要在线程启动前 单独进行设置。
使用上守护线程与普通线程无差别,但是在结束时机上
有一点不同,即:当进程结束时,所有正在运行的守护线程都会被强制中断。
守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
java垃圾回收线程就是一个典型的守护线程
进程结束:当一个进程中的所有前台线程都结束时,进程即结束

join方法可以协调线程之间的同步运行。

  • join方法会让运行该方法的线程处于阻塞状态,直到该方法所属线程运行完毕才会解除阻塞。
  • 当一个方法被synchronized修饰后,那么该方法称为同步方法,即:多个线程不能同时在方法内部执行,从而解决了并发安全问题。
    在方法上使用synchronzied,那么同步监视器对象就是当前方法所属对象,即:方法内部看到的this
  • 同步块可以更准确的控制需要同步运行的代码片段。
    synchronized(同步监视器){
    需要同步运行的代码片段
    }

同步监视器是java中任意的一个对象,只要保证多个线程看到的该对象是"同一个",即可保证同步块中的代码是并发安全的。

互斥锁

当使用synchroinzed锁住多段不同的代码片段,但是
这些同步块使用的同步监视器对象是同一个时,那么这些
代码片段之间就是互斥的。多个线程不能同时执行他们。

线程池的主要作用:

1)控制线程数量
2)重用线程

线程在一定条件下,状态会发生变化。线程一共有以下几种状态:

  • 新建状态(New):新创建了一个线程对象。
  • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
  • 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
    阻塞的情况分三种:
    (1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
    (2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
    (3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
    线程变化的状态转换图如下:

注:拿到对象的锁标记,即为获得了对该对象(临界区)的使用权限。即该线程获得了运行所需的资源,进入“就绪状态”,只需获得CPU,就可以运行。因为当调用wait()后,线程会释放掉它所占有的“锁标志”,所以线程只有在此获取资源才能进入就绪状态。
下面小小的作下解释:
1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样, 当我们new了这个对象后,线程就进入了初始状态;
2、当该对象调用了start()方法,就进入就绪状态;
3、进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4、进入运行状态后情况就比较复杂了
4.1、run()方法或main()方法结束后,线程就进入终止状态;
4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CPU,然后就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源即调用sleep ()函数后,线程不会释放它的“锁标志”。)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配CPU时间片。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
4.4、当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入就绪状态,等待OS分配CPU时间片;
4.5. suspend() 和 resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。
4.6、wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
wait() 使得线程进入阻塞状态,它有两种形式:
一种允许指定以毫秒为单位的一段时间作为参数;另一种没有参数。前者当对应的 notify()被调用或者超出指定时间时线程重新进入可执行状态即就绪状态,后者则必须对应的 notify()被调用。当调用wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。waite()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronizedblock中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

注意区别:初看起来wait() 和 notify() 方法与suspend()和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的suspend()及其它所有方法在线程阻塞时都不会释放占用的锁(如果占用了的话),而wait() 和 notify() 这一对方法则相反。
上述的核心区别导致了一系列的细节上的区别
首先,前面叙述的所有方法都隶属于 Thread类,但是wait() 和 notify() 方法这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
其次,前面叙述的所有方法都可在任何位置调用,但是wait() 和 notify() 方法这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。
wait() 和 notify()方法的上述特性决定了它们经常和synchronized方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block和wake up 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂

线程间通信问题 。

关于 wait() 和 notify() 方法最后再说明两点:
第一:调用notify() 方法导致解除阻塞的线程是从因调用该对象的 wait()方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。
第二:除了notify(),还有一个方法 notifyAll()也可起到类似作用,唯一的区别在于,调用 notifyAll()方法将把因调用该对象的 wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。遗憾的是,Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值