多线程1-第一阶段-第十六天

进程与线程

  1. 进程: 进程是程序的一次动态执行的过程,它经历了从代码加载,执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。程序是一个静态的概念,进程是一个动态的概念。
  2. 线程:线程依附于指定的进程,并且可以快速启动以及并发执行。一个进程中可以包含多个进程,至少包含一个,每个线程就是这个进程的一条执行路径,每个JAVA程序默认包含有一个主线程(运行main方法的)。
  3. 进程可以是线程的容器,进程不完成具体的指定执行,Java指令都是通过线程去执行的,线程是CPU进行调度的最小单位。
  4. 进程之间的数据是不能共享的,但是同一个进程的多个线程之间可以共享数据。比如电脑上面的记事本和画图工具,这两个进程一旦启动,只通过这两个进程无法进行数据共享,想要数据共享那么必须依附其他进程,并通过其上面的线程进行数据传输。

并行和并发

  1. 并行: 在多核CPU下每个核心单独负责一个进程,也就就是能够同时处理多个事情的能力。
  2. 并发: 在电脑中CPU一般会将执行时间分成很多个很小的时间片,然后将这些时间片一次分给不同的线程去执行,并且在多个线程中来回快速的切换,在切换的过程中由于时间片很小,人一般感受不到,就会感觉到任务在同时进行。总的来说并发就是拥有处理多个事情的能力,但不是一起处理,宏观上是并行,微观上是串行。
  3. 在多核CPU下电脑的运行都会在伴有并行的同时夹杂着并发,因为电脑已启动就会有几千个线程,几百个进程,八个CPU肯定是处理不过来的。
    在这里插入图片描述

多线程

多线程编程是Java语言最为重要的特性之一,利用多线程技术可以提升单位时间内的程序处理性能,也是现代程序开发中高并发的主要设计形式。

1. 多线程的优点

  1. 充分利用多核CUP的优势,提高程序的执行效率(可以将多个任务分给多个不同的CPU进行并行,这样就能节省很多时间)。但对于单核CPU执行效率没有提高,因为在并发中,是将多个时间片分给多个线程,又由于切换时间片分配时间片给每个线程需要时间,所以线程的总执行时间肯定不减反增。
  2. 对于一些非常耗时的IO操作,可以减少用户的等待时间。
  3. 从方法调用者的角度来说可以实现异步调用。
    3.1 同步调用: 被调方法如果没有执行完毕,主调方法一直处于等待的状态。
    3.2 异步调用: 无需等待被调用的方法执行完毕,调用方可以在这个过程中继续执行后续的代码。

2. 线程常见的三种创建方式

1. Thread类实现多线程

java.lang.Thread是一个负责线程操作的类,任何类只要继承了Thread类就可以成为一个线程的主类。同时线程类中需要明确重写父类中的run()方法(方法定义: public void run()),当产生了若干个线程类对象时,这些对象就会并发执行run()方法中的代码。
实现方式:创建一个类并从Thread这个父类继承即可,然后创建一个对象调用线程对象的start方法。

举例代码:

package com.qianfeng.day16;

public class MyThread extends Thread{
	@Override
	public void run() {
		for (int i = 101; i <= 200; i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(i);
		}
	}
}

2. Runnable接口实现多线程

出现原因:在面向对象的程序设计通过接口实现标准定义与解耦操作,由于在继承子父类体系中联系太紧密,为了减少耦合,所以多线程提供了Runnable接口实现多线程开发。
实现方式:创建一个类并实现Runnable接口,并实现接口中唯一的方法(run方法),然后通过Thread类中带参构造器(public Thread(Runnable target))创建对象调用start()方法。

代码举例:

package com.qianfeng.day16;

public class MyThread1 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 101; i <= 200; i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(i);
		}
	}

}

3. Callable接口实现多线程

产生原因:Java中针对多线程执行运用Runnable接口有许多弊端,比如重写的run()方法没有返回值,不能提供throws往外申明异常类型只能通过try-catch解决异常,基于上述两种原因,提供了Callabe接口。
实现方式:创建一个类并实现Callable<String>接口,并实现接口中唯一的方法(call方法),将一个Callable接口的实例转成Runnable接口,然后通过Thread类中带参构造器(public Thread(Runnable target))创建对象调用start()方法。

代码举例:

package com.qianfeng.day16;

import java.util.concurrent.Callable;

public class MyThread2 implements Callable<String>{

	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("子线程正在执行!!!");
		Thread.sleep(5000);
		return "hello world";
	}
	
}

测试上述三种实现多线程方式

