实现并发的最直接的方式就是在操作系统级别上使用进程;进程是运行在自己空间内的自包容程序。多任务操作系统,可以通过周期性,将cpu从一个进程切换到另一个进程,在宏观上实现同时运行多个进程。
并发编程可以使我们将程序划分为多个分离地,互不干扰的独立任务(子任务);每一个独立任务都由执行线程来驱动;一个线程就是在进程中的单一顺序控制流;因此每个进程可以拥有多个并发执行的任务;我们所要学习的就是怎样去定义一个任务,然后定义线程。
Java所使用的并发系统,会共享内存或IO等资源;因此编写多线程程序最基本的困难在于协调多线程对资源的使用,使得不会发生死锁的情况;
线程机制是由执行程序表示的单一进程中创建任务,这种方式产生的一个好处就是操作系统的透明性。
Java的线程机制是抢占式的,表示调度机制会周期性的切断线程,从而为每个线程都分配到一个时间片;
实验代码1:如何定义任务
定义一个任务类,这个任务必须要实现Runable接口,实现run方法,定义任务的行为。
package com.JavaSE10.demo01;
//定义一个任务
public class LiftOff implements Runnable{
protected int countDown =10;//倒计时
private static int taskCount =0;
private final int id=taskCount++;
public LiftOff(){}
public LiftOff(int countDown){
this.countDown=countDown;
}
public String status(){
//判断倒计时是否>0
return "#"+id+"(" +(countDown>0?countDown:"LiftOff!") +").";
}
public void run() {//这个方法给到一个线程
while(countDown-- >0){
System.out.println(status());
Thread.yield();//让步方法,暂停当前正在执行的线程对象
}
}
//使用执行器
}
定义完任务后,创建一个主线程类执行任务
package com.JavaSE10.demo01;
public class MainThread {
public static void main(String[] args) {
LiftOff launch = new LiftOff();
launch.run();//调用任务类的run方法
}
}
同样地,我们也可以创建一个基本线程类,为任务分配执行线程。
package com.JavaSE10.demo01;
public class BasicThreads {
public static void main(String[] args) {
//Thread有一个构造器接收任务
Thread t =new Thread(new LiftOff());//Thread是一个线程;这条语句代表把任务给到线程
t.start();//t线程开始工作
//主线程无需等待子线程,所以告诉t线程开始工作后,马上往下执行
System.out.println("我是主线程");
}
}
我们也可以在一个基本线程类中创建多个线程执行多个任务
package com.JavaSE10.demo01;
public class MoreBasicThreads {
public static void main(String[] args) {
//创建5个线程执行5个任务
for (int i = 0; i < 5; i++) {
//执行顺序是不受控制的
//这种线程间的切换是由线程调度器自主控制的
Thread t = new Thread(new LiftOff());
t.start();
}
System.out.println("我是主线程");
}
}
java1.5以后工具包下的并发包里有一个执行器可以为我们管理线程,从而简化并发编程,我们不需要创建那么多线程,因为执行器在客户端和任务执行中提供了一个间接层。与客户端直接执行任务不同,中介对象去执行任务,执行器允许我们管理异步任务的执行,也无需显示的管理线程的声明周期;
通俗地讲就是:执行器拥有线程池,然后执行器帮我们管理在执行任务中的一系列操作。我们只需要为执行器指定线程池的类型。
线程池:缓冲线程池 、固定线程池
实验代码2:缓冲线程池
package com.JavaSE10.demo01;
import java.util.concurrent.*;
public class CacheThreadPool {
public static void main(String[] args) {
//取一个带缓冲的线程池,获得执行器
ExecutorService exec =Executors.newCachedThreadPool();
//让它执行5个任务;执行器中有一个execute方法接收任务,并开始执行;
//执行器在线程池里获取线程去执行任务;如果线程不够的话,它会自行创建线程,无需显式管理
/*
* 通常情况下,它会自行去创建跟所需线程数量相同的线程;
* 在不用的时候,会自动回收旧线程,如果有旧线程可以用,那么它就会调用旧线程去执行任务
* */
for (int i = 0; i < 5; i++) exec.execute(new LiftOff());
//最后用完要关闭执行器
exec.shutdown();
}
}
我们也可以取一个固定线程数量的线程池,通过预先一次性创建,从而达到节省系统开销的目的。
实验代码3:固定线程池
package com.JavaSE10.demo01;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
public static void main(String[] args) {
//创建一个固定数量的线程池
ExecutorService exec = Executors.newFixedThreadPool(5);
/**
* 一次性的预先分配线程,限制线程的数量,可以节省时间,不用为每个任务都固定地付出创建线程的开销
* 在任何线程池当中,在现有线程在可能的情况下都有可能被自动复用。在不用的时候,被线程池回收,但其他任务要用的话,可能会被复用
*
*/
for (int i = 0; i < 5; i++) exec.execute(new LiftOff());
exec.shutdown();
}
}
我们也可以为执行器分配一个单线程。
实现代码4:单线程的执行器
package com.JavaSE10.demo01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExec {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
/**
* 因为是单线程,所以会先把第一个任务执行完了,再去执行第二个任务,即顺序执行
*/
for (int i = 0; i < 5; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
runable接口中的run方法,这个run方法告诉线程,在开始执行任务的时候,从run方法开始执行。
如果我们要实现线程运行任务产生返回值,那么不能实现Runable接口。换一种方式,从javaSE5开始,引入了一个可调用接口,这个可调用接口具有一个类型参数(泛型),我们可以指定返回值的类型。
这个接口是Callable。
实验代码:
package com.JavaSE10.demo02;
import java.util.ArrayList;
import java.util.concurrent.*;
public class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id){
this.id=id;
}
public String call() throws Exception {
return "result of TaskWithResult "+id;
}
public static void main(String [] args){
//创建一个缓冲线程池给执行器
ExecutorService exec = Executors.newCachedThreadPool();
//执行任务的返回结果存到ArrayList中
//扩展:Future是用来接收Callable接口返回的信息,所以要指定泛型的类型
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
//当有返回值时,我们要使用submit方法
//从线程池中获取10个线程,每一个线程都创建一个任务去执行;而任务实现的是Callable接口,可以有返回值,所以我们可以拿到返回值。最终结果放在ArrayList中
for (int i = 0; i < 10; i++) results.add(exec.submit(new TaskWithResult(i)));
//查看返回结果
for (Future<String> fs:results){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally{
exec.shutdown();
}
}
}
}
有时候,我们可能希望线程等一等,那么就可以设置线程休眠
传统的方式,用线程的sleep方法
javaSE5以后有了新的方式 TimeUnit.MILLISECONDS.sleep
扩展知识:
TimeUtil是对Thread.sleep方法的包装,实现是一样的,只是多了时间单位转换和验证,然而TimeUnit枚举成员的方法却提供更好的可读性
sleep和yield的区别
1.sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会
2.yield()方法只会给相同优先级或更高优先级的线程以运行的机会
3.线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态
4.sleep()方法声明会抛出InterruptedException,而yield()方法没有声明任何异常
5.sleep()方法比yield()方法具有更好的移植性(跟操作系统CPU调度相关) ———————————————— 版权声明:本文为CSDN博主「弗兰随风小欢」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32575047/article/details/80085576
package com.JavaSE10.demo02;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SleepingTask extends LiftOff {
public void run() {//这个方法给到一个线程
// Thread.currentThread();//获取到当前的线程
try {
while(countDown-- >0){
/*
传统的方式,用线程的sleep方法
javaSE5以后有了新的方式 TimeUnit.MILLISECONDS.sleep
* 每输出一个数字,就睡眠100毫秒,相当于作一个让步
* 100毫秒之后,不代表会马上执行该线程,要看调用机制执行哪个线程
*
* */
System.out.println(status());
// Thread.sleep(100);//指定睡眠时间 (毫秒)
TimeUnit.MILLISECONDS.sleep(100);//效果是一样的,这是JDK1.5以后的方法
}
} catch (InterruptedException e) {
System.err.println("Interrupted");
}
}
public static void main(String[] args) {
//一般用缓冲线程池,只有有特定的需求、目的才去使用其他类型线程池。
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute( new SleepingTask());
}
exec.shutdown();
}
}
在没有设置线程优先级的情况下,所有线程的优先级都是相同的;在相同的情况执行顺序的先后,是由线程调度机制控制的。
优先级是指:把线程的重要性传给调度器,调度器就让优先级高的线程先执行,但这不意味着优先权低的线程得不到执行。只是优先级别较低的线程,执行频率稍微低一点;在绝大多数的时候,所有的线程都是以默认的优先级执行。少数情况,认为控制优先级。因为在人为的情况下,通常很难保证达到一个预期的效果
package com.JavaSE10.demo02;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimplePriorties implements Runnable{
private int countDown =5;
//这是一种轻重量的同步方式,一旦修改其值,马上传递给各个线程。
private volatile double d;
private int priority;
public SimplePriorties(int priority){
this.priority=priority;
}
public String toString(){
//获取当前线程的名称
return Thread.currentThread().getName()+":"+countDown;
}
public void run() {
//获取当前线程,然后设置优先级
Thread.currentThread().setPriority(priority);
while(true){
for(int i=0;i<10000;i++){
d+=(Math.PI+Math.E)/(double)i;
if(i%1000 ==0){
Thread.yield();//线程让步方法
}
System.out.println(this);
if(--countDown==0)return;//停止运行
}
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
//优先级我们一般是用Thread中的优先级数,不要自己设置的数字;为了便于跟系统映射优先级
exec.execute(new SimplePriorties(Thread.MIN_PRIORITY));
}
exec.execute(new SimplePriorties(Thread.MAX_PRIORITY));
exec.shutdown();
}
}