线程的创建和启动
JAVA使用Thread代表线程,所有线程对象都是Thread或其子类的实例
继承Thread类创建线程类
通过重写run方法来定义线程需要完成的任务,然后通过start来启动线程。
实现Runnable接口创建线程类
先定义runnable接口的实现类,重写该接口的run()方法,之后创建实现类的实例,以该实例作为Thread的target方法创建Thread对象,
Thread t = new Thread(target,name)
t.start()
也就是说Thread类创建线程类可直接作为线程对象,Runnable接口创建线程类只能作为线程对象的target,Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程执行体。
采用Runnable接口创建线程类可以共享线程类的实例变量
使用Callable和Future创建线程
Java5开始提供了Callable接口,其提供了一个call()方法可以作为线程执行体,但call()比run()更为强大。
call()可以有返回值;
call()可以声明抛出异常;
因此,我们可以尝试使用Callable作为Thread的target,但是Callable并不是Runnable子类,不能被当做target使用;
JAVA5提供了Future接口来代表call()的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口–因此可以作为thread的target;
public class thread_ extends Thread{
public static void main(String args[]) {
thread_ t = new thread_();
FutureTask task = new FutureTask(new callable_());
new Thread(task,"Callable").start();
}
static class callable_ implements Callable {
@Override
public Object call() throws Exception {
int i = 0;
for(; i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
}
创建线程方式对比
采用runnable和Callable:
线程只是实现了接口,还可以继承其他类,且多个线程可以共享一个target对象,适合多个相同线程处理同一份资源。
采用thread:
编程简单;
一般使用runnable和Callable来创建线程
线程的生命周期
线程共有5种状态:新建、就绪、运行、堵塞、死亡;
新建和就绪状态
线程被new出来后就处于新建状态,由JAVA虚拟机为其分配内存,并且初始化其成员变量的值,此时线程并为表现出任何的动态特征;
线程对象调用start()后就处于就绪状态,JAVA虚拟机会为其创建方法调用栈和程序计数器,此时线程准备好运行的所有条件,至于何时开始运行,取决于JVM线程调度器的调度。
运行和堵塞状态
线程开始执行run()方法的线程执行体,其就进入了运行状态;
当发生如下状况,线程会进入堵塞状态:
调用sleep()主动放弃占用的CPU资源;
试图获得一个正在被其他线程持有的同步监视器;
调用一个堵塞式IO方法;
等待某个通知;
程序调用了线程的suspend()方法将线程挂起,(该方法容易导致死锁,尽量避免使用)
死亡状态
线程以以下三种方法结束后,处于死亡状态:
run()或者call()执行完成,程序正常结束;
抛出一个未被捕获的异常或error;
直接调用stop()方法强制结束;(该方法容易导致死锁,尽量避免使用)
可以使用isAlive()方法测试线程状态,当为新建、死亡两种状态时返回false,其余返回true;
控制线程
join线程
让一个线程等待另一个线程完成;当在某个线程执行流中调用其他线程的join()时,调用线程会被堵塞,直到被join的线程执行完;
后台线程
后台线程所示在后台运行,为其他线程提供服务的线程,如果所有前台线程都死亡,后台线程也会自动死亡;
调用Thread对象的setDaemon(true)方法可将该线程设置成后台线程,
线程睡眠sleep()
让当前执行的线程暂停一段时间;
改变线程优先级
优先级高的线程可获得较多的执行机会,默认优先级为父线程的优先级,可使用setPriority(int new Priority),getPriority()设置和获得
线程同步
线程安全问题
两个进程并发修改同一个文件时,可能造成异常;JAVA引入了同步监视器来解决这个问题,使用同步监视器的通用方法为同步代码块:
synchronized (obj){
//同步代码块代码
}
其中obj代表着一个同步监视器,上面代码的含义是,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时可只能有一个线程可以获得对同步监视器的锁定,同步代码块执行完成后,线程会释放对同步监视器的锁定。
JAVA允许任何对象作为同步监视器,但同步监视器的目的是阻止并发访问同一个资源,因此通常使用可能被并发访问的共享资源充当同步监视器。
同步方法
同步方法就是使用synchronized关键字来修饰某个方法,对于同步方法而言,无需指定同步监视器,其同步监视器就是This,也就是调用该方法的对象。
释放同步监视器的锁定
线程会在下面情况释放锁定:
同步方法或代码块执行结束后释放;
同步方法或代码块遇到break,return等情况终止时释放;
出现未处理的error或exception释放;
程序执行了同步监视器线程的wait()方法时释放
注意使用sleep或suspend不会释放锁定
同步锁
JAVA5后可通过同步锁对象实现同步,由Lock对象充当;Lock也是控制多个线程对共享资源进行访问的工具,比起同步代码块更为方便灵活,
每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁;
线程通信
Object类提供了三个方法:
wait():导致当前线程等待,直到其他线程调用该同步监视器的notify方法或notifyAll()方法来唤醒该线程,或者是自定的时间达到后。
notify():唤醒此同步监视器上等待的单个线程
notifyAll():唤醒此同步监视器上等待的所有线程
Condition
如果使用Lock对象来保证同步而不是Synchronized,就无法使用上面的方法进行线程通信,JAVA提供了一个Condition类保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,企业能唤醒其他处于等待的线程。
Condition提供了三个类似的方法:
await();signal();signalAll()
堵塞队列
BlockingQueue:当生产者线程试图向其中取元素时,如果队列为空,则线程堵塞;线程试图存元素时,如果队列满,则线程堵塞;
BlockingQueue提供下面两个方法:
put(E e):尝试把e元素放入BlockingQueue中,若满则堵塞;
take():取出一个元素,若空则堵塞;
除此之外BlockingQueue还能使用Queue的一些通用方法;
线程组和未处理的异常
JAVA使用ThreadGroup来表示线程组,可以对一批线程组进行分类管理,JAVA允许程序直接对线程组进行控制,对线程组的控制相当于同时控制这批线程;
每个线程都属于一个线程组,没有显示指定的话就属于默认线程组。默认情况下,子线程与其父线程属于同一组;
线程只能在new的时候指定其线程组,之后不能改变;
ThreadGroup(String name);创建新线程组,名字为name;
ThreadGroup(ThreadGroup parent,String name);以指定的名字,指定的父线程组创建一个新线程组;
线程池
线程池在系统启动时就创建大量空闲线程,程序讲一个Runnable或者Callable对象传递给线程池,线程池就会启动一个线程执行他们的run()或call()方法,结束完毕后返回线程池成为空闲状态。
使用如下:
Executors类的静态工厂创建方法创建一个Executor对象,返回一个线程池。
创建线程实现类;
使用submit()方法提交runnable()实例;
使用shutdown()关闭线程池
ForkJoinPool
可以看下面的文章,讲一个任务拆分成多个小任务进行计算,再讲小人物结果合并成总的计算结果。ForkJoinPool是ExecutorService的实现类;
https://www.jianshu.com/p/91e504c910c9