文章目录
参考文章:多线程
进程与线程
- 进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。
- 多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU执行速度非常快,使得所有程序好像是在同时运行一样。
- 多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。
- 所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。
- 线程与进程的不同之处是,每个进程拥有自已的一套变量,而线程却可以共享数据。
实现多线程
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现Runnable接口。而继承Thread类也是要重写Thread的run方法,目前已经不推荐使用。在此只介绍实现Runable接口的方法。
实现Runnable接口,需要重写Runnable接口中定义的run方法,run方法也就是调用线程时会执行的方法。
class ThreadWork1 implements Runnable { public void run() { while (true) { System.out.println("线程类1"); } } } class ThreadWork2 implements Runnable { public void run() { while (true) { System.out.println("线程类2"); } } } public class TreadDemo { public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ThreadWork1 t1 = newThreadWork1() ; ThreadWork2 t2 = new ThreadWork2() ; Thread thread1 = new Thread(t1); //用实现了Runnable接口的对象,初始化线程类 Thread thread2 = new Thread(t2); thread1.start(); thread2.start(); } }观察代码输出可看到交替输出线程类1和线程类2,说明两个线程在同时执行。
线程的状态变化
要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。下面分别介绍一下这几种状态:
创建状态 在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread类的构造方法来实现,例如
“Thread thread=new Thread()”。就绪状态 新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待CPU 服务,这表明它已经具备了运行条件。
运行状态 当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run()方法定义该线程的操作和功能。
阻塞状态 一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep()方法,线程将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
死亡状态 线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
线程休眠(Sleep)
- 在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。
- 请注意Thread.sleep() 是Thread类的静态方法,会休眠当前进程,想让特定的线程休眠应将Thread.sleep() 方法写到该线程对应的run方法中。
加入Thread.sleep()后代码:
public class TreadDemo { public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ThreadWork1 t1 = new ThreadWork1() ; ThreadWork2 t2 = new ThreadWork2() ; Thread thread1 = new Thread(t1); //用实现了Runnable接口的对象,初始化线程类 Thread thread2 = new Thread(t2); thread1.start(); thread2.start(); } } class ThreadWork1 implements Runnable{ @Override public void run() { while (true) { System.out.println("线程类1"); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class ThreadWork2 implements Runnable{ @Override public void run() { while (true) { System.out.println("线程类2"); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }代码输出:
线程中断(Interrupt)
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
public class TreadDemo { public static void main(String[] args) throws InterruptedException { ThreadWork1 t1 = new ThreadWork1() ; Thread thread1 = new Thread(t1); //用实现了Runnable接口的对象,初始化线程类 thread1.start(); thread1.interrupt(); } } class ThreadWork1 implements Runnable{ public void run() { while (true) { System.out.println("线程类1"); } } }这段代码运行将会发现,线程类1被不停的输出。
这是因为interrupt()方法其实只是将该线程的中断状态置为true,并不是直接停止该线程,我们应配合run方法
通过Thread.currentThread().isInterrupted()可检测当前进程是否被中断。
这样我们修改ThreadWork1class ThreadWork1 implements Runnable{ public void run() { while (!Thread.currentThread().isInterrupted()) 检测是否被中断 { System.out.println("线程类1"); } } }线程成功中断,跳出循环,run方法执行完毕进入死亡状态。
休眠与中断
- 特别的是,当线程调用休眠方法
sleep()时,会先检查中断状态,如果线程处于中断状态(即之前调用了interrupt()方法),则会清除这一状态,并抛出InterruptedException异常。- 所以当run方法中总是含有休眠方法
sleep(),就可以不用Thread.currentThread().isInterrupted()来判断线程是否被中断,也可以通过捕获InterruptedException异常来判断线程是否被中断。
线程优先级 (Priority)
优先级相关API
线程的礼让(yield)
在线程操作中,可以使用
yield()方法将一个线程的操作暂时让给其他线程执行,yield()方法也是静态方法需要在线程对应的run方法中使用。
使用yield()方法会使当前进程从运行态变为阻塞态。
守护线程(Daemon)
在 Java 程序中,只要有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个守护线程。守护线程为其他线程提供服务,这样即使还有守护线程没有结束任务,JAVA进程也会结束守护进程并结束JAVA进程,直接使用
setDaemon() 方法即可。public static void main(String[] args) throws InterruptedException { ThreadWork1 t1 = newThreadWork1() ; ThreadWork2 t2 = new ThreadWork2() ; Thread thread1 = new Thread(t1); //用实现了Runnable接口的对象,初始化线程类 Thread thread2 = new Thread(t2); thread1.setDaemon(true); 设置为守护线程 thread1.start(); thread2.start(); } } class ThreadWork1 implements Runnable { public void run() { for (int i = 0; i < 50000; i++) { System.out.println("111:"+i); } } } class ThreadWork2 implements Runnable{ public void run() { for (int i = 0; i < 500; i++) { System.out.println("222:"+i); } } }代码输出:
可发现,线程1还没有执行完成,JAVA进程直接结束了。
资源同步
一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。
如下代码,没有进行同步问题的处理:
class MyThread implements Runnable{ private int ticket = 50 ; // 假设一共有50张票 public void run(){ for(int i=0;i<100;i++){ if(ticket>0){ // 还有票 System.out.println("卖票:ticket = " + ticket-- ); } } } } public class TreadDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定义线程对象 Thread t1 = new Thread(mt) ; // 定义Thread对象 Thread t2 = new Thread(mt) ; t1.start() ; t2.start() ; } }输出:
可以看到输出并不是50递减到1。
这是因为有可能第一个线程将ticket减一后,还没来得及覆盖ticket的值,就被停止了线程。
第二个线程修改了ticket的值后,线程一又获得了运行权,继续操作直接覆盖了ticket的值。
使得线程二的操作直接被覆盖掉。
锁(lock)
可以通过给访问同步资源的代码段加锁来防止这种情况的发生:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class MyThread implements Runnable{ private int ticket = 50 ; // 假设一共有50张票 private Lock ticketLock = new ReentrantLock(); 锁锁锁锁锁锁锁锁锁锁锁锁锁锁锁锁锁锁锁锁 public void run(){ ticketLock.lock(); 加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁加锁 try { for(int i=0;i<100;i++){ if(ticket>0){ // 还有票 System.out.println("卖票:ticket = " + ticket-- ); } } } finally { ticketLock.unlock(); 解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁解锁 } } } public class TreadDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定义线程对象 Thread t1 = new Thread(mt) ; // 定义Thread对象 Thread t2 = new Thread(mt) ; t1.start() ; t2.start() ; } }代码输出:
这样线程执行代码前需要加锁,如果已经被其他线程加锁,则无法对同步资源进行访问。将解锁放在finally里>面防止出现异常时跳过代码段导致锁无法释放。
条件(condition)
当一个线程获得了锁,但没有达成某种条件而导致无法继续进行操作可以使用Condition
比如有两个线程售票,和退票。当售票线程进入临界区(访问同步资源)发现票数=0,无法继续当前操作,希望等待退票线程使票数增加。然而在访问临界区时已经锁住了,退票线程无法访问临界区。
这种情况我们可以加入条件,条件隶属于某个锁,比如上述的有余票条件是隶属于票数锁的:
Condition hasTicketCondition = new TicketLock.newCondition这样我们在售票线程进入临界区(访问同步资源)发现票数=0时可以使用
hasTicketCondition.await();使得当前进程进入阻塞状态,并将此进程加入该条件的等待集合中当退票线程使票数增加后可以使用
hasTicketCondition.signalAll();来唤醒该条件的等待集合中的所有线程,去判断是否符合条件,可以继续任务。
同步(Synchronized)
也就是说,下面两种方式是等价的。
也就是说,通过Synchronized关键字,wait()方法,和notifyAll方法可以简化实现上述锁与条件的功能。
Synchronized关键字可以使代码更简洁。
死锁
死锁 : 当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
形成死锁需要四个条件:
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
在进行多线程编程时,一定要精心设计,破坏四个条件,避免发生死锁。
本文深入探讨Java中多线程的实现方式,包括进程与线程的区别,线程的生命周期,以及如何通过实现Runnable接口创建多线程。此外,还详细讲解了线程的休眠、中断、优先级调整、礼让、守护线程、资源同步锁、条件变量、死锁等高级主题。











1418

被折叠的 条评论
为什么被折叠?



