多线程

一、是什么

先了解几个概念:

进程:进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。说白了就是程序进入内存运行时,就会产生一个相应的进程。

线程:线程是进程中的一个执行单元,负责当前进程中某一段程序的执行,一个进程中至少有一个线程(main方法作为程序执行的入口,就是一个线程)。

多线程多个线程呗。一般而言,一个进程中会有多个线程,分别执行不同程序,共同完成进程的功能,他们会共用进程中的一些资源。

线程的生命周期:线程的一生要经过 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Bolcked)、死亡(Dead)5种状态。在多个线程启动之后,CPU需要在各个线程之间相互切换,线程的状态便会多次在运行、阻塞之间切换。

新建:当用new创建一个线程后,该线程处于新建状态。此时,它和一般的Java对象没有区别,仅仅由java虚拟机为其分配内存,并初始化其成员变量值。

就绪:线程的就绪状态意思就是该线程可以却未运行,在等待运行中。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示线程可以运行,但是进入运行状态取决于JVM里线程调度器的调度。线程在调用start()方法启动后,会进入就绪状态,等待运行。或者处于阻塞状态的线程消除掉导致阻塞的原因之后,会进入就绪状态,重新等待执行。

运行:处于就绪状态的线程获得CPU的执行权之后,开始执行线程执行体---run()方法,此时该线程处于运行状态中。

阻塞:处于运行状态中的线程由于一些原因,暂时放弃对CPU的使用权,暂停运行,此时进入阻塞状态,直到其等到导致阻塞的原因消除之后进入到就绪状态,才有机会再次被CPU调用来重新进入到运行状态。或者被其他的线程中断,退出阻塞状态,同时抛出InterruptedException异常。

线程阻塞分类及一般原因
等待阻塞运行状态的线程执行wait()方法,直接进入阻塞状态,等待其他线程执行notify()或者notifyAll()方法
同步阻塞线程获取synchronized同步锁失败(因为锁被其它线程占用)会进入到同步阻塞状态,直到获取了同步锁,才能恢复执行
其他阻塞通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态

死亡:线程正常或非正常死亡,退出执行体,该执行单元关闭。

