要提到线程,则不得不提进程这个概念,因为线程是依附于进程的,它们都属于操作系统中的知识,现在的操作系统都属于多任务的,多线程是实现多任务的一种方式。
进程是指一个内存中运行的应用程序的一次活动,是一个动态的概念,是系统进行资源分配和调度的基本单位,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程总是属于某个进程,进程中的多个线程共享进程的内存
一创建java线程
创建java线程包括三种方式:
1定义一个子类继承自Thread类
2定义一个子类实现Runnable接口,然后将其实例化后的对象作为参数传递给Thread,Thread的构造器如下:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
3使用线程池:
Java通过Executors提供四种线程池:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
使用线程池的目的::为了减少创建和销毁线程的次数,每个工作线程可以被重复利用,可以执行多个任务。通常我们使用第一种方式创建固定大小的线程池,代码如下:
executorService = Executors.newFixedThreadPool(Runtime.getRuntime() .availableProcessors() * 50); executorService.execute(new SocketTask(socket)); private final class SocketTask implements Runnable { <span style="white-space:pre"> </span>public void run() { <span style="white-space:pre"> </span>} }
二java线程的状态:
java中的线程主要包括五大状态:即初始状态,运行状态,阻塞状态,等待状态(或超时等待),终止状态。
1初始状态:线程对象已经创建,还没有在其上调用start()方法。
就绪状态:当线程具备运行资格,但调度程序(JVM)还没把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
2运行状态:java线程将操作系统中的运行于就绪笼统的称为运行状态,
运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
3阻塞状态:包括等待//睡眠状态这几种情况,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,当满足某条件时可以返回可运行状态
如处于等待状态的线程当使用notify()唤醒,处于sleep()状态的线程在睡眠结束后都会返回到可运行状态。等待JVM调度。
4等待状态:包括等待状态与超时等待这两种情况,超时等待状态在超过指定的时间后会自动返回。
5死亡状态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
用图示表示如下:
注意上图中的等待状态是不能直接进入运行状态的,当调用notify()/notifyAll()时会将该线程(暂称之为waitThread)从等待队列移到同步队列,此时的状态为阻塞状态,即阻塞状态包括两种情况,一种是进入同步方法/块时的阻塞,另一种是当另一个线程(暂称之为NotifyThread)调用notify()/notifyAll()会将waitThread从等待队列移到同步队列时,当调用notify()/notifyAll()的线程释放锁之后,此时waitThread会进入运行状态。
三java线程的同步与锁
1锁的原理:
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放,释放锁是指持锁线程退出了synchronized同步方法或代码块。
需要注意:
在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
public int fix(int y) {
synchronized (this) {
x = x - y;
}
return x;
}
当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
public synchronized int getX() {
return x++;
}
与
public int getX() {
synchronized (this) {
return x;
}
}
效果是完全一样的。
2静态方法的同步:
要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。
例如:
public static synchronized int setName(String name){
Xxx.name = name;
}
等价于
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}
四java线程常见方法:
五Callable接口
通常我们通过定义一个类来实现Runnable接口来创建一个线程,但是在java1.5之后出现了Callable接口,其用法与Runnable接口非常类似,主要不同点如下:
Runnable和Callable的区别:
(1)Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
(2)Callable规定的方法是call(),Runnable规定的方法是run()
(3)Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)
(4)call方法可以抛出异常,run方法不可以
(5)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。通常我们使用其子类FutureTask(它同时实现了Runnable接口),它提供了检查计算是否完成的方法,以等待计算的完成,。通过Future对象可以了解任务执行情况,可取消任务的执行,还可以通过FutureTask的get()方法获取执行结果。
(6)加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。
Callable接口也是位于java.util.concurrent包中。Callable接口的定义为:
public interface Callable<V>
{
V call() throws Exception;
}
使用实例如下:
import java.util.concurrent.Callable; public class OneTask implements Callable<String> { private int id; public OneTask(int id){ this.id = id; } @Override public String call() throws Exception {//这儿可以抛出异常,而Runnable接口的run函数不可以 int i=5; while (i>=0) { System.out.println("Thread "+ id +" is working"); Thread.sleep(1000); i--; } return "result of Test2 " + id; //Runnable接口的run函数是没有返回值的 } }执行Callable的线程的方法可以借助FutureTask或者加入到线程池中。首先看怎么用FutureTash执行,代码如下:
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test1 { public static void main(String[] args) { Callable<String> oneCallable = new OneTask(1); FutureTask<String> ft= new FutureTask<String>(oneCallable); //FutureTask<String>是一个包装器,它通过接受Callable<String>来创建,它同时实现了Future和Runnable接口 new Thread(ft).start(); while(!ft.isDone()){ try { System.out.println("检查线程执行完了吗..."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } String result = ""; try { result = ft.get(); } catch (Exception e) { e.printStackTrace(); } System.out.println(result); } }FutureTash扮演监督的角色,主线程通过不断询问实现Callable的类对应的线程是否执行完毕,最后可以得到返回的结果。通过线程池的方式如下:
import java.util.ArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test3 { public static void main(String[] args){ // TODO Auto-generated method stub ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<Future<String>>(); //Future 相当于是用来存放Executor执行的结果的一种容器 for (int i = 0; i < 10; i++) { results.add(exec.submit(new OneTask(i))); } for (Future<String> fs : results) { if (fs.isDone()) { try { System.out.println(fs.get()); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("Future result is not yet complete"); } } exec.shutdown(); } }线程池的submit方法会返回一个Future,通过这个Future可以判断线程是否执行完毕,已经通过它可以得到返回值。