一、线程的五种基本状态
1.新建状态(New)
创建一个线程对象后,该线程对象就处于新建状态。此时它不能运行,仅仅是Java虚拟机为其分配了内存。
2.就绪状态(Runnable)
当线程对象调用的start()方法后,该线程就进入就绪状态。处于就绪状态的线程位于线程队列中,等待系统的调度以获得CPU的使用权。
3.运行状态(Running)
如果处于就绪状态的线程获得了CPU的使用权,并开始执行run()方法中的线程执行体,那么该线程处于运行状态。(只有处于就绪状态的线程才可能转换到运行状态。)
注意,一个线程启动后,可能不会一直处于运行状态,当运行状态的线程使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。
4.阻塞状态(Blocked)
一个正在执行的线程在某些特殊情况下(如,被人为挂起或执行耗时的输入输出操作时),会让出CPU的使用权并暂时终止自己的执行,进入阻塞状态,直至引起阻塞的原因被消除转入就绪状态。
常见的线程运行状态变阻塞状态的原因:
- 当线程试图获取的对象正被其他线程所持有时,
- 当线程调用了一个阻塞式的I/O方法时,
- 当线程点用了某个对我的wait()方法时,
- 当线程调用了Thread的sleep(long millis)方法时,
- 当在一个线程中调用了另一个线程的join()方法时...
5.死亡状态(Terminated)
如果线程调用stop()方法或run()方法正常执行完毕,或者线程抛出一个未捕获的异常、错误,线程就进入死亡状态。一但进入死亡状态,该线程将不再拥有运行的资格,也不能再转换到其他状态。
二、多线程的创建和启动
在Java中提供了两种多线程实现方式:一种是继承java.lang包下的Thread类重写Thread类的run()方法,在run()方法中实现运行在线程上的代码;另一种是实现java.lang.Runnable接口,同样是在run()方法中实现运行在线程上的代码。
1.继承Thread类方法
适用于不同资源对应不同的线程
创建一个class继承Thread类,并重写Thread类中的run()方法便可实现多线程。
class MyThread extends Thread{
public void run(){
线程代码
}
}
在使用多线程时,在主函数中,需要先创建一个对应的线程对象,然后开启线程。
public static void main (String[] args){
MyThread myThread = new MyThread;
myThread.start();
主函数代码
}
2.实现Runnable接口方法
适用于同一资源对应不同的线程
当通过Thread( Runnable target)构造方法创建线程对象时,只需要为该方法传递一个实现类Runnable接口的实例对象即可。
class MyThread implements Runnable{
public void run(){
线程代码
}
}
而在主函数在调用时,需要先创建一个MyThread的实例对象,然后创建线程对象,再开启线程即可。与前面有所不同,注意区分。
public static void main (String[] args){
MyThread myThread = new MyThread();
Thread thread = new thread(myThread);
thread.start();
主函数代码
}
三、线程的同步
多线程虽然可以提高程序的效率,但是当多个线程去访问同一个资源时,可能会导致一些安全问题,例如同一资源被非正常利用,这时,我们便需要用线程的同步来限制某个资源在同一时刻只能被一个线程访问。
1.synochronized
当多个线程使用同一个共享资源时,可以将处理的代码放在一个使用synchronized关键字修饰的代码块中,这个代码块称为同步代码块。语法格式如下:
synchronized(lock){
操作共享资源代码块
}
其中,lock是一个锁对象,它是同步代码块的关键,可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的。当某一个线程执行同步代码块时,其他线程将无法执行当前同步代码块,会发生阻塞,等当前线程执行完同步代码块后,所有线程再开始抢夺线程的执行权。
另外,使用同步方法也可以实现与同步代码块相同的功能,即在方法前面使用synchroniaed关键字来修饰,语法格式如下:
synchronized 返回值类型 方法名 ([参数1,...]){ 方法体}
2.死锁
两个线程在运行时都在等待对方的锁,这样便造成了程序的停滞,这种现象称为死锁。
四、多线程通信
1.线程的同步机制
Java线程的同步机制是一套用于协调线程之间的数据访问和活动的机制,旨在保障线程安全和实现线程的共同目标。以下是Java中几种常见的同步机制:
- synchronized关键字:这是Java中最常用的同步机制,通过在方法或代码块前加上synchronized关键字来实现。它可以确保同一时间只有一个线程能够访问被同步的代码,从而保护共享资源不被多个线程同时修改。
- Lock接口:Java提供了java.util.concurrent.locks.Lock接口,其实现类如ReentrantLock,它提供了比synchronized更灵活的锁机制。Lock接口允许线程显式地获取和释放锁,从而更好地控制并发访问。
- volatile关键字:volatile关键字用于修饰共享变量,它可以保证变量的内存可见性,即一个线程对变量的修改对其他线程是立即可见的。这有助于解决多线程编程中的可见性问题。
- 原子类:Java提供了一些原子类,如AtomicInteger、AtomicLong等,它们通过使用硬件支持的原子操作来确保操作的原子性,即一个操作在执行过程中不会被其他线程中断。
- 线程安全的集合类:Java中的java.util.concurrent包提供了一些线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,它们通过使用内部锁或CAS(比较并交换)操作来实现线程安全。
- 信号量(Semaphore):Semaphore信号量是一种同步工具,它可以控制同时访问某个资源的线程数量。通过acquire()和release()方法来获取和释放信号量,从而实现线程同步。
- 循环屏障(CyclicBarrier):CyclicBarrier循环屏障是一种同步工具,它可以等待多个线程都到达某个特定点,然后继续执行。通过await()方法来等待其他线程,从而实现线程同步。
综上所述,Java提供了多种同步机制,开发人员可以根据具体的需求和场景选择合适的同步方式。通过这些同步机制,可以有效地防止多线程编程中的资源冲突和数据不一致问题,确保程序的正确性和性能。
2.等待与唤醒
Java线程的等待与唤醒机制是多线程编程中用于协调线程间通信的重要手段。以下是对这一机制的全面总结:
- 基本概念:
- 等待(wait)方法:当一个线程执行到某个条件不满足时,它可以调用wait方法来暂停自己的执行,并释放所持有的锁。
- 唤醒(notify)方法:其他线程可以通过调用notify方法来唤醒等待中的线程。
- 使用场景:
- 生产者-消费者问题:一个线程负责生产数据,另一个线程负责消费数据,它们通过等待和唤醒机制来协调工作。
- 线程同步:在多个线程共享某些资源时,通过等待和唤醒机制来确保资源的正确使用顺序。
- 实现方式:
- 使用Object类的wait和notify方法:这两个方法是Java中实现线程等待与唤醒的基本方法。
- 使用Lock和Condition接口:通过Lock接口获取锁,然后使用Condition接口的signal或signalAll方法来唤醒线程。
- 使用等待唤醒工具类(LockSupport):LockSupport提供了一些方法,如park和unpark,它们可以实现线程的等待和唤醒。
注意事项:
等待方法会释放锁,而唤醒方法不会立即释放锁,而是在适当的时候释放。
线程在等待时,如果被中断,可能会抛出InterruptedException异常。
可以通过设置等待时间来避免无限期的等待。
通过上述总结,我们可以看到Java线程的等待与唤醒机制是多线程编程中不可或缺的一部分,它能够帮助我们实现线程间的通信和同步,从而提高程序的效率和稳定性。
五、线程控制基本方法
1.线程休眠
如果希望人为地控制线程,使正在执行的线程暂停,将CPU让给别的线程,可以使用静态方法sleep(long millis)让当前正在执行的线程暂停一段时间,进入休眠等待状态。
注意,使用sleep(long millis)方法时会抛出InterruptedException异常,因此在调用时应该捕获异常。另外,sleep()是静态方法,只能控制当前正在运行的线程休眠,休眠时间结束后,线程会返回就绪状态,而不是立即开始运行。
2.中断线程
我们可以通过设置一个标志位来中断线程。当任务或者线程运行的时候先判断标志位的状态,如果是已经关闭那么这个任务或者线程就直接结束。这个标志位需要用volatile关键字修饰,以确保修改后其他线程立即可见。
另外,通过调用Thread.interrupt()方法,可以设置线程的中断标志位为true。这个方法并不是直接中断线程,而是通过改变标志位,让线程自己根据标志位和时机,灵活地决定要不要退出线程。如果线程被阻塞,调用interrupt()方法会抛出InterruptedException异常,并且会清除线程的中断标志位。
3.线程插队
在Thread类中提供了一个join()方法来实现插队,当在某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后它才会继续运行。
4.线程让步
线程让步是指正在执行的线程在某些情况下将CPU资源让给其他线程执行。可以通过yield()方法来实现,当某个线程调用yield()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。
Thread.yield();
5.线程的优先权
如果要对线程进行调度,最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大,我们可以通过Thread类的setPriority(int newPriority)方法来设置线程的优先级,数字(1~10)越大,优先级越高。
不过,不同的操作系统对优先级的支持是不一样的,可能不会与Java中的线程优先级一一对应,因此,只能把线程优先级作为一种提高程序效率的手段。
6.守护线程
在Java中,可以通过调用线程对象的setDaemon(true)方法来将一个线程设置为守护线程。这必须在线程启动之前进行,一旦线程启动,就不能再更改其守护状态。
注意,守护线程的主要用途包括执行一些低优先级的任务,如垃圾回收、对象销毁回收资源、日志监控等。它们不适合执行读写操作或计算逻辑,因为它们可能在操作未完成时JVM就退出了。
7.线程终止
标签:同步,Java,Thread,线程,JavaSE,多线程,方法 From: https://blog.csdn.net/m0_62823517/article/details/141502471
- 正常运行结束:线程执行完毕后自然结束,这是最理想的终止方式。
- 使用退出标志退出:通过设置一个布尔型变量作为退出标志,线程在每次迭代或检查点检查该标志,从而决定是否退出。
- 使用Interrupt方法结束线程:Java提供了Interrupt方法来请求终止线程。它不会立即停止线程,而是设置一个中断标志位,线程可以定期检查这个标志位并优雅地结束运行。
- 被动终止:在同一进程中其他线程调用pthread_cancel函数。任意线程调用了exit、_Exit、_exit 导致整个进程终止,又或者主线程在main函数中执行return语句都会导致进程中的所有线程立即终止。
需要注意的是,在终止线程时,应该避免使用过时且不安全的stop方法,因为它可能导致业务逻辑不完整或破坏原子性操作。此外,在使用Interrupt方法时,应该通过捕获异常或检查中断状态来响应中断请求,而不是直接调用stop方法。