关于Java线程,应该说两种基本的创建线程的方法是知道的,现成的集中状态也是知道。对于简单的锁同步也清楚。
还有第三种创建线程的方式不清楚,关于线程池这个概念不清楚? 总结前面两种方式,学习后面两个问题。 还有一个wait和sleep区别。
几个基本概念说清楚:
1:进程,线程
进程让操作系统并发成为可能,而线程让进程内部并发成为可能。
一个进程虽然包含多个线程,但是这些线程是共同享有进程占有的资源和地址空间。进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。
由于多个线程是共同占有所属进程的资源和地址空间,那么就会存在问题:所个线程同时访问进程的某个资源,怎么处理?
这个问题就是后序文章中要重点讲述的同步问题。
1、创建线程
在java中创建线程一般有两种方法,1)继承Thread类, 2)实现Runnable接口
1.1继承Thread类
1 package Lesson1218Thread; 2 3 public class MyThread extends Thread { 4 5 public static int num = 0; 6 public MyThread(){ 7 num++; 8 } 9 10 @Override 11 public void run() { 12 for(int i=0;i<10;i++){ 13 System.out.println("Thread ID " + this.getId() ); 14 } 15 } 16 17 } 18 package Lesson1218Thread; 19 20 public class ThreadDemo { 21 22 public static void main(String[] args) { 23 24 Thread thread1 = new MyThread(); 25 Thread thread2 = new MyThread(); 26 27 thread2.start(); 28 thread1.start(); 29 } 30 }
看上面例子,新建一个MyThread类继承于Thread, 通过start将thread准备就绪,什么时候占有CPU资源就开始run().
上面例子建立了两个线程,会同时抢占CPU资源,不同步。
1.2.通过实现Runnable接口去创建线程
通过Runnable去创建线程,必须实现run()函数。
1 package Lesson1218Thread; 2 3 public class MyRunnable implements Runnable { 4 5 @Override 6 public void run() { 7 8 //for(int i=0;i<10;i++){ 9 System.out.println("Thread name " + Thread.currentThread().getName()); 10 //} 11 } 12 13 } 14 15 package Lesson1218Thread; 16 17 public class ThreadDemo { 18 19 public static void main(String[] args) { 20 21 Thread thread1 = new MyThread(); 22 Thread thread2 = new MyThread(); 23 thread2.start(); 24 thread1.start(); 25 26 MyRunnable myRunnable = new MyRunnable(); 27 Thread thread3 = new Thread(myRunnable); //接口不可实例化,所以参数必须为实现接口的类或匿名类。下面有匿名类例子 28 Thread thread4 = new Thread(myRunnable); 29 30 System.out.println(thread3.equals(thread4)); //false 31 32 thread3.start(); 33 thread4.start(); 34 35 return; 36 } 37 }
运行结果:
false
Thread name Thread-2
Thread name Thread-3
Runnable的意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意这种方式必须将Runnable作为Thread类的参数来创建线程。然后通过Thread的start()方法来启动线程。
事实上查看Thread的源码,可以看到Thread也是实现了Runnable接口。 Thread有多个构造函数,可以传入以下参数,这个看具体要求。
从上面两种看。通过Runnable来创建线程是优于Thread, 类还可以去继承其他类。
1.3.通过匿名内部类来创建线程
上面两种方式是比较常见的创建线程的方法,但它们都有一个弊端,就是太麻烦,比如说项目里我就需要创建一个线程而已,难道还要创建一个类,然后再调用它吗,其实线程在项目中一般用下面这种简单的方式创建。
1 package Lesson1218Thread; 2 3 public class ThreadDemo { 4 5 public static void main(String[] args) { 6 new Thread(new Runnable(){ 7 8 @Override 9 public void run() { 10 System.out.println("通过内部类来创建Thread!! " + Thread.currentThread().getName()); 11 } 12 13 }).start(); 14 15 return; 16 } 17 18 }
运行结果:
通过内部类来创建Thread!! Thread-4
匿名内部类还要去学习。
1.4通过Callable和FutureTask创建线程
1>创建Callable接口的实现类,并实现call()方法
2>创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
3>使用FutureTask对象作为Thread对象的target创建并启动新的线程。
4>调用FutureTask对象的get()方法来获取线程执行结束后的返回值。
Callable是一个带有泛型的接口,里面只有一个抽象函数call(),带有返回值泛型。
1 public interface Callable<V> { 2 V call() throws Exception; 3 }
下面给出通过Callable和FutureTask来创建线程例子:
1 package Lesson1218Thread; 2 3 import java.util.concurrent.Callable; 4 5 public class MyCallable implements Callable<Object> { 6 7 @Override 8 public Object call() throws Exception { 9 System.out.println("hehe....."); 10 return 110; 11 } 12 } 13 14 package Lesson1218Thread; 15 16 import java.util.concurrent.ExecutionException; 17 import java.util.concurrent.FutureTask; 18 19 public class ThreadDemo { 20 21 public static void main(String[] args) { 22 23 FutureTask<Object> ft = new FutureTask<>(new MyCallable()); 24 new Thread(ft).start(); 25 try { 26 System.out.println(ft.get()); 27 } catch (InterruptedException | ExecutionException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 32 return; 33 } 35 }
运行接口输出:
false
hehe.....
110 //这个地方是返回值
使用接口实现线程的好处:
多个线程可共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
这个地方有一个问题,一直没有弄明白,如果哪位大牛清楚,希望帮忙解析下???
就是上面的例子,如果通过ft多new几个Thread,结果发现那些后面新建的线程都没有运行, 如下:
1 package Lesson1218Thread; 2 3 import java.util.concurrent.ExecutionException; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.FutureTask; 7 8 public class ThreadDemo { 9 10 public static void main(String[] args) { 11 FutureTask<Object> ft = new FutureTask<>(new MyCallable()); 12 new Thread(ft).start(); //new 两个Thread, 可是只有一个运行 13 new Thread(ft).start(); 14 try { 15 System.out.println(ft.get()); 16 } catch (InterruptedException | ExecutionException e) { 17 // TODO Auto-generated catch block 18 e.printStackTrace(); 19 } 20 return; 21 } 22 23 }
运行结果:
hehe.....
110
??????不知道为什么?????
再看到FutureTask的时候,发现接口居然可以多继承,以前一直以为Java就是单继承。出门看另一篇帖子:Java接口多继承。
FutureTask最后还是实现了Runnable接口。
1 public class FutureTask<V> implements RunnableFuture<V> {} 2 3 public interface RunnableFuture<V> extends Runnable, Future<V> { 4 /** 5 * Sets this Future to the result of its computation 6 * unless it has been cancelled. 7 */ 8 void run(); 9 }
Runnable和Callable接口的区别:
(1)Callable重写的方法是call(),Runnable重写的方法是run();
(2)Callable的任务执行后可返回值,而Runnable不能返回值;
(3)call方法可以抛出异常,run()不可以;
(4)运行Callable任务可以拿到一个future对象,表示异步计算的结果,它供检查计算是否完成的方法,以等待计算完成,并检索计算的结果。通过Future对象可以了解任务的执行情况,可取消任务的执行,还可以获取执行的结果。(对于这点不清楚,先复制在这)
1.5通过线程池来创建线程
对线程池确实不了解,我们先看一个例子
1 package lesson.threadDemo; 2 3 import java.util.concurrent.Callable; 4 5 public class MyCallable implements Callable<String> { 6 7 @Override 8 public String call() throws Exception { 9 System.out.println("hehe....."+Thread.currentThread().getName()); 10 11 return "110"; 12 } 13 14 } 15 16 package lesson.threadDemo; 17 18 import java.util.ArrayList; 19 import java.util.List; 20 import java.util.concurrent.ExecutionException; 21 import java.util.concurrent.Executor; 22 import java.util.concurrent.ExecutorService; 23 import java.util.concurrent.Executors; 24 import java.util.concurrent.Future; 25 import java.util.concurrent.FutureTask; 26 27 public class ThreadDemo { 28 29 public static void main(String[] args) { 30 MyCallable myCallable = new MyCallable(); 31 //不用线程池,用Callable和FutureTask创建函数 32 /*FutureTask ft = new FutureTask<>(myCallable); 33 34 new Thread(ft).start(); 35 36 try { 37 System.out.println(ft.get()); 38 } catch (InterruptedException | ExecutionException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 }*/ 42 43 //用线程池创建函数 44 ExecutorService es = Executors.newCachedThreadPool(); 45 List<Future<String>> fts = new ArrayList<Future<String>>(); 46 for(int i=0;i<5;i++) { 47 fts.add(es.submit(myCallable)); 48 } 49 50 for(Future<String> ft1:fts) { 51 try { 52 System.out.println(ft1.get()); 53 } catch (InterruptedException e) { 54 // TODO Auto-generated catch block 55 e.printStackTrace(); 56 } catch (ExecutionException e) { 57 // TODO Auto-generated catch block 58 e.printStackTrace(); 59 } 60 61 } 62 63 } 64 65 }
运行结果:
hehe.....pool-1-thread-2
hehe.....pool-1-thread-1
hehe.....pool-1-thread-3
110
110
110
hehe.....pool-1-thread-3
hehe.....pool-1-thread-4
110
110
从上面例子看,看不出现成是在哪儿启动的,也不知道怎么运行的。对其中几个类Executors和EcecutorService两个类不了解。
通过另一个例子看线程池如何创建线程:
1 package lesson.threadDemo; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class ThreadPool { 7 8 private static int POOL_NUM = 10; 9 10 public static void main(String[] args) { 11 12 ExecutorService es = Executors.newFixedThreadPool(6); 13 14 for(int i =0;i<POOL_NUM;i++) { 15 16 es.execute(new RunnableThread()); 17 } 18 } 19 } 20 21 22 class RunnableThread implements Runnable{ 23 24 @Override 25 public void run() { 26 System.out.println("hehe....." + Thread.currentThread().getName()); 27 } 28 29 }
运行结果:
hehe.....pool-1-thread-1
hehe.....pool-1-thread-4
hehe.....pool-1-thread-3
hehe.....pool-1-thread-6
hehe.....pool-1-thread-2
hehe.....pool-1-thread-6
hehe.....pool-1-thread-5
hehe.....pool-1-thread-1
hehe.....pool-1-thread-4
hehe.....pool-1-thread-3
下面再给出个线程池创建例子:
1 package Lesson1218Thread; 2 3 import java.util.concurrent.Callable; 4 5 public class MyCallable implements Callable<Object> { 6 7 @Override 8 public Object call() throws Exception { 9 System.out.println("MyCallable......" + Thread.currentThread().getName()); 10 return 110; 11 } 12 } 13 package Lesson1218Thread; 14 15 public class MyRunnable implements Runnable { 16 17 @Override 18 public void run() { 19 20 //for(int i=0;i<10;i++){ 21 System.out.println("MyRunnable...... " + Thread.currentThread().getName()); 22 } 23 } 24 25 package Lesson1218Thread; 26 27 import java.util.concurrent.Callable; 28 import java.util.concurrent.ExecutionException; 29 import java.util.concurrent.ExecutorService; 30 import java.util.concurrent.Executors; 31 import java.util.concurrent.Future; 32 import java.util.concurrent.FutureTask; 33 34 public class ThreadDemo { 35 36 public static void main(String[] args) { 37 MyRunnable myRunnable = new MyRunnable(); 38 //void execute(Runnable command); 39 ExecutorService es = Executors.newCachedThreadPool(); 40 es.execute(myRunnable); 41 es.execute(myRunnable); //每次都运行 42 43 MyCallable myCallable = new MyCallable(); 44 FutureTask ft = new FutureTask<>(myCallable); 45 es.execute(ft); 46 //es.execute(ft); //重复不运行,????? 47 //Future<?> submit(Runnable task); 48 //<T> Future<T> submit(Callable<T> task); 49 Future ft0 = es.submit(ft); //没有运行,不知道为啥?????? 猜测是前面有es.execute(ft); 50 Future ft1 = es.submit(myCallable); 51 52 Future ft2 = es.submit(myRunnable); 53 54 try { 55 System.out.println("ft0:"+ft0.get()); 56 System.out.println("ft1:"+ft1.get()); 57 System.out.println("ft2:"+ft2.get()); 58 } catch (InterruptedException e) { 59 // TODO Auto-generated catch block 60 e.printStackTrace(); 61 } catch (ExecutionException e) { 62 // TODO Auto-generated catch block 63 e.printStackTrace(); 64 } 65 66 return; 67 } 68 69 }
运行结果:
MyRunnable...... pool-1-thread-1
MyCallable......pool-1-thread-1
MyRunnable...... pool-1-thread-2
MyRunnable...... pool-1-thread-5
ft0:null
MyCallable......pool-1-thread-4
ft1:110
ft2:null
分析上面的运行过程:
ft2由于调的是Runnable,所以肯定没有返回值,ft0也是调的Runnable,所以也不会有返回值。并且在程序中也没有运行(至于为啥没有运行,猜测与line45冲突)。
Exetucor提供的创建线程池方法:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
线程池运行的两个方法:submit()和execute()
submit()方法,传递一个Callable,或Runnable,返回Future,如果传递的是Callable,Future里面才真正有值。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
execute()方法,传递Runnable,没有返回值。 如果是Callable也可以封装成FutureTask后传进去执行,但是没有返回值。
结论:
1:使用继承子Thread类的子类来创建线程类时,多个线程无法共享线程类的实例变量
2:采用Ruunable接口的方式创建多个线程可以共享线程类的实例变量,这是因为在这种方式下,程序创建的Runnable对象只是线程的target,而多个线程可以共享一个target,所以多个线程可以共享一个实例变量
2.Java中如何创建进程
进程的三个特点:
1:独立性:进程是系统中独立存在的实体,它可以独立拥有资源,每一个进程都有自己独立的地址空间,没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间。
2:动态性:进程和程序的区别在于进程是动态的,进程中有时间的概念,进程具有自己的生命周期和各种不同的状态。
3:并发性:多个进程可以在单个处理器上并发执行,互不影响。
并发性和并行性是不同的概念:并行是指同一时刻,多个命令在多个处理器上同时执行;并发是指在同一时刻,只有一条命令是在处理器上执行的,但多个进程命令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果
第一种方法:通过 Runtime 类的 exec() 方法来创建进程
public
class
Runtime
extends
Object
①、表示当前进程所在的虚拟机实例,每个Java应用程序都有一个Runtime类的Runtime ,允许应用程序与运行应用程序的环境进行接口。
②、由于任何进程只会运行与一个虚拟机实例当中,即只会产生一个虚拟机实例(底层源码采用 单例模式)
③、当前运行时可以从getRuntime方法获得。
1 package Lesson1218Thread; 2 3 import java.io.IOException; 4 5 public class ProcessDemo { 6 7 public static void main(String[] args) throws IOException { 8 Runtime runt = Runtime.getRuntime(); 9 runt.exec("notepad"); 10 } 11 }
运行后:弹出一个notepad框框.
第二种方法:通过 ProcessBuilder 创建进程
public
final
class
ProcessBuilder
extends
Object<br>
①、此类用于创建操作系统进程。
②、每个ProcessBuilder实例管理进程属性的集合。 start()方法使用这些属性创建一个新的Process实例。 start()方法可以从同一实例重复调用,以创建具有相同或相关属性的新子进程。
1 package Lesson1218Thread; 2 3 import java.io.IOException; 4 5 public class ProcessDemo { 6 7 public static void main(String[] args) throws IOException { 8 Runtime runt = Runtime.getRuntime(); 9 runt.exec("notepad"); 10 11 ProcessBuilder pb = new ProcessBuilder("notepad"); 12 pb.start(); 13 } 14 }
参照的帖子有:
https://blog.csdn.net/weixin_41891854/article/details/81265772 有问题
https://blog.csdn.net/u012843873/article/details/51314572
https://www.cnblogs.com/WJ-163/p/6261835.html
https://blog.csdn.net/u012973218/article/details/51280044
https://blog.csdn.net/renyl_blog/article/details/78204590
https://www.cnblogs.com/ysocean/p/6883491.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人