Java多线程
一、多线程实现方式
1.继承Thread类,重写run方法
1 2 3 4 5 6 7 8 9 10 11 12 | public class ThreadDemo extends Thread{<br> @Override public void run(){ //编写自己的线程代码 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args){ ThreadDemo threadDemo = new ThreadDemo(); threadDemo.setName( "我是自定义的线程1" ); threadDemo.start(); System.out.println(Thread.currentThread().toString()); } } |
优点 :代码简单 。 缺点 :该类无法集成别的类。
2.实现Runnable接口,重写run方法
通过实现Runnable接口,实现run方法,接口的实现类的实例作为Thread的target作为参数传入带参的Thread构造函数,通过调用start()方法启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class MyThreadRunable implements Runnable{ public static void main(String[] arg){ MyThreadRunable runable = new MyThreadRunable(); Thread myThread = new Thread(runable); myThread.start(); System.out.println( "step2" ); } @Override public void run() { System.out.println( "step1" ); try { sleep( 10000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } |
优点 :继承其他类。 同一实现该接口的实例可以共享资源。
缺点 :代码复杂
3.通过Callable和FutureTask创建线程
1)创建Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,且该 call() 方法有返回值 。
2)创建Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象, 该 FutrueTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class ThreadDemo { public static void main(String[] args) { Callable<Object> oneCallable = new Tickets<Object>(); FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable); Thread t = new Thread(oneTask); System.out.println(Thread.currentThread().getName()); t.start(); } } class Tickets<Object> implements Callable<Object>{ //重写call方法 @Override public Object call() throws Exception { System.out.println(Thread.currentThread().getName()); return null ; } } |
优点 :可以获得返回值
start() : 作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用,真正的实现了多线程并发运行。
run() : 只是类的一个普通方法而已,直接调用run方法的话,程序中依然只有主线程这一个线程,其程序执行路径还是要顺序执行,要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。
二、线程中的状态
java中,每个线程都需经历新生、就绪、运行、阻塞和死亡五种状态,线程从新生到死亡的状态变化称为生命周期。
用new运算符和Thread类或其子类建立一个线程对象后,该线程就处于新生状态。
新生—>就绪:通过调用start()方法。
就绪—>运行:处于就绪状态的线程一旦得到CPU,就进入运行状态并自动调用run()方法。
运行—>阻塞:处于运行状态的线程,执行sleep()方法,或等待I/O设备资源,让出CPU并暂时中止运行,进入阻塞状态。
阻塞—>就绪:睡眠时间已到,或等待的I/O设备空闲下来,线程便进入就绪状态,重新到就绪队列中等待CPU。当再次获得CPU时,便从原来中止位置开始继续运行。
运行—>死亡:(1)线程任务完成;(2)线程被强制性的中止,如通过执行stop()或destroy()方法来终止线程。
三、如何优雅的终止一个线程
线程终止有两种情况:线程的任务执行完成;线程在执行任务过程中发生异常。
1、使用stop()方法,已被弃用。原因是:stop()是立即终止,比较暴力,会带来数据不一致性,所以被废弃。
2、使用退出标志:实现一个Runnable接口,在其中定义volatile标志位,在run()方法中使用标志位控制程序运行,当一个变量被 volatile
修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
3、使用interrupt()中断的方式,使用interrupt()方法中断正在运行中的线程只会修改中断状态位,可以通过isInterrupted()判断。如果使用interrupt()方法中断阻塞中的线程,那么就会抛出InterruptedException异常,可以通过catch捕获异常,然后进行处理后终止线程。有些情况,我们不能判断线程的状态,所以使用interrupt()方法时一定要慎重考虑。
四、线程中sleep()和wait()有何区别
sleep():使当前线程暂停执行指定的一段时间,但监视状态依然保持,过了指定的时间会自行恢复运行状态。
wait():使当前线程暂停执行,同时释放对象监视器的所有权,直到另一个和它有相同对象监视器的线程调用notify()或者notifyAll()唤醒它,再恢复运行状态。
(1)sleep()不会释放资源,wait()会释放资源;
(2)sleep()是Thread类里的函数,wait()是Object类里的函数;
(3)sleep()可以在任何地方调用,wait()只能在同步方法或者同步代码块中调用(否则会抛IllegalMonitorStateException异常);
五、Runnable接口和Callable接口的区别
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
六、多线程同步
(1)synchronized关键字:synchronized有两种用法:synchronized方法和synchronized块
public synchronized void mutithreadAccess();
synchronized(syncObject){代码块}
当一个线程进入一个对象的synchronized()方法后,其他线程是否能够进入此对象的其他方法?
答案:其他线程可进入此对象的非synchronized修饰的方法。如果其他方法有synchronized修饰,都用的是同一对象锁,就不能访问。
如果其他方法是静态方法,且被synchronized修饰,是否可以访问?
答案:可以的,因为static修饰的方法,它用的锁是当前类的字节码,而非静态方法使用的是this,因此可以调用。
(2)Thread类的join()方法
join()方法是Thread中的一个public方法,它有几个重载版本:
1. join()
2. join(long millis) //参数为毫秒
3. join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
(3)wait()方法和notify()方法:当使用synchronized来修饰某个共享资源时,如果线程A1执行synchronized代码,另外一个线程A2也要同时执行同一对象的同一 synchronized代码时,线程A2将要等到线程A1执行完后,才能继续执行。这种情况下可以使用wait()方法和notify()方法。
在synchronized代码被执行期间,线程可以调用对象的wait方法,释放对象锁,进入等待状态,并且可以调用notify()方法或notify()方法通知正在等待的其他线 程。notify()方法仅唤醒一个线程(等待队列中的第一个线程)并允许它去获得锁,notifyAll()方法唤醒所有等待这个对象的线程并允许它们去获得锁。
(4)Lock
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
ReenreantLock类的常用方法有:
1. ReentrantLock() : 创建一个ReentrantLock实例
2. lock() : 获得锁
3.unlock() : 释放锁
七、一些编程题
1、编写一个有两个线程的程序,第一个线程用来计算2~100000之间的素数的个数,第二个线程用来计算100000~200000之间的素数的个数,最后输出结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package classtest; public class ThreadSushu extends Thread{ int i,j,x= 0 ; ThreadSushu ( int m, int n){ this .i=m; this .j=n; } public static void main(String[] args) { ThreadSushu thread1= new ThreadSushu( 2 , 100000 ); ThreadSushu thread2= new ThreadSushu( 100000 , 200000 ); thread1.start(); thread2.start(); } public void run(){ int p,q; p= 0 ;q= 0 ; for ( int m=i;m<=j;m++) { for ( int h= 1 ;h<=m;h++) { q=m%h; if (q== 0 )p=p+ 1 ; } if (p== 2 ) { x=x+ 1 ; } p= 0 ; } System.out.println( "输出" +i+ "到" +j+ "之间的质数个数:" +x); } } |
2、设计2个线程,定义一个变量j,初始值为0。其中第1个线程每个循环对j增加1,循环1000次。另外1个线程对j每次减少1,循环1000次。保证线程安全,最终两个线程执行完j的值仍然是0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package classtest; public class Thread2 extends Thread{ static int j= 0 ; static Object lock= new Object(); int flag= 0 ; Thread2( int flag){ this .flag=flag; } public static void main(String[] args) throws InterruptedException { Thread2 thread1= new Thread2( 1 ); Thread2 thread2= new Thread2( 2 ); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(j); } public void run(){ for ( int i= 0 ;i< 100000 ;i++) { synchronized (lock){ if (flag == 1 ) { j=j+ 2 ; } else { j=j- 1 ; } } } } } |
3、假如新建T1、T2、T3三个线程,同时启动,如何保证它们按顺序执行?也就是先执行T1,执行完后,再执行T2,执行完后,再执行 T3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package classtest; public class JoinTest { public static void main(String[] args) { Thread t1= new Thread( new Work( null )); Thread t2= new Thread( new Work(t1)); Thread t3= new Thread( new Work(t2)); t1.start(); t2.start(); t3.start(); } static class Work implements Runnable { private Thread t; public Work(Thread t) { this .t = t; } public void run() { if (t != null ) { try { t.join(); System.out.println( "thread start:" +Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println( "thread start:" + Thread.currentThread().getName()); } } } } |
4、写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package classtest; public class TwoThread { public static void main(String args[]){ MyObject1 my = new MyObject1(); new Thread( new Runnable(){ @Override public void run() { // TODO Auto-generated method stub for ( int i = 0 ; i < 26 ; i++){ my.printNum(); } } }).start(); new Thread( new Runnable(){ @Override public void run() { // TODO Auto-generated method stub for ( int i = 0 ; i < 26 ; i++){ my.printA(); } } }).start(); } } class MyObject1{ private static boolean flag = true ; public int count = 1 ; public synchronized void printNum(){ while (flag == false ){ try { this .wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.print(( 2 *count- 1 )); System.out.print( 2 *count); flag = false ; this .notify(); } public synchronized void printA(){ while (flag == true ){ try { this .wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.print(( char )(count+ 'A' - 1 )); count++; flag = true ; this .notify(); } } |
5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package classtest; public class TenThread { public static class SumThread extends Thread{ int forct = 0 ; int sum = 0 ; SumThread( int forct){ this .forct = forct; } @Override public void run() { for ( int i = 1 ; i <= 10 ; i++){ sum += i + forct * 10 ; } System.out.println(getName() + " " + sum); } } public static void main(String[] args) { int result = 0 ; for ( int i = 0 ; i < 10 ; i++){ SumThread sumThread = new SumThread(i); sumThread.start(); try { sumThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } result = result + sumThread.sum; } System.out.println( "result " + result); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义