多线程
一、线程简述:
什么是线程,以及线程和进程的区别: 线程:一个程序可以同时执行多个任务,每个任务就可以称为一个线程,可以同时运行一个以上的程序称为多线程程序; 线程和进程的区别:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位,一个进程可以有多个线程。 比如:打开qq相当于打开了一个进程,打开微信又打开了一个进程,而在qq中传输文字是一个线程,发送图片是一个线程,一个进程有多个线程。
二、线程状态
新建状态(new) 就绪状态也叫可运行状态(runnable) 运行状态( running) 阻塞状态(Blocked) 终止状态(Dead)
下图解释了线程状态的转换
![](https://img2018.cnblogs.com/blog/1887466/201912/1887466-20191222221935025-1700447766.png)新建状态:当使用new操作符创建一个新线程时,比如new Thread(r),这个时候线程还没有开始运行,只是占有了内存资源。处于new状态
就绪状态:调用了start方法之后,线程处于Runnable状态,这个时候等待cpu的使用权,因为线程是抢占式调度,抢占式调度系统给每一个可运行线程一个时间片来执行任务,每个线程都有抢得运行的机会,当时间片用完,操作系统剥夺该线程的运行权给另一个线程。在线程没有结束之前,如果再次调用start方法将会出现异常
阻塞状态:当现成处于被阻塞或者等待状态时,是因为由于某些原因放弃执行权,直到线程调度器重新激活它。 阻塞状态分为三种
- 同步阻塞:当一个线程试图获取对象的锁的时候,而该锁被其它线程占用,这个时候线程进入同步阻塞,直到该线程释放该锁,并且操作系统的线程调度器允许这个线程使用的时候,才解除阻塞状态
- 等待阻塞:运行中的线程执行了wait()等方法的时候,进入了等待阻塞状态
- 计时等待状态:运行的线程执行了sleep()或者join方法的时候,或者发出了I/O请求。的时候,jvm会把该线程进入阻塞状态,直到请求处理完毕或者等待超时的时候,解除阻塞状态进入可运行状态。
终止状态:线程因为两种原因而被终止,第一种是线程正常结束,然后被终止,第二种是一个没有捕获的异常终止了run方法而意外被终止 void join()等待终止指定的线程,里面有参数就是经过指定的时间终止 Thread(Runnable target):构造一个新线程,用于调用给定目标的run()方法 void run () 将要执行的任务放进run方法中,必须要重写run方法,一般和Thread一起用
三、线程启动的方式
线程的创建和启动 线程的创建有三种方法,run方法中的是线程执行体,里面写线程需要完成的任务。
- 继承Thread类,重写该类的run()方法。
- 实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。(推荐使用)
- 使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
第一种,继承Thread类,重写该类的run()方法 示例代码:
public class MyThread extends Thread { public void run(){ System.out.println("这是重写的run方法"); }
}
public class ThreadTest {
public static void main(String[] args) {
for (int i=0;i<20;i++)
{
System.out.println(Thread.currentThread().getName());
if ( i==10)
{
Thread t = new MyThread();
Thread t2 = new MyThread();
t.start();
t2.start();
}}
}
} 第二种,实现Runnable接口,并重写run方法
public class MyThread implements Runnable {
private int i = 0;
@Override
public void run() {
System.out.println("这是重写的线程");
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable r = new MyThread();
Thread t = new Thread(r);//r是目标对象
t.start();
}
} 第三种,实现Callable接口,使用call()方法
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
可以发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target 执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢? 原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。