多线程基础学习

多线程的学习

1、Callable和runnable区别:

class c implements Callable<String>{
	@Override
	public String call() throws Exception {
		return null;
	}
}

class r implements Runnable{
	@Override
	public void run() {
	}
}

相同点:

1、两者都是接口

2、两者都需要调用Thread.start启动线程

不同点:

1、如上面代码所示,callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值

2、call方法可以抛出异常,但是run方法不行

3、因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常

4、callable和runnable都可以应用于executors。而thread类只支持runnable

测试:使用线程池来运行

public static void main(String[] args) throws Exception{
		//1 创建一个线程池
		//调用Executors类的静态方法
		ExecutorService service = Executors.newFixedThreadPool(10);
		//2提交runnable对象
		service.submit(new Runnable() {
			@Override
			public void run() {
			}
		});
		//3 提交callable对象
		service.submit(new Callable<String>() {
			@Override
			public String call() throws Exception {
				return null;
			}
		});
		//4 关闭线程池
		service.shutdown();
	}

使用Thread来创建一个Callable,调用

Tread的构造方法中没有Tread(Callable callable)方法,所以需要一个既和Runable有关系,又与Callable有关系的类,FutureTask类。

FutureTask实现了Runable接口,并且其构造方法可以传递Callable

public static void main(String[] args) throws Exception{
//		callable用thread调用需要一个FutureTask中间类
		Runnable target;
		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				return null;
			}
		};
		FutureTask futureTask = new FutureTask<>(callable);
		Thread thread = new Thread(futureTask);
		thread.start();
//		获取返回值
		Object o = futureTask.get();
    	}

做个简单的小测试:

  • 有4个同学,1同学计算1+2+3+4,2同学计算1+2+…+10000,3同学计算20+21+22,4同学计算100+200+300。
  • 这时同学2的计算量比较大,FutureTask单开启线程给2同学计算,先汇总1,3,4同学的计算数据,等2同学完成后再调用get汇总全部。
public static void main(String[] args) throws ExecutionException, InterruptedException {
		FutureTask futureTask = new FutureTask(new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				System.out.println(Thread.currentThread().getName()+System.currentTimeMillis()+"进入了callable中");
				int result = 0;
				for (int i = 0 ;i<10001;i++){
					result += i;
				}
				System.out.println(Thread.currentThread().getName()+System.currentTimeMillis()+"结束");
				return result;
			}
		});
		Thread thread = new Thread(futureTask);
		thread.start();
		System.out.println("第二位同学的计算值:"+futureTask.get());
		int a = 1+2+3+4;
		int b = 20+21+22;
		int c = 100+200+300;
		int all = a+b+c+(int)futureTask.get();
		System.out.println("所有的计算值:"+all);

	}

2、线程池基本方法了解:

1、基本了解

java.util.concurrent包中的ExecutorService。ExecutorService就是Java中对线程池的实现

Java API对ExecutorService接口的实现有两个,所以这两个即是Java线程池具体实现类

1. ThreadPoolExecutor
2. ScheduledThreadPoolExecutor

除此之外,ExecutorService还继承了Executor接口(注意区分Executor接口和Executors工厂类),这个接口只有一个execute()方法,最后我们看一下整个继承树:

2、ExecutorService的创建

创建一个什么样的ExecutorService的实例(即线程池)需要根据具体应用场景而定,不过Java给我们提供了一个Executors工厂类,它可以帮助我们很方便的创建各种类型ExecutorService线程池,Executors一共可以创建下面这四类线程池:

1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutorScheduledThreadPoolExecutor这两个类的实例。

3、基本使用方法

ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(new Runnable() {
public void run() {
    System.out.println("Asynchronous task");
}
});

executorService.shutdown();

4、ExecutorService的执行

ExecutorService有如下几个执行方法:

- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
4.1 execute(Runnable)

-------接收一个runnable实例,犹豫runnable缘故,是没有返回值的,且是异步执行

这个方法接收一个Runnable实例,并且异步的执行,请看下面的实例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(new Runnable() {
public void run() {
    System.out.println("Asynchronous task");
}
});

executorService.shutdown();

这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例(下面会介绍)。

4.2 submit(Runnable)

submit 和execute的区别:

1)submit 可以传入runnable或者是callable,execute只能传入runnable

2)由于runnable 和callable区别,会导致,是否有任务的结果

submit(Runnable)和execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕,请看下面执行的例子:

Future future = executorService.submit(new Runnable() {
public void run() {
    System.out.println("Asynchronous task");
}
});

future.get();  //returns null if the task has finished correctly.

如果任务执行完成,future.get()方法会返回一个null。注意,future.get()方法会产生阻塞。

4.3 submit(Callable)

submit(Callable)和submit(Runnable)类似,也会返回一个Future对象,但是除此之外,submit(Callable)接收的是一个Callable的实现,Callable接口中的call()方法有一个返回值,可以返回任务的执行结果,而Runnable接口中的run()方法是void的,没有返回值。请看下面实例:

Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
    System.out.println("Asynchronous Callable");
    return "Callable Result";
}
});

System.out.println("future.get() = " + future.get());

如果任务执行完成,future.get()方法会返回Callable任务的执行结果。注意,future.get()方法会产生阻塞。

