java多线程总结

本文详细介绍了Java线程的生命周期,包括新创建、可运行、等待、计时等待、被阻塞和被终止的状态。同时阐述了抢占式调度的原理及线程达到不活动状态的几种条件。此外,文章还探讨了线程终止的原因,线程的属性如优先级、守护线程、线程组以及处理未捕获异常的处理器。最后,对比了同步方法、Lock和Condition对象的区别与使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程的状态(或称为线程的几种生命周期):
1、新创建(new) 2、可运行(runnable) 3、等待(waitting) 4、计时等待(Timed Waitting) 4、被阻塞(Blocked) 5、被终止(Terminated)

抢占式调度/时间片调度的原理:一旦一个线程开始运行,它不必始终保持运行。事实上,运行中的线程被中断,目的是为了让其他线程获得运行机会。线程调度的细节依赖于操作系统提供的服务。抢占式调度系统给每一个可运行线程一个时间片来执行任务。当时间片用完,操作系统剥夺该线程的运行权,并给另一个线程运行的机会。当选择下一个线程时,操作系统考虑线程的优先级。

线程达到不活动状态(被阻塞或等待状态)的几种条件:
1、当一个线程试图获取一个对象的内部锁,而该内部锁被其他线程占用时,该线程进入阻塞状态。
2、当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。出现情况:调用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent库中的Lock或Condition时。
3、有几个方法有一个超时参数,调用他们导致进入计时等待状态。带有超时参数的方法:Thread.sleep、Object.wait、Thread.join、Lock.tryLock、Condition.await。

导致线程终止的两种原因:
1、因为run方法正常退出而自然死亡。
2、因为一个没有捕获的异常终止了run方法而意外死亡。

线程的属性:线程优先级、守护线程、线程组以及处理未捕获异常的处理器。

线程优先级:
默认状态:一个线程继承它父类的优先级。 线程优先级设置方法:setPriority方法,java中最小优先级为1,最大优先级为10。默认优先级为5  线程优先级高度依赖于系统。

守护线程:
将普通(用户)线程转换为守护线程的方法:调用t.setDaemon(true)。守护线程的唯一用途是为其他线程提供服务。在一个程序中当只剩下守护线程时,虚拟机就退出。

ThreadGroup类实现Thread.UncaughtExceptionHandler接口。它的uncaughtException方法做如下操作:
1、如果该线程组有父线程组,那么父线程组的uncaughtException被调用。
2、否则,如果Thread.UncaughtExceptionHandler方法返回一个非空的处理器,则调用该处理器。
3、否则,如果Throwable是ThreadDeath的一个实例,什么都不做。
4、否则,线程的名字以及Throwable的栈踪迹被输出到System.err上。

两种防止代码块受并发访问干扰的机制:1、synchronize关键字。2、javaSE5.0后引入的ReentrantLock类。

ReentrantLock锁的基本结构:
首先实例化一个锁对象:
private Lock myLock = new ReentrantLock();
用在相应的方法里
myLock.lock(); //a ReentrantLock object
try
{
   critical  section
}
finally
{
    myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown
}



条件对象的作用:
通常,线程进入临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能做有用工作的线程。
Class Bank
{
    private Condition sufficientFunds;
    ...
    public Bank()
    {
       ...
       sufficientFunds = bankLock.newCondition();
     }
}
在并发代码块中使用条件对象的实例:
public void transfer(int from,int to,int amount)
{
   bankLock.lock();
   try
   {
       while (accounts[from] < amount)
           sufficientFunds.await();
       //transfer funds
       ...
       sufficientFunds.singalAll();
     }
       finally
      {
          bankLock.unlock();
       }
}
总结:
锁和条件的关键之处:
  • 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
  • 锁可以管理试图进入被保护代码段的线程。
  • 锁可以拥有一个或多个相关的条件对象。
  • 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

synchronize关键字:从1.0版开始,java中的每一个对象都有一个内部锁。如果一个方法用synchronize关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。

换句话说,

public synchronized void method()
{
     method body
}
等价于

public void method()
{ 
    this.intrinsicLock.lock();
    try
    {
       method body
     }
     finally
     {
        this.intrinsicLock.unlock();
      }
}
内部对象锁只有一个相关条件。wait方法添加一个线程到等待集中,notifyAll/notify方法解除等待线程的阻塞状态。

注意:wait、notifyAll以及notify方法时Object类的final方法。Condition方法必须被命名为await、signalAll和signal以便它们不会与那些方法发生冲突。

内部锁和条件的局限:

1、不能试图中断获得锁的线程。

2、获得锁时不能设定超时。

3、每个所只有单一的条件可能是不够的。


在代码中应该使用哪种方法?Lock和Condition对象还是同步方法?

1、最好既不用Lock/Condition也不使用synchronized关键字。建议使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁。

2、如果synchronized关键字适合你的程序。那么尽量使用它。这样可以减少编写代码的数量,减少出错的几率。

3、如果特别需要Lock/Condition结果提供的独有特性时,才使用Lock/Condition。


监视器(monitor)的特性:

  • 监视器是只包含私有域的类。
  • 每个监视器类的对象有一个相关的锁。
  • 使用该锁对所有的方法进行加锁。
  • 该锁可以有任意多个相关条件。

java对象在哪几方面不同于监视器,使得线程的安全性下降?

  • 域不要求必须是private。
  • 方法不要求必须是synchronized。
  • 内部锁对客户是可用的。

在同步时,使用现代的处理器和编译器,出错的可能性很大的原因:

  • 多处理器的计算机能够暂时在寄存器或本地内存缓冲区中保存内存中的值。结果是,运行在不同处理器上的线程可能在同一内存位置取到不同的值。
  • 编译器可以改变指令执行的顺序以使吞吐量最大化。

volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域可能被另一个线程并发更新的。(个人理解:对一个变量声明为volatile后其他线程可以看到读取,但是不能编辑,并且其他线程看到的都是最新更新的值。)


除了使用锁或volatile修饰符,还有另外一种情况可以安全地访问一个共享域,即这个域声明为final时。其他线程会在构造函数完成构造之后才看到用final修饰的变量。

eg:final Map<String,Double> accounts = new HashMap<>();


读/写锁:

java.util.concurrent.locks包定义了两个锁类:RenntrantLock类和ReentrantReadWriteLock类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值