常见线程死亡方式
正常死亡run()方法或call()方法执行完成,线程正常结束
非正常死亡抛出未捕获的Exception或Error 
非正常死亡直接调用该线程的stop()方法结束该线程(易死锁,不推荐

 

 

二、怎么用

I. 创建线程 --- 4种方式

创建线程的四种方式
继承Thread类,重写run()方法
实现Runnable接口,重写run()方法
使用Callable和Future创建线程,重写run()方法
使用线程池例如用Executor框架

①继承Thread类,重写run()方法

package CreatedByThread;
/**
 * ① 定义线程类继承Thread类
 * ② 重写run()方法
 * ③ 创建子类的实例,调用start()方法开启线程
 * 
 * @author RoRoBear
 *
 */
public class TestCreateThreadOne extends Thread{

	public void run(){
		System.out.println(getName()+" 开始执行run()方法啦!");
	}
	
	
	public static void main(String[] args) {
		
		Thread td1 = new TestCreateThreadOne();
		Thread td2 = new TestCreateThreadOne();
		td1.start();
		td2.start();
			
	}

}

②实现Runnable接口,重写run()方法

package CreatedByThread;

/**
 * ① 定义Runnanle接口的实现类,重写run()方法
 * ② 创建Runnable实现类的实例,并作为Target传入Thread()作为参数,
 *   以此创建Thread对象,该对象才是真正的线程对象
 * ③ 调用start()方法开启线程
 * 
 * @author RoRoBear
 *
 */
public class CreatedByRunnable implements Runnable {

	public void run() {
		System.out.println( Thread.currentThread().getName()+" 开始执行run()方法啦!");
	}

	
	public static void main(String[] args) {
		CreatedByRunnable td1 = new CreatedByRunnable();
		CreatedByRunnable td2 = new CreatedByRunnable();
		
		new Thread(td1).start();
		new Thread(td2).start();

	}

}

③使用Callable和Future创建线程,重写run()方法

package CreatedByThread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * ① 创建Callable接口的实现类,重写call()方法
 * ② 创建FutureTask对象,包装Callable对象
 * ③ 以FutureTask对象为Target参数传入Thread内,创建新线程
 * ④ 调用start()方法启动线程
 * 
 * @author RoRoBear
 *
 */
public class CreatedByCallable implements Callable<Object> {

	public Object call() throws Exception {
		System.out.println(Thread.currentThread().getName()+" 开始执行run()方法啦!");
		return null;
	}

	public static void main(String[] args) {
		
		CreatedByCallable cc = new CreatedByCallable();
		
		FutureTask<Object> ft = new FutureTask<Object>(cc);
		FutureTask<Object> ft2 = new FutureTask<Object>(cc);
		
		new Thread(ft).start();
		new Thread(ft2).start();

	}
	
}

④使用线程池

前面提到的三种方法,创建线程时可以很好地控制线程,如线程启创建、启动、设置优先级等等。而现在这种方法,则是通过线程池创建线程。例如创建一个线程池,池中可以有一个或多个线程,这些线程都是线程池去维护而不是程序员去显示的创建、启动、设置优先级等。创建线程池的前提最好是你的任务量大,因为创建线程池的开销比创建一个线程大得多

如果通过线程池创建线程,要知道ExecutorService 是一个比较重要的接口,实现这个接口的子类有两个 ThreadPoolExecutor (普通线程池)、ScheduleThreadPoolExecutor (定时任务的线程池)。你可以通过这两个类来创建一个线程池,但要传入各种参数,不太方便。为了方便用户,JDK中提供了工具类Executors,提供了几个创建常用的线程池的工厂方法。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * ① 创建一个只有一个线程的线程池
 * ② 创建任务,并提交任务到线程池中
 * @author RoRoBear
 *
 */

public class ThreadPoolTest {
	
	public static void main(String[] args) {

        //①创建一个只有一个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //②创建任务,并提交任务到线程池中
        executorService.execute(new CreatedByRunnable());

	}
	
}

 

II. 启动线程

启动线程的方法是start() !!!

刚才的代码中都看到了,启动线程是调用线程的start()方法,不是其他的run()、call()等方法

 

三、比较几个方法的区别 

run()和start()、call()的区别

start():作用是启动线程,线程调用start()方法后会转入就绪状态,等待JVM的线程调度器指令,调用start()后,该线程可能立即运行,也可能稍后运行,也可能一直不运行,但是线程处于启动后的就绪状态,调用start()方法后,线程执行时会执行run()方法的内容。

run():run()方法是线程的执行体,并不能启动线程!但是调用线程的run()方法也可以运行出相同代码是为什么呢?因为run()此时就是一个普通的方法被调用了。如果程序直接调用run(),注意这里并不会启动线程,而且此时的run()方法并没有运行在它应该在的自身线程之中,而是运行在当前run()的调用方所在的线程之中。这和我们创建线程的初衷是不一致的。

call():call()方法和run()方法一样,是线程的执行体,区别在于call()方法是通过实现Runnable接口和Callable接口实现线程时使用的执行体,run()是通过继承Thread类创建线程时使用的执行体,并且call()方法是有返回值的,而run()没有返回值

wait()和sleep()的区别

1.参数

sleep()方法必须传参,参数就是休眠时间,时间到了就会自动转入就绪状态等待再次运行。

wait()方法可传参也可不传参,若传参则会在参数结束的时间点开始等待,不穿参则会直接等待(同参数=0)。

2.作用范围

wait()方法只能在同步方法或者同步代码块中使用,而sleep()方法可以在任何地方使用。但是sleep是静态方法,它只对当前对象有效。通过对象名.sleep()想让该对象线程进入休眠是无效的,它只会让当前线程进入休眠。

3.捕获异常

sleep方法必须要捕获异常,而wait方法不需要捕获异常。

一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

4.关于释放锁

调用sleep方法不会释放锁对象。当调用sleep方法后,当前线程进入阻塞状态。目的是让出CPU给其他线程运行的机会。但是由于sleep方法不会释放锁对象,所以在一个同步代码块中调用这个方法后,线程虽然休眠了,但其他线程无法访问它的锁对象。这是因为sleep方法拥有CPU的执行权,它可以自动醒来无需唤醒。而当sleep()结束指定休眠时间后,这个线程不一定立即执行,因为此时其他线程可能正在运行。

wait()方法会释放锁对象。当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时释放了锁对象,等待期间可以调用里面的同步方法,其他线程可以访问,等待时不拥有CPU的执行权,否则其他线程无法获取执行权。当一个线程执行了wait方法后,必须调用notify或者notifyAll方法才能唤醒,而且是随机唤醒,若是被其他线程抢到了CPU执行权,该线程会继续进入等待状态。由于锁对象可以时任意对象,所以wait方法必须定义在Object类中,因为Obeject类是所有类的基类。

 

以上!

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值