多线程编程基础

今天学习了多线程编程基础,理解了串行与并发、进程与线程、学会了如何创建线程

多线程编程基础

基础概念

  • 程序是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是一个静态的概念。
  • 进程是具有一定独立功能程序的运行过程,是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。
    • 进程是程序的一次执行过程,通常是一个可执行程序在内存中的一个完整副本,每个进程都有自己的数据段、栈段和代码段,是一段完整的程序,在内存中占据较大的空间,是系统进行调度和资源分配的一个独立单位。是一个动态的概念
    • 多进程是指操作系统能同时运行多个任务(程序)。
  • 线程是进程中的一个独立执行线索,是进程中的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈(栈帧)
    • 线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。创建一个进程时,它的第一个线程称为主线程,它由系统自动生成。
    • 多线程是指在同一程序中有多个顺序流在执行。

进程

每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)。
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器PC,线程切换开销小。(线程是cpu调度的最小单位)。

启动进程的方法

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
cmd /c启动后关闭窗口。
启动进程方法1

ProcessBuilder pb=new ProcessBuilder("cmd","/c","ipconfig/all"); //用于构建进程的对象
Process p=pb.start();//启动进程
BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));//获取进程的输出内容
String temp=null;
while((temp=br.readLine())!=null)
     System.out.println(temp);

如果使用mac系统则编码为

ProcessBuilder pb=new ProcessBuilder("bash","-c","ifconfig");

启动进程方法2

String cmd="cmd "+"/c "+"ipconfig/all";
Process process=Runtime.getRuntime().exec(cmd);
BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));
String temp=null;
while((temp=br.readLine())!=null)
    System.out.println(temp);

动态生成代码并编译执行

        File f = new File("T1.java");
		if (f.exists())
			f.delete();
		// 生成代码文件
		PrintWriter pw = new PrintWriter(new FileWriter("T1.java"));
		pw.println("public class T1{");
		pw.println("public static void main(String[] args) throws Exception{");
		pw.println("System.outprintln(\"Hello Java!\");");
		pw.println("}}");
		pw.close();
		// 编译T1.java javac T1.java
		Process process = Runtime.getRuntime().exec("cmd /c javac T1.java");
		boolean runnable = true;
		// process.getInputStream()用于获取进程的输出信息,不是报错信息。如果需要获取报错信息则应该使用
		process.getErrorStream();
		// javac编译通过实际上是没有响应信息,所以这里获取响应信息是不正确的
		BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
		while (true) {
			String temp = br.readLine();
			if (temp == null)
				break;
			System.out.println(temp);
			runnable = false;
		}
		// 运行T1.class
		if (runnable) {
			process = Runtime.getRuntime().exec("cmd /c java T1");
			br = new BufferedReader(new InputStreamReader(process.getInputStream()));
			while (true) {
				String temp = br.readLine();
				if (temp == null)
					break;
				System.out.println(temp);
			}
		}

进程的三大特征
独立性
动态性
并发性

僵尸进程和孤儿进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源 —是对系统资源的浪费,必须解决。
孤儿进程是一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。—没有什么危害。

并行与并发

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景
中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS(每秒钟处理的事务数)或者QPS(每秒钟处理的请求数)来反应这个系统的处理能力。

主线程

线程是进程中的一个实体,用来描述进程的执行,它负责执行包括在进程的地址空间中的代码。
创建一个进程时,它的第一个线程称为主线程,它由系统自动生成
它是产生其他子线程的线程通常它是最后完成执行,因为它执行各种关闭动作。注意这里不绝对。

public class T1 {
	public static void main(String[] args) {
		Thread t1 = new Thread() {// 定义子线程,是主线程启动的线程,是主线程的子线程
			public void run() {
//Thread.currentThread()获取当前正在运行的线程对象
//.getName()获取线程对象的标识名称
				System.out.println(Thread.currentThread().getName());
			}
		};
		t1.start(); // 启动子线程
		System.out.println(Thread.currentThread().getName());
	}
}

进程中线程之间的关系
线程不像进程,一个进程中的线程之间是没有父子之分的,都是平级关系。即线程都是一样的, 退出了一个不会影响另外一个。但是所谓的"主线程"main,其入口代码是类似这样的方式调用mai的:exit(main(…))。main执行完之后 会调用exit()。exit会让整个进程over终止,那所有线程自然都会退出。

进程和线程的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中需要协作同步。
4、CPU分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体,两者都是动态的概念。

多线程

