1、要讲java多线程,首先要说什么是线程,我们先来说说进程的概念,进程是系统资源分派和调度的基本单位,需要在内存为进程分配空间。像运行exe文件就会在系统生成一个对应的进程。线程存在于进程,是进程中实际执行代码的部分,一个进程可能只有一个主线程也可能有多个线程,线程是cpu分派和调度的基本单位。像运行java程序,生成一个进程,而实际执行main代码的部分就是主线程部分。
2、java中如何使用多线程编程,java为多线程封装了一个类Thread,生成线程有两种方式。
1)继承Thread类
public class MyThread extends Thread{
public MyThread(String threadName){
super(threadName);
}
public void run(){
System.out.println(Thread.currentThread()+":run");
}
public static void main(String[] args){
MyThread myThread=new MyThread();
myThread.start();
}
}
2)实现Runnable接口
public class MyRunnable implements Runnable{
public void run(){
System.out.println("I run");
}
public static void main(String[] args){
MyRunnable myRunnable=new MyRunnable();
Thread thread=new Thread(myRunnable);
thread.start();
}
}
两种实现最后都是调用Thread类或其派生类的start方法开启线程,如果是继承Thread的类,在调用start方法后,在开启的线程中会调用Thread类的run()方法,所以如果继承Thread类的话,只要在派生类中重写run()方法并在方法中加入需要在线程中执行的代码。如果直接调用run()方法的话就是普通的方法调用,并没有生成线程。如果是实现Runnable接口的话,从代码中可以看出实现类的实例会作为参数传给Thread对象,然后调用Thread对象的start方法,所以这种方式下开启线程还是执行的是Thread类的run()方法,然后再run()方法中调用Runnable实现类的run()方法,这里有点像代理模式,Thread类是Runnable实现类的代理。
Runnable的实现类的同一个对象可以传给多个Thread对象。
MyRunnable myRunnable=new MyRunnable();
Thread thread1=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable);
....
thread1.start();
thread2.start();
.....
但是在实际应用中,一个MyRunnable对象一般只是给一个线程使用,所以我们在实际开发中这样的代码更加常见
new Thread(new Runnable{
public void run(){
.....
}}.start();
什么时候用实现Runnable接口,什么时候继承Thread类呢
1.由于java的单继承,如果继承了Thread类,就不能继承其他类,所以需要继承其他类的时候需要实现Runnable接口。
2.一般来说能用继承Thread的地方都能用实现Runnable接口,所以有时为了减少生成的类,比如后面使用实现Runnable的方式,所以个人认为尽量用Runnable,而且如果用线程池,Runnable实现类的对象可以直接被线程池的接口类ExecutorService执行。
java1.5引进了java.util.concurrent包,引入了很多用于并发处理的类,这里主要讲讲线程池相关的类和接口。
ExecutorService接口,继承自Executor接口,
这里主要讲我们平时在项目中经常用到的几个方法
void execute(Runnable command);执行Runnable任务
<T> Future<T> submit(Callable<T> task);执行Callable任务,与Runnable任务的最大区别是它有返回值
Future<?> submit(Runnable task);执行Runnable任务,与execute没有多大区别,虽然有返回值,但是返回对象Future中取得的值为null
void shutdown();如果执行了,线程池不再接受新任务,但是会执行完已经提交的任务。
ExecutorService的实现类ThreadPoolExecutor,
它有个构造函数
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程空闲时间,即线程空闲多少时间久关闭线程
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue//任务接收队列
通过传入不同的参数可以得到不同的线程池对象。线程池总的原则是如果当前线程数小于核心线程数,则当有任务到来的时候,无论当前是否有空闲线程,都开启新的线程来执行任务;如果当前线程数大于等于核心线程数,但是任务接受队列没满,则将任务加入接受队列,等待线程执行,如果任务接受队列满了,而且当前线程数小于最大线程数,则开启新的线程来执行任务。如果任务接收队列满了,而且当前线程数等于最大线程数,则执行策略,或重试,或抛弃任务等。
java.util.concurrent包下有个帮助类Executors类,它提供了返回ThreadPoolExecutor对象的方法。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从调用的构造函数可以看出,当调用newCachedThreadPool()得到线程池处理对象时,核心线程数是0,最大线程数不限,所以只要有任务到来,而由于核心线程数是0,所以当前线程数必定大于等于核心线程数,所以要将其放入任务接收队列,这里要详细说说SynchronousQueue。
SynchronousQueue其实不算是个队列,因为它的容量是0,为什么说它的容量是0呢,如果有线程往该队列放入任务,如果没有其它线程正从队列中取任务,则放入任务失败。这里取任务操作是个阻塞方法,如果单独有线程要取任务,则该线程阻塞,直到有其它线程往队列放任务,此时放任务的线程放任务成功,取任务的线程解除阻塞,取任务成功。放任务的线程不会阻塞,只是没有线程取任务的话,放任务将失败,即无法将任务放入接收任务队列。
下面看举例代码,加深对该队列的理解
public class MyTest2 {
class A extends Thread{
private SynchronousQueue<Integer> q;
public A(SynchronousQueue<Integer> q){
this.q=q;
}
public void run(){
System.out.println("before offer1");
System.out.println(q.offer(1));
System.out.println("after offer1");
}
}
class B extends Thread{
private SynchronousQueue<Integer> q;
public B(SynchronousQueue<Integer> q){
this.q=q;
}
public void run(){
try {
System.out.println("before take1");
System.out.println(q.take());
System.out.println("after take1");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class C extends Thread{
private SynchronousQueue<Integer> q;
public C(SynchronousQueue<Integer> q){
this.q=q;
}
public void run(){
try {
System.out.println("before take2");
System.out.println(q.take());
System.out.println("after take2");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[]args){
MyTest2 m=new MyTest2();
SynchronousQueue<Integer> q=new SynchronousQueue<Integer>();
A a=m.new A(q);
B b=m.new B(q);
C c=m.new C(q);
b.start();
c.start();
a.start();
}
执行结果
before take1
before take2
before offer1
true //表明往队列放数据成功
after offer1
1
after take2
有b,c两个线程再取数据,只有a线程放数据,所以放数据线程能放数据成功,而只有一个线程能取数据成功解除阻塞,只有after take2,表明c线程解除阻塞,取数据成功,而b线程继续阻塞。
newCachedThreadPool生成的线程池,当有任务到来的时候,如果没有空闲线程,则此时没有线程从接收任务队列取任务,所以往队列放任务失败,根据线程池总的原则,如果放任务失败,而当前线程数又小于最大线程数的时候,则开启新的线程执行任务。如果有空闲线程,则空闲线程试图从接收任务队列中取任务,所以在有任务到来的时候,往接收任务队列放任务将成功,放的任务将被空闲线程取走执行。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
这里核心线程数等于最大线程数,也就是传入的线程数,线程池将维护指定数目的线程。这里时间参数为0,表示无乱线程空闲多少时间都不会将其关闭。当有任务到来的时候,如果有空闲线程,则用空闲线程来执行任务,如果线程都忙着,查看当前线程数是否小于核心线程,如果小于则开启新线程,否则则将其加入等待队列,这里的等待队列是LinkedBlockingQueue结构,这是个链表结构的队列,无论加入多少任务,都不会满
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这个和fixed的线程池差不多,只是核心线程数变成了1,所以加入的任务,根据加入的顺序用单线程来执行。
下面看下用线程池来执行Runnable任务的代码
MyRunnable myRunnable=new MyRunnable();
ExecutorService executorService=null;
executorService=Executors.newCachedThreadPool();
executorService.execute(myRunnable);
executorService=Executors.newFixedThreadPool(10);
executorService.execute(myRunnable);
executorService=Executors.newSingleThreadExecutor();
executorService.execute(myRunnable);
线程池提供了比原始多线程使用更多的功能和更方便的使用,而且也更加高效,所以在实际项目中尽量用线程池来开启新线程执行代码。
分享到:
相关推荐
总的来说,多线程是提高程序并发性和效率的重要手段,但同时也引入了线程安全、死锁等问题,需要开发者精心设计和管理线程的生命周期,正确使用同步和异步机制,以及处理好临界资源的访问,以保证程序的稳定和高效...
【Java多线程编程】是Java开发中不可或缺的一部分,它允许程序同时执行多个任务,从而提高效率和响应速度。本文将深入探讨Java多线程的优缺点、创建方式以及线程安全与同步机制。 **一、多线程的优缺点** 1. **...
"浅谈Java多线程编程" 从标题和描述可以看出,这篇文章的主题是讨论Java多线程编程的相关知识点。 多线程编程的概念 Java语言的一个重要特点是支持多线程机制,这使得Java程序可以支持多程序并发执行,从而提高...
浅谈JAVA语言的多线程技术 一、多线程技术的概述 JAVA语言作为一种面向对象的编程语言,它具有平台独立性、安全性、网络化、多线程、面向对象等特点。其线程机制在实践中广泛应用而受到编程者的极大关心。本文就...
- POSIX标准定义了一些线程安全的函数,如`printf`家族的`printf_s`,这些函数在多线程环境中使用时不会引发竞态条件。 在实际开发中,理解并熟练掌握上述概念和API是进行有效多线程编程的基础。同时,良好的编程...
在Java编程语言中,多线程是程序设计中的一个重要概念,尤其在开发高效能、...在实际开发中,合理地使用多线程可以提高程序的运行效率,但也需要考虑到线程安全、资源竞争等问题,避免出现死锁、活锁、饥饿等并发问题。
### 浅谈Java的多线程机制 #### 一、引言 随着计算机技术的不断发展,编程模型变得越来越复杂和多样化。多线程编程模型作为目前计算机系统架构中的一个重要组成部分,其重要性日益凸显。特别是在X86架构的硬件成为...
例如,当一个任务可以分解为多个子任务时,使用多线程可以将各个子任务分配给不同的线程,提高整体的执行效率。然而,多线程也需要注意线程间同步,以避免数据竞争和资源冲突。 在Linux和Windows下,多线程的创建和...
在.NET框架中,多线程和并行计算是提高应用程序性能和响应能力的关键技术。本文将深入探讨这两个概念,以及如何在.NET环境下有效地利用它们。 首先,多线程是指一个程序中同时执行多个独立的线程,每个线程都有自己...
在.NET框架中,多线程和并行计算是提高应用程序性能和响应能力的关键技术。本文将深入探讨这两个概念,以及...通过合理地使用多线程和并行计算,开发者可以充分利用现代硬件资源,创建出响应迅速、资源利用率高的软件。
在Java开发中,多线程技术是一项重要的编程技能,它允许同时运行两个或多个部分,这些部分称为线程,每个线程可以处理不同的任务。这不仅能够提高程序的执行效率,还能改善用户体验,因为多线程可以实现程序的异步...
多线程编程是一种在单个进程中同时执行多个并发任务的技术,它可以提高程序的效率和响应性,尤其是在处理大型计算任务或需要并行处理的场景中。本文将对比Linux和Windows平台上的多线程编程,探讨它们的异同点。 ...
### .NET下的多线程与并行计算:深入解析与应用 #### 一、引言 随着计算机硬件的发展,特别是多核处理器的普及,多线程和并行计算已成为现代软件开发不可或缺的一部分。本文旨在探讨.NET框架下多线程与并行计算的...
总的来说,Linux下的多线程编程涉及线程创建、属性设置、同步机制等多个方面,合理利用多线程可以提高程序效率,优化系统资源使用,但同时也需要处理好线程间的同步问题,避免数据竞争和死锁的发生。在实际编程中,...