SE高阶(4):多线程(并发)—①创建启动方式和控制线程方法
2017-04-25 22:49
561 查看
进程概念
进程是操作系统运用程序实例,拥有独立的内存空间和数据,一个进程包含多个子线程,不同进程相互独立。进程的特征:
独立性:进程是系统中独立存在的实体,拥有独立的资源,每个进程都有自己的内存空间。一个进程不能直接访问另一个进程的内存空间。
动态性:进程是一个正在系统中活动的指令集合,有时间概念,具有生命周期和不同状态。程序是一个静态的指令集合,不具备这些状态。
并发性:多个进程在同一个CPU上并发执行,各进程互不影响。
并发和并行的区别:并发是同时间一个CPU上只执行一条指令集合,多个指令集合轮流抢占CPU执行权;并行是同一时间在不同的CPU上执行多个指令集合。
简要理解:并发是单核CPU执行多个任务,但同一时刻只会执行一个任务,任务会争抢CPU权。并行是多个CPU在同一时刻在执行多个任务,互不干扰。
多线程概念
多线程是对多进程的扩展,使一个进程同一时刻并发执行多个任务。线程也可以叫做轻量级进程,具有进程的部分特征。线程是进程的执行单元,线程在程序中是独立、并发的执行流。可以理解为:操作系统同时执行多个任务,任务就是进程;进程同时执行多个任务,任务是线程。线程为进程服务,进程为操作系统服务。
线程的特征:
只要进程运行,就必然有一个主线程。当一个进程中有多个线程并发执行,这就是多线程。
线程共享进程的全部资源。
线程是独立运行的,线程不知道进程中其他线程的存在。
线程是抢占式的,意味着当前运行的线程下一时刻就会被挂起,另一个线程得到运行。
线程的生命周期:
Java多线程的创建和启动
Java使用Thread类代表线程,所有线程对象都必须是Thread类或者是其子类实例,每个线程都有一个run()方法,该方法作为线程执行体。创建线程的三种方式:
继承Thread类。实例变量不会共享。
实现Runnable接口。实例变量共享。
实现Callable接口,call()允许有返回值。实例变量共享,增加许多方法。
创建线程的三种方式对比:
继承Thread类,就不能继承别的父类。但可以使用this访问当前线程,能直接使用线程对象来启动线程执行体。但需注意线程类的对象的实例变量不共享,因为每一个对象都是独立的。
实现Runnable和Callable差不多,Callable是call()作为线程执行体,允许有返回值。
多个线程可以共享一个Runnable对象,很适合多个相同线程处理同一份资源的情况。
Callable不能直接被线程对象使用,需要一个FutureTask对象对其包装。
继承Thread类实例:
public class ThreadDemo { //主线程 public static void main(String[] args) { for(int i = 0; i <= 50; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } //启动线程 new A().start(); new A().start(); } } class A extends Thread{ int i = 0; @Override public void run() { for(; i <= 50; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
实例解析:查看结果会发现,一共有三个线程在执行,而且线程对象中的实例不共享。这是因为对象具有唯一标识,实例变量和方法都属于对象本身,不会共享出来。这是A继承了Thread类,A的对象是能直接代表线程对象,再加上一个主线程,所以一共三个线程在执行。
实现Runnable接口实例:
public class ThreadDemo { //主线程 public static void main(String[] args) { A a = new A(); //把Runnable对象作为target Thread t1 = new Thread(a); Thread t2 = new Thread(a); //启动线程 t1.start(); t2.start(); } } class A implements Runnable{ int i = 0; @Override public void run() { for(; i <= 50; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
实例解析:查看结果会发现两个线程共用一个i变量,这是因为两个线程使用同一个Runnable对象。如果我们往两个线程对象中传入同一个Thread类对象,也能实现该效果,所以这个本质上是执行同一个对象的run()方法。但建议使用Runnable,继承Thrad类有许多限制。
实现Callable接口实例:
public class ThreadDemo { //主线程 public static void main(String[] args) throws InterruptedException, ExecutionException { for(int i=0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + i); } //包装Callable对象 FutureTask task = new FutureTask(new Abc()); //FutureTask对象作为target Thread t1 = new Thread(task,"Call线程"); //取消Callable任务 task.cancel(true); //启动线程 t1.start(); System.out.println(task.isDone());//Callable任务是否完成 System.out.println(task.get());//获取call()返回值,Excepetion } } class Abc implements Callable{ @Override //带返回值 public Object call() throws Exception { int sum = 0; for(int i=0; i <= 50; i++) { sum += i; } return sum; } }
实例解析:FutureTask的get()可以获取call()返回值,但前提是call()执行完成。有两种方式获取不了值:第一种是取消FutureTask中的Callable任务,则call()不会被执行。第二种是在线程启动之前就使用get(),线程没完成自然返回不了值。可以使用isDone()来判断Callable任务是否完成,然后再选择获取返回值。
控制线程
线程之间是抢占式的,CPU在下一时刻就可能执行别的线程,充满不确定性。Java提供一些方法来控制线程的执行,保证线程按规定来执行。sleep():使当前线程睡眠,让出CPU,进入阻塞状态,时间到了进入就绪状态争抢CPU。
yield():直接让出CPU,进入就绪状态,然后又开始抢CPU。所以有可能连续执行,因为又抢到执行权了。
join():被加入的线程会马上执行,其他线程进入等待状态。
setDaemon():将线程设成后台线程,当前台线程执行结束,后台在一段时间后就随之死亡。JVM的gc回收就是一个典型后台线程。
setPriority():设置线程优先级,通常主线程(main)的优先级最高。建议使用常量作为设置值。
sleep()方法实例:
public class ThreadDemo { //主线程 public static void main(String[] args) throws InterruptedException { Abc abc = new Abc(); Thread t = new Thread(abc); 4000 for(int i=0; i < 100; i++) { System.out.println(Thread.currentThread().getName() +"->" + i); if(i == 30) { Thread.sleep(2000);//睡2s } } t.start(); } } class Abc implements Runnable{ @Override public void run() { int i = 0; while(++i <= 30) { System.out.println(Thread.currentThread().getName() + "->" + i); } } }
实例解析:使用sleep(2000)让当前线程(主线程)睡眠2s,睡眠期间为什么没执行别的线程呢?这是因为要t.start()才会启动线程,而该方法是由主线程来调用,所以必须等循环语句结束之后才会执行到该语句,自然不会出现两个线程交替执行。如果我们先执行t.start(),就能看到效果了
//主线程 public static void main(String[] args) throws InterruptedException { Abc abc = new Abc(); Thread t = new Thread(abc); //先启动线程 t.start(); for(int i=0; i < 50; i++) { System.out.println(Thread.currentThread().getName() +"->" + i); if(i == 30) Thread.sleep(2000);//睡眠2s } }
yield()方法实例
public class ThreadDemo { //主线程 public static void main(String[] args) throws InterruptedException { Abc abc = new Abc(); Thread t = new Thread(abc); //先启动线程 t.start(); for(int i=0; i < 50; i++) { System.out.println(Thread.currentThread().getName() +"->" + i); if(i == 30) Thread.yield(); //放弃CPU,进入就绪 } } } class Abc implements Runnable{ @Override public void run() { int i = 0; while(++i <= 30) { Thread.currentThread().setName("Abc线程"); System.out.println(Thread.currentThread().getName() + "->" + i); } } }
图片显示yield()的作用。
join()和setDaemon()与setPriority()方法实例
public class ThreadDemo { public static void main(String[] args) throws InterruptedException { Thread ta = new Thread(new A(),"A线程"); Thread tb = new Thread(new B(),"B线程"); for(int i=0; i <= 80; i++) { System.out.println(Thread.currentThread().getName() + "->" + i); //循环到10,启动B线程并使用join() if(i == 10) { tb.start(); tb.join(); //主线程必须等B线程执行结束才开始执行 } } //A线程的优先级设置为10 ta.setPriority(Thread.MAX_PRIORITY); //A线程设为守护线程 ta.setDaemon(true); ta.start(); } } class A implements Runnable{ @Override public void run() { int i = 0; while(++i <= 150) System.out.println(Thread.currentThread().getName() + "->" + i); } } class B implements Runnable{ @Override public void run() { int i = 0; while(++i <= 100) System.out.println(Thread.currentThread().getName() + "->" + i); } }
执行流程:从main(主线程)开始执行,当循环值到10时,启动B线程并join(),此时主线程就会进入阻塞,直到B线程执行结束,处于就绪状态的线程开始争抢CPU。但该程序现在只有一个主线程还在运行,执行完整个循环,继续往下执行,将A线程优先级设为最高(这个其实很鸡肋,了解怎么用就好),然后把A线程设为主线程的后台线程,然后启动该线程。
实例解析:B线程join()结束之后,仅仅只有主线程被执行,是因为此时只有一个主线程,A线程还没启动。当主线程执行到ta.start(),会发现A线程并不会全部执行完,有时候甚至没执行就死了。这是因为主线程已经消亡了,A线程一段时间后也随之消亡。
相关文章推荐
- 1多线程的概述2多线程(创建多个线程实例,并启动多个线程)的实现方式,main主方法是单线程的4多线程的实现方式5多线程模拟火车站售票出现问题7线程的声明周期
- java并发多线程,线程的创建启动
- day10 反射创建数组 线程的基本概念 线程的编写和启动方式 线程的运行状态以及状态转换方法 线程的调度和优先级设置
- 【java多线程与并发库】---传统java多线程<2> 线程创建方式
- Java基础:多线程(1)--线程的概述、创建线程的方式、线程的多种状态、线程常用的方法
- 【java多线程与并发库】---传统java多线程<2> 线程创建方式
- Java —— 多线程笔记 一、线程创建、启动、生命周期、线程控制
- 创建线程的几种方式,以及为什么启动线程不用run,而用start方法!!!
- java多线程与并发之创建线程的几种方式
- Java多线程与并发学习之(二):创建线程的方式
- 线程 创建和启动线程的两种方式 实现Runnable接口 继承Thread类 重写唯一方法run()
- 创建线程的几种方式,以及为什么启动线程不用run,而用start方法。
- 05-多线程(多线程创建的方式一-继承Thread类)1 2 06-多线程(Thread类中的方法&线程名称) 08-多线程(线程的状态)图解
- 多线程基础知识第一篇:创建线程的方式及线程常用的方法
- 多线程线程池控制一个方法的并发量 限制只有5个线程执行任务
- Java并发1——线程创建、启动、生命周期与线程控制
- 多线程创建 方法一: NSThread 创建线程的三种方式
- 线程学习之--2多线程的创建的第一种方式
- java多线程总结一:线程的两种创建方式及优劣比较
- 多线程并发库高级应用 之 多个线程之间共享数据的方式探讨