多线程方式是指在一个程序中存在多个线程,每一个线程执行一个独立的任务,多个线程可以并发执行

  • 在Java中,一个应用程序可以包含多个线程,每个线程执行特定的任务,并可与其他线程并发执行
  • 多线程使系统的空转时间最少,提高CPU利用率,多线程编程环境用方便的模型隐藏CPU在任务间切换的细节
    吞吐量,充分利用cpu资源,减少CPU空转时间;伸缩性,通过CPU核数来提升性能.
    在许多情况中可以显式地使用线程以提高程序的性能、响应速度或组织。要用到多线程的主要是需要处理大量的IO操作时或处理的情况需要花大量的时间等等,比如:读写文件、视频图像的采集、处理、显示、保存等。
    线程的工作场景主要有两条:
    1、并发操作,避免阻塞和更有效利用资源。典型的例子有:在长时间工作的程序中使用工作线程避免界面失去响应。在网络下载程序中,使用多个线程提高对网络的使用效率,更快下载文件。
    2、并行,线程是处理器调度的最小单位。如果你的计算机配置了多个处理器或者内核,那么可以同时利用多个处理器同时计算,加快问题解决的速度
    基于线程的多任务处理的优点
    1.基于线程所需的开销更少
  • 在多任务中,各个进程需要分配它们自己独立的地址空间
  • 多个线程可共享相同的地址空间并且共同分享同一个进程
    2.进程间调用涉及的开销比线程间通信多
    3.线程间的切换成本比进程间切换成本低

Java与多线程

Java语言的一个重要功能特点就是内置对多线程的支持,它使得编程人员可以很方便地开发出具有多线程功能,能同时处理多个任务的功能强大的应用程序
Java的所有类都是在多线程的思想下定义的,Java利用线程使整个系统成为异步
每个Java程序都有一个隐含的主线程
application main 方法Applet小程序【application let】,主线程指挥浏览器加载并执行java小程序

线程的编程4种实现方法

1、继承Thread
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。

public class Thread implements Runnable
@FunctionalInterface //函数式接口,其中包含一个抽象方法run
public interface Runnable {
      public abstract void run();
}

启动线程的唯一方法就是通过Thread类的start()实例方法,不能直接调用run()方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法

class LeftThread extends Thread {
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println("左手一个慢动作...");
			try {// 使当前正在执行的线程休眠等待500ms
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
//在主线程中启动这个线程
	Thread t1=new LeftThread();
	t1.start();//不能直接调用run方法,否则就是在主线程中单线程执行,不是多线程

粗略的阅读实现

public synchronized void start() {
		if (threadStatus != 0) // 当前线程状态检查
			throw new IllegalThreadStateException();
		group.add(this); // 将当前线程对象添加到一个线程组中进行管理
		boolean started = false; // 标志值用于判断当前线程是否已经启动
		try {
			start0();
			started = true;
		} finally {
			try {
				if (!started) {
					group.threadStartFailed(this);
				}
			} catch (Throwable ignore) {
			}
		}
	}
	private native void start0();// 通过对等类实现启动线程的细节,内部就是自动调用run()方法

定义线程类

class LeftThread extends Thread{
//定义左手线程类,只需要覆盖(重写)定义其中的run()方法,这里实际就是线程的执行线索,执行的程序
    public void run(){
        for(int i=0;i<50;i++)
           System.out.println("左手一个慢动作....")
    }
}

调用方法

Thread t1=new LeftThread();
t1.start();//启动线程必须使用start方法,而不能是调用run方法
//t1.run();不允许直接调用run方法,因为这种调用方式不是多线程,而是单线程
public class T4 {
	public static void main(String[] args) {
		new Thread() {
			public void run() {
				System.out.println("匿名类内部输出" + Thread.currentThread().getName());
			}
		}.run();// 从语法上来说,可以直接调用run方法,但是运行run方法则不是多线程
//.start();输出结果证明不是main线程
		System.out.println("main方法输出" + Thread.currentThread().getName());
	}
}

如果判断线程运行情况:

// 最简单的方式是查看线程个数
		// Thread.currentThread()用于获取当前正在运行这个代码的线程对象
		System.out.println(Thread.currentThread());
		Thread t1 = new LeftThread();
		// t1.run();在当前主线程中执行t1对象中的run方法
		// t1.start();可以在LeftThread中获取到另外一个线程对象
		public class LeftThread extends Thread {
			@Override
			public void run() {// 这里包含的就是执行线索
				System.out.println(Thread.currentThread());
			}
		}
	}

2、实现Runnable接口
接口定义

@FunctionalInterface //函数式接口,简化定义方式为lambda表达式
public interface Runnable{
    public abstract void run();
}

可以使用lambda表示式进行定义,或者使用匿名内部类进行定义

class RightRunnable implements Runnable{
     public void run(){
         for(int i=0;i<50;i++)
             System.out.println(Thread.currentThread().getName()+"说:右手慢动作重播!");
     }
}

启动方法:

//启动通过实现Runnable接口定义的线程对象,不能直接使用Runnable r=new RightRunnable();
Thread t2=new Thread(new RightRunnable());
t2.start

使用lambda表达式的写法

// 匿名内部类的写法
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 20; i++) {
					System.out.println(Thread.currentThread() + "--" + i);
				}
			}
		});
		t1.start();
		// lambda表达式的写法---要求接口必须是函数式接口
		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 20; i++) {
				System.out.println(Thread.currentThread() + "--" + i);
			}
		});
		t1.start();

