首先需要对线程有一定的认识,这里不叙说的那么具体,比如线程和进程有啥区别啥的就不说了。先说一下线程的生命周期,一般分为新建---等待/阻塞态---就绪态---运行态---死亡;阻塞和就绪的区别在于,就绪态表示此线程所需要的条件以满足,只差CPU的使用权,而阻塞表示线程所需要的条件仍未满足,比如IO限制,则此时就算让出CPU使用权此线程也是不能进行的。学习操作系统的时候记得就绪态和阻塞态可以分别理解为两个队列,阻塞态满足条件之后,线程从阻塞队列移到就绪队列,等待CPU使用权。大概就是这样去理解。
然后就是使用多线程必须要了解的一点,线程之间共享哪些资源?不共享哪些资源?我觉得这是学习多线程的必要出发点,很显然,程序之外的东西是共享的(文件),其次程序中的全局变量是共享的,静态变量与全局变量同样放在.data/.bss段,所以也是共享的,最后程序进程所开辟的堆空间,也是共享的; 而线程的运行有自己的栈帧空间,所以栈对于线程来说是独立的,不与其他线程共享,其次值得注意的是寄存器对于线程来说也是独立的,线程中放的是寄存器副本。
在了解了线程的基本属性之后,先从最基本的开始,JAVA如何开启一个线程,我们只要去new一个Thread对象,并重写其中的run()方法后调用thread.start()方法即可,run方法中的即为我们所要交给线程处理的事件;或者我们可以去实现一个Runnable接口,并用其初始化一个Thread对象,然后在调用thread的start()方法。可见线程的启动最终都是调用Thread的.start()方法,那么这两种方法有什么区别呢,我们查看源代码发现:
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
private volatile char name[];
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
我们所使用的Thread类是一个Runnable接口的实现,并且其中还有一个Runnable类型的target私有变量,我们再往下查看发现:
/**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, null, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, target, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*
* @param target
* the object whose {@code run} method is invoked when this thread
* is started. If {@code null}, this classes {@code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
其中Thread的构造方法有一个空参的,也有一个接受一个Runnable对象的,这正好与我们上述所说开启线程的方法吻合,init()函数可以查看JAVA源代码,查看之后可以知道,实际上最终被线程执行的任务是Runnable,而非Thread。Thread只是对Runnable的包装,并通过一些状态对Thread进行管理和调度。当启动一个线程的时候,如果Thread的target不为空,则会在子线程中执行这个target的run方法,否则虚拟机就会执行该线程自身的run函数。
JAVA中有几种对于线程的操作很常见:
1.wait()+notify()/notifyAll():当一个线程执行到wait()方法时,它进入到一个和该对象相关的等待池中,同时失去了对象的机锁,使得其他线程可以访问,用户可以使用notify()/notifyAll()函数或者指定睡眠时间来唤醒当前等待的线程;(wait(),notify(),notifyAll()必须在synchronized 块中使用,否则会抛出异常);
2.sleep():sleep()的作用是使当前线程休眠,是Thread的一个静态方法,不能改变对象机锁,比如在synchronized中使用sleep()使线程休眠,但此时线程仍然持有对象锁;
3.join():join函数的功能是使调用线程等待所调用join的线程执行完之后在继续进行;比如在A线程中调用B.join(),则A线程会等B线程执行完再继续执行;
4.yield():yield()函数与join函数类似,可以理解为线程的礼让,调用之后线程让出自己的时间片给其他线程,其他线程能否得到优先执行则看各个线程的状态;
除了Runnable之外,JAVA还有几个与多线程相关的概念,在这里简单介绍一下:
1.Callable泛型接口:Callable是一个泛型接口,其接口中的call()函数的作用就如同Runnable接口中的run()函数,区别在于call()函数有一个泛型类型的返回值,而run()是没有返回值的;
2.Future<V>接口为线程提供了一个可管理的任务标准,,它提供了对Runnbale/Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作等功能,其中部分函数会阻塞线程,比如获取结果函数get(),会阻塞直到获取结果;
3.FutureTask是Future的实现类。
以下代码进行对上述类型接口作用的基本描述:
package zs;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Threadtest {
static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
public static void main(String[] args)
{
try{
futureWithRunnable();
futureWithCallable();
futureTask();
}catch(Exception e){
}
}
private static void futureWithRunnable() throws
InterruptedException,ExecutionException{
Future<?> result = mExecutor.submit(new Runnable()
{
@Override
public void run()
{
fibc(20);
}
});
System.out.println(" futretask : "+result.get());
}
private static void futureWithCallable() throws
InterruptedException,ExecutionException{
Future<?> result = mExecutor.submit(new Callable<Integer>()
{
@Override
public Integer call() throws Exception{
return fibc(20);
}
});
System.out.println(" futretask : "+result.get());
}
private static void futureTask() throws
InterruptedException,ExecutionException{
FutureTask<Integer> futureTask = new FutureTask<Integer>(
new Callable<Integer>()
{
@Override
public Integer call() throws Exception{
return fibc(20);
}
}
);
mExecutor.submit(futureTask);
System.out.println(" futretask : "+futureTask.get());
}
private static int fibc(int num)
{
if(num == 0)
{
return 0;
}
if(num == 1)
{
return 1;
}
return fibc(num - 1)+fibc(num - 2);
}
}
其中executor为一个工厂类,为我们创建了一个单线程的线程池,然后我们调用三个方法得到结果如下:
futretask : null
futretask : 6765
futretask : 6765;
从结果可以看出,run方法无返回值,使用get()得到的结果为null,而Callable接口的call方法存在返回值,使用get()可以获得。
不知道大家有没有注意到这样一行代码:
static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
我们获取了一个单线程的线程池,这不是通过new出来的,而是通过Executors的方法得到的,从代码可以推测,newSingleThreadExecutor()方法应该是Executors的一个public static 方法,实际上的确是这样,源代码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
代码中 ThreadPoolExecutor()方法用于启动指定数量的线程,构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数的含义分别为:
corePoolSize:线程中所保存的核心线程数;
maximumPoolSize:线程池允许创建的最大线程数;
keepAliveTime:当前线程池线程总数大于核心线程数时,终止多余的空闲线程的时间
Unit:前一个参数的单位,可选,秒,毫秒等;
workQueue:任务队列,如果当前线程池核心线程数到达,且所有线程处于活动状态时,则将新的任务放入此队列中;
threadFactory:线程工厂,通常不需要设置;
Handler:拒绝策略,当线程池与workQueue都满了的时候,对新加任务采取的拒绝策略。
上述讲到了一个新概念---线程池,线程池可以理解为一个能对线程进行统一管理,有固定参数的线程合集,使用线程池进行管理可以有效避免线程间的互相竞争,占用过多资源,线程缺乏定时执行,线程中断等功能,具体来说有以下几种好处:
1.重用存在的线程,减少对象创建、销毁的开销;
2.可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源阻塞;
3.提供定时执行,定期执行,单线程,并发数控制等功能。
当然,最大的好处就是JAVA提供了四种线程池,强大到几乎不需要开发人员自定义的程度。
再看回上述代码,submit方法就是ExecutorService的接口,使用之后线程提交到线程池中并交由线程池运行,上述线程池为单线程池,所以每个线程都会独立执行完毕,submit()可以简单理解为run()!!
以上就是到目前我所看到的地方,原本打算全部学习完之后在进行整理做笔记,但是正好今儿是除夕,我想新的一年会需要好的开始,过去的一年也需要一个好的结尾,就不再拖到春节之后去写了;学习过程中有了很大的进步,学会使用eclipse进行源代码查看,查看源代码的操作很简单,导入src.zip即可,具体教材网上有很多也很简单,在此不进行介绍了;
在此希望新的一年自己能不断进步,新的一年我也马上要工作了,也是非常重要的一年,希望自己能顺利过渡,也希望家人天天开心,身体健康。也祝看到这篇渣文章的各位新的一年身体健康,万事如意。