JAVA 多线程学习---from《android从小工到专家》

首先需要对线程有一定的认识,这里不叙说的那么具体,比如线程和进程有啥区别啥的就不说了。先说一下线程的生命周期,一般分为新建---等待/阻塞态---就绪态---运行态---死亡;阻塞和就绪的区别在于,就绪态表示此线程所需要的条件以满足,只差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即可,具体教材网上有很多也很简单,在此不进行介绍了;

在此希望新的一年自己能不断进步,新的一年我也马上要工作了,也是非常重要的一年,希望自己能顺利过渡,也希望家人天天开心,身体健康。也祝看到这篇渣文章的各位新的一年身体健康,万事如意。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值