package com.qianfeng.day16;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test01 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//第一种创建线程的方式
		MyThread t1 = new MyThread();
		t1.start();
		//第二种创建线程的方式
		MyThread1 te = new MyThread1();
		Thread t2 = new Thread(te);
		t2.start();
		//第三种线程的创建和启动方式
		MyThread2 t3 = new MyThread2();
		FutureTask<String> task = new FutureTask<String>(t3);
		Thread t31 = new Thread(task);
		//可以获得接口的实现类重写方法的返回值
		String str = task.get();
		System.out.println(str);
	}
}

4. 三种实现多线程的注意事项

  1. 使用Thread类和Runnable接口都可以以同一功能的方式来实现多线程,但是从
    Java实际开发的角度来说,推荐使用Runnable接口,因为采用这种方式可以有效的避免单继承的局限。
  2. 在使用Callable接口的时候通过调用未来任务类对象的get方法获取子线程执行返回的结果,在此期间,如果子线程还没有执行完成,此时主线程会处于等待状态。

3. 线程的常用方法介绍

  1. setName(String name) 设置当前线程的名称,系统默认为每个线程取一个名字:Thread-num,后期程序中就可以通过getName方法获取当前线程的名称
  2. currentThread() 这个方法不是线程对象的方法,属于线程类的一个静态方法 Thread.currentThread(),始终获取的是调用这个方法所在的线程对象,一般可以通过此对象调用getName()获得线程名称
  3. sleep(long 毫秒数) 也是一个线程类的静态方法,让调用该方法的线程进入到休眠的状态,不再和其他的线程一起抢占CPU的时间片
    此方法有两种解除休眠的情况:一种是睡够了时间 另一种是被打断了(打断的时候会抛出一个异常)
  4. yield()是一个本地方法,让当前线程放弃CPU的执行权,重新回到执行的等待队列
  5. join()线程合并,还可以指定等待的时间,传入一个毫秒数
  6. setPriority(Thread.MIN_PRIORITY);设置线程的优先级 1 5 10

问题:在一个程序中添加一个监控系统,该线程不会结束,除非强制线程结束
解决方案:
1.第一种方式:调用线程对象的stop方法,但是这个方法不建议使用,没有给结束的线程一个缓冲的时间,直接将线程对象销毁,过于粗暴
2.第二种方式:调用线程对象的interrupt()方法打断当前线程(本质是让这个线程对象的状态isInterrupted置为true),解决方法是在catch块中手动调用线程对象的interrupt方法将标记置为true

package com.qianfeng.day16;

public class Monitor implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		Thread thread = Thread.currentThread();
		while(true) {
			System.out.println("正在监控系统...");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				System.out.println("睡眠过程被打断了...");
				thread.interrupt();
			}
			
			if(thread.isInterrupted()) {
				System.out.println("进行监控数据的保存....");
				break;
			}
		}
	}

}

class TestThreadStop {
	public static void main(String[] args) throws InterruptedException {
		Monitor monitor=new Monitor();
		Thread t=new Thread(monitor);
		
		t.start();
		System.out.println("主线程干自己的事...");
		Thread.sleep(5000);
		
		//1.结束某个子线程
		//t.stop();
		//2.正确的终止线程执行的方式
		t.interrupt();
		System.out.println("main方法结束");
	}
}

此问题的细节:

如果我们打断的是正常执行的代码,打断标记会被置为true,但是如果打断的是正在休眠的部分,此时线程对象中的是否被打断的标记依然为false,所以需要在打断的时候再次将打断标记设为true

守护线程

描述:只要程序中其他所有的线程全部执行完毕,那么守护线程自动结束,例如 垃圾回收器就是一个守护线程
在Java中GC(垃圾回收机制)就是一个守护线程
代码:thread.setDeamon(true);意思上当所有线程执行结束那么它也自动结束进程

3. 多线程的运行状态

在这里插入图片描述

    1. 创建状态:在程序中用构造方法创建一个线程对象后,新的线程对象便处于新建状态,此时他已经有了相应的内存空间和其他资源,但还处于不可运行状态,新建一个线程对象可采用Thread类造方法来实现,例如Thread thread = new Thread();
    1. 就绪状态:新建线程对象后,调用该线程的start()方法就可以启动线程,当线程启动时线程进入就绪状态,此时线程将进入线程队列排队,等待CPU调度服务,这表明它已经具有了运行条件。
    1. 运行状态:当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态,此时将自动调用该线程对象的run()方法,run()方法定义了该线程的操作和功能。
    1. 阻塞状态: 一个正在运行的线程,在某些特殊情况下,如被人为挂起或需要运行耗时的输入/输出操作时,将让出cpu并暂时中止自己的运行,进入阻塞状态,在可运行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入阻塞状态,阻塞时,线程不能进入队列,只有当引起阻塞的原因被消除好。线程才可以转入就绪状态。
    1. 终止状态:当线程体中的run()方法运行结束后,线程即处于终止状态,处于终止状态的线程不具有继续运行的能力。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值