4.4 invokeAny(…)

invokeAny(…)方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。请看下面实例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 2";
}
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
    return "Task 3";
}
});

String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

大家可以尝试执行上面代码,每次执行都会返回一个结果,并且返回的结果是变化的,可能会返回“Task2”也可是“Task1”或者其它。

4.5 invokeAll(…)

invokeAll(…)与 invokeAny(…)类似也是接收一个Callable集合,但是前者执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。情况下面这个实例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 1";
}
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
    return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 3";
}
});

List<Future<String>> futures = executorService.invokeAll(callables);

for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
}

executorService.shutdown();
5、ExecutorService的关闭

当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。

举个例子,如果的应用程序是通过main()方法启动的,在这个main()退出之后,如果应用程序中的ExecutorService没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为ExecutorService中运行的线程会阻止JVM关闭。

如果要关闭ExecutorService中执行的线程,我们可以调用ExecutorService.shutdown()方法。在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。

如果我们想立即关闭ExecutorService,我们可以调用**ExecutorService.shutdownNow()**方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。

3、ScheduledExecutorService用法

创建一个线程池,主要用来,定时,或者周期行的执行某些任务

		ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
		scheduledExecutorService.schedule(new Runnable() {
			@Override
			public void run() {
				System.out.println("延迟三秒");
			}
		},3, TimeUnit.SECONDS);
		scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				System.out.println("延迟1秒,每3秒钟执行一次");
			}
		},1,3,TimeUnit.SECONDS);
	}

4、线程的生命周期

线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。 在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自 运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换

1、new新建状态 :线程被创建之后,就处于新建状态,此时只由JVM来给其分配内存,并初始化其变量值

2、runnable就绪状态:当线程对象调用了start方法后,线程就进入就绪状态,JVM为其创建方法,调用栈和程序计数器,等待执行;

3、running运行状态,当在就绪状态的线程获得了cpu的使用权之后,此时开始执行run方法的中的具体代码了,此时线程处于运行状态;

4、blocked阻塞状态:阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice(时间片),暂时停止运行。 直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状 态。

阻塞的情况分三种:

等待阻塞(o.wait->等待对列): 运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue) 中。

同步阻塞(lock->锁池) 运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线 程放入锁池(lock pool)中。

其他阻塞(sleep/join) 运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时, JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态

5、线程死亡(DEAD) 线程会以下面三种方式结束,结束后就是死亡状态。

正常结束 1. run()或 call()方法执行完成,线程正常结束。

异常结束 2. 线程抛出一个未捕获的 Exception 或 Error。

调用 stop 3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QfBQW3YP-1645523308700)(E:\笔记\image-20220222163705954.png)]

5、终止线程的4种方式

5.1、程序正常运行结束,线程自动结束

5.2、使用标志位来终止线程

般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的 运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如: 最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false 来控制 while 循环是否退出,代码示例:

package com.example.ThreadStudy;

/**
 * 通过标志位退出线程
 *
 * @author LZH
 * @version 1.0
 * @date 2022/02/22 17:09:00
 */
public class ThreadTest1 extends Thread{
    public volatile boolean isExit = true;
    @Override
    public void run(){
        while (isExit){
            System.out.println("通过标志位退出线程");
        }
    }
}

义了一个退出标志 exit,当 exit 为 false时,while 循环退出,exit 的默认值为 true.在定义 exit 时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只 能由一个线程来修改 exit 的值。

java中volatile关键字的含义

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。

Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。

synchronized (代码块)

同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用

synchronized 修饰的方法 或者 代码块。

volatile(变量)

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作

5.3、使用Interrupt 方法结束线程

1、程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时, 会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。 阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让 我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实 际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正 常结束 run 方法。

2、线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用 interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理

isInterrupted 默认是false ,如果try catch 中的线程阻塞的话,会抛出异常,结束本次线程,

package com.example.ThreadStudy;

/**
 * 通过interrupt方法退出线程
 *
 * @author LZH
 * @version 1.0
 * @date 2022/02/22 17:09:00
 */
public class ThreadTest2 extends Thread{
    @Override
    public void run(){
        while (!isInterrupted()){ //不是阻塞状态的话,通过isInterrupted返回值来判断
            try {
                Thread.sleep(6 * 300);//阻塞状态的话,通过异常的捕获,来break当前的循环
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;//退出循环
            }
        }
    }
}

5.4 使用stop 方法终止线程(线程不安全)

程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关 闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是: thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子 线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈 现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因 此,并不推荐使用 stop 方法来终止线程。

6、sleep 和wait 的区别

sleep 和 wait 区别
     1、sleep 是 Thread 类的方法,wait 是object 类的方法
    2、sleep 睡眠的时候是不会释放对象锁的,wait释放对象锁,并且进入等待池中,等再次获取到锁   
    3、sleep 睡眠之后,时间到了之后,自动回复运行状态

7、start和run方法的区别

  1Thread调用start 是线程启动的方法,调用start之后,线程进入了runnable就绪状态,不用管run方法是否执行,可以执行下面的方法
  2、run方法执行之后,线程进入运行状态
  3、run方法中是线程体,执行完成之后,线程结束,cpu调用其他的线程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值