3、使用Callable和Future接口创建线程
具体是创建Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程
接口定义
Callable接口用于定义线程的执行逻辑

@FuntionalInterface //属于函数式接口,所以可以直接使用lambda表达式进行定义
public interface Callable<V>{ //<>写法叫做泛型,用于定义返回的数据类型
    V call()throws Exception;
}

Future接口用于Callable接口中call方法的执行结果,并可以测试Callable的执行情况

public interface Future<V>{//用于获取Callable接口中的call方法的执行结果
    boolean cancel(boolean mayIntersupteIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException,ExecutionException;
    V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException;
}

系统提供了一个Future接口的实现类FutureTask

public class FutureTask<V> implements RunnableFuture<V> //这个实现类实现了两个接口Runnable和Future

有了FutureTask类则可以将Callable实现封装到FutureTask对象中,同时由于FutureTask实现了Runnable接口,所以可以作为Thread类的构造器参数传入Thread对象中,供线程执行对应call方法中的逻辑

Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装
MyCallable对象
Thread thread = new Thread(ft);//FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果

使用方法:
1、 定义Callable接口的实现类

public class MyCallable implements Callable<Integer>{
int begin=0;
int end=0;
public MyCallable(int begin, int end){
this.begin=begin;
this.end=end;
}
public Integer call()throws Exception{
int res=0;
for(int k=begin; k<=end; k++)
res+=k;
System.out.println(Thread.currentThread()+"---"+res);
return res;
}
}

2、调用

FutureTask[] arr=new FutureTask[10];
for(int i=0;i<10;i++){
arr[i]=new FutureTask<>(new MyCallable(i*10+1,(i+1)*10));
Thread t=new Thread(arr[i]);
t.start();
}
int res=0;
for(int i=0;i<arr.length;i++)
res+=((Integer)arr[i].get());
System.out.println("计算结果为:"+res);

注意:FutureTask实现了Future和Runnable接口,所以new Thread(futureTask),当执行thread.start()方法时会自动调用Callable接口实现中的call方法。当调用futureTask.get()方法时可以获取对应的线程对象的执行结果,如果线程并没有返回时,当前线程阻塞等待

4、使用线程池创建线程
享元模式
享元模式Flyweight Pattern主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式

  • 优点:大大减少对象的创建,降低系统内存的使用,以提高程序的执行效率。
  • 缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部
  • 状态的变化而变化,否则会造成系统的混乱。不考虑线程问题
public interface IShape {
    void draw();
}
public class Circle implements IShape {
     public void draw() {
        System.out.println("我画一个圆");
   }
}

定义工厂类提供获取实例的方法

public class ShapeFactory {
private static IShape[] shapes = new IShape[20];
static{
    for(int i=0;i<shapes.length;i++)
     shapes[i]=new Circle();
  }
public static IShape getShape() {
   IShape res = null;
    for (int i = shapes.length - 1; i >= 0; i--) {
        res = shapes[i];
          if (res != null) {
          shapes[i]=null;
          break;
   }
}
return res;
}
public static void relaseShape(IShape circle) {
    for (int i = 0; i < shapes.length; i++) {
        IShape obj = shapes[i];
           if (obj == null)
           shapes[i] = circle;
         }
    }
}

使用ExecutorService、Callable、Future实现有返回结果的线程 ,连接池的具体实现实际上是依赖于ThreadPoolExecutor

//需要计算1+...+10000
public class T1 {
	public static void main(String[] args) throws Exception {
		// 创建一个固定大小的连接池,经常实现使用少量线程以应对波峰请求
		ExecutorService es = Executors.newFixedThreadPool(3);
		// 为了使用返回结果所以使用Callable
		Future[] fs = new Future[10];
		for (int i = 0; i < fs.length; i++) {
			Callable<Integer> caller = new MyCallable(i * 1000 + 1, (i + 1) * 1000);
			// 使用线程池执行任务并获取Future对象
			fs[i] = es.submit(caller);
		}
		int res = 0;
		for (int i = 0; i < fs.length; i++) {
			Object obj = fs[i].get();
			if (obj != null && obj instanceof Integer)
				res += (Integer) obj;
		}
		es.shutdown();// 关闭线程池
		System.out.println("Main:" + res);
	}
}
class MyCallable implements Callable<Integer> {
	private int begin;
	private int end;
}
	public MyCallable(int begin, int end) {
		this.begin = begin;
		this.end = end;
	}
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread() + "---" + begin + ".." + end);
		int res = 0;
		for (int i = begin; i <= end; i++)
			res += i;
		return res;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值