多线程
1.什么是多线程
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
多线程可以让程序并行的执行多个任务,而不是程序在任意时刻都只能执行一个步骤.
2.实现多线程需要做什么
在java中实现多线程的方法很简单.
1.通常无返回值的情况:通过继承Runable接口中的run()方法,通过Thread类来驱动它,主要用这个做例子来理解.
下边我们通过一个例子来了解一下:
实现Runable中的run接口:
package threadTest;
public class runableTest implements Runnable {
//这里继承了Runable接口
protected int countDown = 10;
private static int taskCount = 0;//方便线程记录
private final int id = taskCount++;//将线程名称固定,不变.
public runableTest(int countDown){
this.countDown = countDown;
}
public runableTest(){}
public String status(){
return "#"+id+"("+(countDown>0 ? countDown : "liftoff!")+"),";
}
@Override
public void run() {
//实现了Runable接口中的run()方法
while(countDown-->0){
System.out.println(status());
}
}
}
线程测试类:
public class thtest {
public static void main(String[] args){
runableTest runableTest = new runableTest();//创建一个新的对象
Thread t =new Thread(runableTest);//将对象交给Thread类
t.start();//通过调用Thread类中的start()方法开始执行.
System.out.println("ruaaaaaaaaaa");
}
}
1.我们首先创建了一个 runableTest的对象,
2.我们将继承了Runable接口的对象交给Thread类
3.我们调用Thread类中的start的方法开始执行线程
4.会开始执行run()方法.
在run()方法中会进入while循环,调用status()方法打印当前状态.
执行结果如下
ruaaaaaaaaaa
#0(9),
#0(8),
#0(7),
#0(6),
#0(5),
#0(4),
#0(3),
#0(2),
#0(1),
#0(liftoff!),
这里发生的多线程为main()方法的线程与咱们通过Thread在新线程中启动内容,从输出结果可以看出rua的输出与线程计数的输出是相反的.
这是因为在start()调用runable对象的run()方法的时候main()方法已经执行完了了System.out.println这个方法.
创建多个对象多个线程同时去执行任务,来取得更加直观的效果,实现Runable的类不变,下边我们对测试线程的代码进行一些小改动.
线程测试类:
public class thtest {
public static void main(String[] args){
for(int i=0;i<5;i++){
new Thread(new runableTest()).start();//直接将创建好的Runable对象传入新建的Thread对象并且调用start方法启动线程.
System.out.println("ruaaaaaaaaaa");
}
}
}
这里我们通过for循环启动了5个新线程
ruaaaaaaaaaa
ruaaaaaaaaaa
ruaaaaaaaaaa
ruaaaaaaaaaa
ruaaaaaaaaaa
#0(9),#0(8),#1(9),#2(9),#0(7),#3(9),
#1(8),#0(6),#4(9),#2(8),#4(8),#0(5),
#1(7),#3(8),#1(6),#0(4),#4(7),#2(7),
#4(6),#0(3),#1(5),#3(7),#1(4),#0(2),
#4(5),#2(6),#4(4),#0(1),#1(3),#3(6),
#1(2),#0(liftoff!),#4(3),#2(5),#4(2),
#1(1),#3(5),#1(liftoff!),#4(1),#2(4),
#4(liftoff!),#3(4),#3(3),#2(3), #3(2),
#2(2),#3(1),#2(1),#3(liftoff!),#2(liftoff!),
从结果我们可以看到,执行的顺序是乱序的,完全是随机执行,这种随机性是由线程调度器决定的,如果你的机器上有多个处理器,线程调度器会在这些CPU之间分发线程, 由于调度机制是非确定性的,所以再次执行的结果不一定相同.
使用Executor来管理Thread对象.
Executor可以显示的创建对象,从而简化并发编程,它作为一个在客户端与任务执行之间的中间层,Executor将会执行任务,无需我们自己显示的管理线程的生命周期.
ExecutorService对象可以执行Runable对象,
这里介绍一下两种创建线程池的方法:
newCacheThreadPool:
CachedThreadPool 是通过 java.util.concurrent.Executors 创建的 ThreadPoolExecutor 实例。这个实例会根据需要,在线程可用时,重用之前构造好的池中线程。这个线程池在执行 大量短生命周期的异步任务时(many short-lived asynchronous task),可以显著提高程序性能。调用 execute 时,可以重用之前已构造的可用线程,如果不存在可用线程,那么会重新创建一个新的线程并将其加入到线程池中。如果线程超过 60 秒还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任何资源。
newFixedThreadPool:
FixedThreadPool 是通过 java.util.concurrent.Executors 创建的 ThreadPoolExecutor 实例。这个实例会复用 固定数量的线程 处理一个 共享的无边界队列 。任何时间点,最多有 nThreads 个线程会处于活动状态执行任务。如果当所有线程都是活动时,有多的任务被提交过来,那么它会一致在队列中等待直到有线程可用。如果任何线程在执行过程中因为错误而中止,新的线程会替代它的位置来执行后续的任务。所有线程都会一致存于线程池中,直到显式的执行 ExecutorService.shutdown() 关闭。
同样我们通过例子来了解Executor如何管理Thread对象:
第一种使用newCacheThreadPool可以系统会自动创建相应的线程数量并且回收.
上代码:
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();//创建新的缓存线程池,executor是静态方法
,直接使用这个方法创建ExecutorService对象,这样可以确认其Executor的类型.
for(int i=0;i<5;i++){
exec.execute(new runableTest());//直接传入一个Runable对象
}
exec.shutdown();//调用shutdown方法可以防止新任务被提交给这个executor,这里面即为main()
当前线程将继续运行在shutdown()被调用之前提交的所有任务,这个程序将会在executor执行完所有任务后尽快退出.
}
执行结果:
#0(9),#0(8),#0(7),#1(9),#0(6),#2(9),#1(8),
#0(5),#2(8),#1(7),#0(4),#3(9),#2(7),#1(6),
#0(3),#3(8),#4(9),#2(6),#0(2),#1(5),#3(7),
#4(8),#2(5),#0(1),#1(4),#3(6),#4(7),#2(4),
#0(liftoff!),#1(3),#3(5),#4(6),#2(3),#1(2),
#3(4),#4(5),#2(2),#1(1),#3(3),#2(1),#4(4),
#3(2),#2(liftoff!),#3(1),#1(liftoff!),#4(3),
#3(liftoff!),#4(2),#4(1),#4(liftoff!)
第二种使用FixedThreadPool,创建固定数量的线程池,也就是我们可以预先执行线程分配操作,自己限制线程数量.
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(5);//按照传入参数创建线程数量,建立线程池.
for(int i=0; i<5;i++){
exec.execute(new runableTest());
}
exec.shutdown();
}
执行结果
#0(9),#1(9),#0(8),#1(8),#1(7),#0(7),#1(6),
#2(9),#0(6),#3(9),#2(8),#3(8),#1(5),#3(7),
#2(7),#0(5),#4(9),#0(4),#2(6),#3(6),#1(4),
#3(5),#2(5),#0(3),#4(8),#0(2),#2(4),#3(4),
#1(3),#3(3),#2(3),#0(1),#4(7),#0(liftoff!),
#2(2),#3(2),#1(2),#4(6),#2(1),#3(1),#1(1),
#4(5),#1(liftoff!),#2(liftoff!),#3(liftoff!),
#4(4),#4(3),#4(2),#4(1),#4(liftoff!),
下面介绍一个创建单独线程池的方法,这个方法通常使用在需要长期存活的任务中.
通过SingleThreadExecutor就相当于创建了线程数量为1的线程池.
2.在线程执行的过程中产生返回值:通过继承Callable接口,并且实现其中的call()方法,并且必须通过ExecutorService.submit()来调用.
submit()会产生future对象,你可以通过这个对象的get()方法拿到之前线程返回值.
下边来看一个的例子:
先上callable代码:
package threadTest;
import java.util.concurrent.Callable;
public class callableTest implements Callable<String> {
private int id ;//记录线程ID
public callableTest(int id){
this.id=id;
}
@Override
public String call() throws Exception {
return "ruaaaaa:thread id = "+id;//线程返回值,赋上ID
}
}
下面是测试类代码:
public static void main(String[] args) throws InterruptedException {
ArrayList<Future<String>> results = new ArrayList<Future<String>>();//创建一个用于接受submit产生对象的动态数组
ExecutorService exec = Executors.newCachedThreadPool();//创建ExecutorService
for(int i =0;i<5;i++) {
results.add(exec.submit(new callableTest(i)));//通过循环创建对象
}
for(Future<String> fs:results){
try {
System.out.println(fs.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
执行结果:
ruaaaaa:thread id = 0
ruaaaaa:thread id = 1
ruaaaaa:thread id = 2
ruaaaaa:thread id = 3
ruaaaaa:thread id = 4
线程的生命周期
上边程序执行的时候就是执行start()方法,由start方法执行run(),最后执行完成死亡,被系统回收.
阻塞:阻塞指的是暂停一个线程的执行以等待某个条件发生.
- 调用sleep将线程睡眠,进入阻塞状态
- 调用wait()方法,直到获得notify()或者notifyAll()的信号,才会恢复
- 在等待某个输入或者输出流完成
- 对于锁不可用的同步方法,等待锁的获取.
线程的中断:
在线程的run()也就是正在运行的状态的时候对其进行中断,可能会造成资源问题,比如资源没办法即时回收, 任务处理一半产生垃圾数据等.
当run()任务被中断的时候,更像是抛出了异常,为了处理这种中断情况造成的垃圾,要在catch子句中正确回收所有需要的事务.
中断的方法:
Thread.interrupt(),这个方法将设置线程的中断状态.
如果一个线程在阻塞状态,或者试图进行一个阻塞操作,那么设置这个线程的中断状态后,会抛出interruptedException.
当调用Thread.interrupted()方法的时候,中断状态将会被复位.
如果你在Executor上调用shutdownNow(),它会发送一个interrupt调用给它所启动的所有线程.
Executor的interrupt的用法.
话不多说,上代码:
继承Runable接口的类:
package interruptThreadTest;
import java.util.concurrent.TimeUnit;
public class interruptTestOne implements Runnable {
@Override
public void run() {
try{
TimeUnit.MILLISECONDS.sleep(1100);
}catch(InterruptedException e){
System.out.println("线程已经中断,抛出异常");
}
System.out.println("线程退出");
}
}
测试用类
package interruptThreadTest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class interruptTest {
private static ExecutorService exec = Executors.newCachedThreadPool();//首先创建一个线程池
static void test(Runnable runnable) throws InterruptedException {
Future<?> fs = exec.submit(runnable);//exec返回Future对象,由于本身Future是泛型,这里并不能知道是什么类型的数据,所以用通配符代替.
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("中断的类名:"+runnable.getClass().getName());//通过反射打印出传入方法类名.
fs.cancel(true);//如果线程在运行 则中断它
System.out.println("发送中断信息的类:"+runnable.getClass().getName());//也就是对其进行interrupt的类.
}
public static void main(String[] args) throws InterruptedException {
test(new interruptTestOne());//调用上边写的test方法并且传入Runable对象.
}
}
执行结果:
中断的类名:interruptThreadTest.interruptTestOne
发送中断信息的类:interruptThreadTest.interruptTestOne
线程已经中断,抛出异常
线程退出
上边设置的sleep是为了让线程一定在运行状态,这样cancel(true)的操作才会有效果, 在线程运行时通过cancel()操作 对其进行中断.
这里要注意的是Runable中的run()方法并没有附带返回值,所以你从Future对象中取值也是拿不到东西的,这里只是接Future对象作为Cancel方法的桥梁.
线程的同步与协作
什么是线程的同步?
线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
一些敏感数据不允许多个线程同时访问,保证数据的安全性,这时候就需要使用线程同步的技术.
怎么实现线程的同步?
实现线程同步的方法在java中非常简单,通过在方法上边添加Synchronized关键字, 这个方法就会变为同步方法,也称之为加锁(或者互斥),变成同步方法之后同一时间只有一个线程可以对其进行操作.
如果A与B同时需要使用这个方法, A先抢到了这个方法的使用权,我们就说A获得了这个方法的锁,B此时处于阻塞状态,什么也不能干,等待A使用完毕后释放方法的锁.
线程的协作
上边说了线程的同步方法,下边说一下线程的协作方法.
因为在我们实际使用当中,某些部分必须依赖于另外一部分完成之上,就例如生活中要有电才能开灯,实现协作方法其中重要的一个点就是,线程之间的交流,我们利用了线程的特性之一互斥.
在这个基础之上我们添加了一种途径,可以将自身挂起,直到相应某种信号,或者外部条件发生变化,让其继续执行下去.
下边来介绍将线程挂起与唤醒的方法:
wait() 将线程挂起:
notify()随机唤醒一个挂起线程:
notifyAll():唤醒所有挂起线程:
wait()与sleep():
通过wait()方法可以等待某个条件发生变化,而不是不断的对CPU进行空循环(这种空循环成为忙等待),wait()只有在通过notify()或者notifyAll()发生的时候,才会被唤醒并且去检查所产生的变化,并且在使用wait()方法的时候,会释放该对象锁(不会造成锁占用).
通过sleep()方法是将线程进行睡眠,而不会将锁释放, 也就是说 在sleep期间会一直占用synchronized方法的对象的锁.
下面通过一个例子来了解一下线程协作的使用方法:
首先是实体类car:
package car;
public class Car {
private boolean waxOn=false;//默认创建的car对象没有做打蜡处理
//进行打蜡处理
public synchronized void waxed(){
waxOn = true;//表示打蜡完成可以进行抛光
notifyAll();
}
//抛光完成
public synchronized void buffed(){
waxOn = false;// 准备上另一层蜡
notifyAll();
}
//等待打蜡
public synchronized void waitForWaxing()throws InterruptedException{
while(waxOn==false){
wait();//当车没有进行打蜡的时候 将线程挂起
}
}
public synchronized void waitForBuffing()throws InterruptedException{
while(waxOn==true){
wait();//当打蜡完成的时候将线程挂起等待响应.
}
}
}
car中只有一个waxOn的属性,当为false的时候就是没有进行打蜡处理,true代表打蜡处理完成,可以进行抛光操作,其中所有的方法全部是线程同步的,也就是加上了synchronized关键字.
下边是打蜡和抛光两个类:
package car;
import java.util.concurrent.TimeUnit;
import static sun.misc.Version.print;
1.打蜡
public class WaxOn implements Runnable{
private Car car;//一个car对象
public WaxOn(Car c){
//有参构造,将CAR对象传入进来
car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("打蜡开始!");//开始进行打蜡工作
TimeUnit.MILLISECONDS.sleep(500);//模拟打蜡工作
car.waxed();//进行打蜡工作
System.out.println("打蜡完成,等待抛光");
car.waitForBuffing();//打蜡完成等待抛光
}
}catch (InterruptedException e){
System.out.println("中断");
}
System.out.println("Ending wax on");
}
}
2.抛光
public class WaxOff implements Runnable{
private Car car;
public WaxOff(Car c){
this.car=c;
}
@Override
public void run() {
try{
while(!Thread.interrupted()){
car.waitForWaxing();//等待打蜡,进行抛光操作
System.out.println("开始抛光");
TimeUnit.MILLISECONDS.sleep(500);//抛光
System.out.println("抛光完成");
car.buffed();//更改状态
}
}catch (InterruptedException e){
System.out.println("中断");
}
System.out.println("Ending of WaxOff");
}
}
最后是测试类:
public class catTest {
public static void main(String[] args) throws InterruptedException {
Car car = new Car();//创建一个Car对象
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new WaxOff(car));
exec.execute(new WaxOn(car));
TimeUnit.MILLISECONDS.sleep(1000);
exec.shutdownNow();
}
}
执行结果:
打蜡开始!
打蜡完成,等待抛光
开始抛光
抛光完成
打蜡开始!
打蜡完成,等待抛光
开始抛光
中断
Ending of WaxOff
中断
Ending wax on
创建car对象以及ExecutorService对象用于调用run方法,传入两个Runable对象给execute.
新创建的car默认waxon为false,会先进行打蜡工作,调用Waxon的run()方法,打印出第一个打蜡开始的状态,在打蜡完成后将调用car中的waxed()方法,更改车的打蜡状态,表明打蜡完成可以进行抛光操作,同时唤醒所有wait()的线程,打印出第一个打蜡完成等待抛光,调用waitForBuffing操作,也就是等待抛光.
在上边wanON进行打蜡的同时,WaxOff这边会先进入线程,通过调用waitForWaxing()也就是等待打蜡,在这个方法里如果车未打蜡完成,将线程挂起,等待打蜡完成,此时线程挂起(阻塞),在WaxOn的打蜡完成后,此线程被唤醒,继续向下执行, 打印出第一个开始抛光,之后打印出第一个抛光完成,调用car.buffed()更改车的开始状态,进行第二次打蜡.
由于run方法是循环执行, 执行完这一轮后,会继续执行下一轮 就看到我们下边的结果了,同时通过shutdownNow()方法将线程结束, exec同样是调用interrupt方法,对其进行中断, 我们上边就catch到了interruptException,从而调用了结果.