您的位置:首页 > 其它

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线程一段时间后也随之消亡。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