线程的概念
首先了解进程与线程的区别与联系
- 在操作系统中,通常将进程看作是系统资源的分配单位和独立运行的基本单位。一个任务就是一个进程。比如,正在运行的火狐浏览器,同时还可以打开一个网易云音乐,系统就会产生两个进程。通俗的说,一个进程既包含了它要执行的指令,也包括了执行指令时所需要的各种系统资源,如CPU,内存,输入输出端口。不同进程所占用的资源相对独立。进程具有动态性,并发行,独立性,异步性。
- 线程是比进程更小的执行单位。某一个进程在执行过程中,可以产生多个线程。每个线程都有着自己相对独立的资源,生存周期。线程之间可以共享代码和数据、实时通信、进行必要的同步操作。线程在提高系统吞吐率,有效利用系统资源,改善用户之间的通讯效率以及发挥多处理机的硬件性能等方面都有着显著的作用。
- 在一个进程中,可以由一个或多个线程的存在。如果我们步创建线程对象,那么系统至少会创建一个主线程。
多线程的特点
- 在基于线程的多任务处理环境中,线程是最小的执行单位。 这意味着一个程序可以同时执行两个或者多个任务的功能,进程是重量级的任务,需要分配他们自己独立的地址空间,进程间通信是昂贵和受限的,进程间的转换也是很需要花费的。但是,线程是轻量级的,它们共享相同的地址空间并且共同分享同一个进程,线程之间的通信也是低成本的。当Java程序使用多进程任务处理环境时,多进程不受Java的控制,而多线程是受Java控制的。
- 设计好的多线程,能够写出CPU最大利用率的高效程序,因为CPU的空闲时间保持最低。
- 进程是由操作系统控制的,而线程是由进程来控制的。进程之间都是相互独立的,各自享有各自的内存空间。而一个进程中的多个线程是共享内存的,这意味着它们可以访问相同的变量和对象,着一方面方便了线程之间的通讯,另一方面又引入了新问题:多个线程同时访问一个变量可能回导致意想不到的错误。
线程的状态
线程具有五大状态,分别是:新建,就绪,执行,阻塞,死亡。
- 新建:当建立一个Thread类和它的一个子类对象后,新产生的线程对象就处于新建状态,并获得除CPU以外所需的资源
- 就绪:当处于新建状态的线程被启动后,将进入线程队列等待CPU资源。这时它已经具备了运行的条件,一旦获得CPU资源,就可以脱离创建它的主线程独立运行了,另外原来处于阻塞状态的进程结束阻塞状态后也会进入就绪状态,而不是执行状态
- 执行:当一个就绪状态的线程获得CPU时,就进入执行状态,每一个Thread类及其子类对象都有一个run()方法,一旦线程开始运行,就会自动运行该方法
- 阻塞:一个正在运行的线程因为某种特殊的情况,比如资源无法满足就会进入阻塞状态
- 死亡: 不具备继续运行能力的想成处于死亡状态,一般时两种情况引起的:一种是run()方法已经运行完毕了,另一种是其他的线程(一般时主线程)强制终止它。
这里需要注意的是:处于就绪状态的线程是在就绪队列中等待CPU资源的,而一般情况下,就绪队列中会有很多个线程。为此,系统就会为每个线程分配一个优先级,优先级高的排在前面的位置,能先得到CPU的资源。对于优先级相同的线程,一般按照先来先服务的原则调度。
线程的三种创建方式
- 1.通过继承Thread类创建线程及步骤
1.继承Thread类
2.重写run()方法
3.创建继承了Thread类的对象,调用start()方法启动线程
public class TestThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("线程执行体"+i);
}
}
public static void main(String[] args) {
TestThread1 thread1 = new TestThread1();
thread1.start();
for (int i = 0; i < 300; i++) {
System.out.println("main线程执行体"+i);
}
}
}
运行结果为:
main线程执行体0
main线程执行体1
main线程执行体2
main线程执行体3
线程执行体0
main线程执行体4
线程执行体1
main线程执行体5
从上面的运行结果中可以看出两个线程是相互交替执行的。
2.通过继承Runnable接口创建线程及步骤
1.实现Runnable接口
2.创建Runnable接口实现类
3.启动线程。需要创建一个Thread对象,将本类传递给Thread对象,最后调用start()方法启动。
public class TestThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("线程执行体"+i);
}
}
public static void main(String[] args) {
TestThread2 thread2 = new TestThread2();
new Thread(thread2).start();
for (int i = 0; i < 300; i++) {
System.out.println("main线程执行体"+i);
}
}
}
3.通过继承Callable接口创建线程及步骤
1.创建一个Callable接口的实现类,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务
5.提交执行
6.获取结果
7.关闭服务
import java.util.concurrent.*;
public class TestThread6 implements Callable {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName());
return true;
}
public static void main(String[] args) {
TestThread6 th1 = new TestThread6();
TestThread6 th2 = new TestThread6();
//创建线程池,传入线程个数
ExecutorService executorService = Executors.newFixedThreadPool(2);
//提交执行
Future submit = executorService.submit(th1);
Future submit1 = executorService.submit(th2);
//获取结果
try {
Object o = submit1.get();
Object o1 = submit1.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdownNow();
}
}
}
三种创建线程方式的优缺点
1.继承Thread()类
- 优点
简单,访问当前线程只需要使用this关键字 - 缺点
线程已经继承Thread类,因此不可以再继承其他类
2.实现Runnable和Callable接口
- 优点
1.线程实现了Runnable和Callable接口,,因此还可以继承其他父类
2.多个线程可以共享一个对象,因此可以使多个线程来处理同一个资源对象,可以将代码和数据分开。 - 缺点
编程复杂,访问当前线程使用Thread.currentTHread()方法
例子
例一 抢票
public class ticket implements Runnable {
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums <= 0){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNums-- +"张票");
}
}
public static void main(String[] args) {
ticket th = new ticket();
new Thread(th,"张三").start();
new Thread(th,"李四").start();
new Thread(th,"王五").start();
}
}
例二 龟兔赛跑
public class Race implements Runnable {
//只有一个胜利者
private static String name;
@Override
public void run() {
//跑道
for (int step = 1; step <= 100; step++) {
if(Thread.currentThread().getName().equals("兔子")&&step==80){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
Boolean flag = GameOver(step);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+step+"步");
}
}
public Boolean GameOver(int step){
//如果已经存在胜利者
if(name != null){
return true;
}
//如果步数已经超过一百步,那么线程就结束
if(step >=100){
this.name = Thread.currentThread().getName();
System.out.println("比赛结束");
System.out.println("胜利者为"+name);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
例三 多线程下载图片
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class downLoad implements Runnable{
private String url;
private String name;
public downLoad(String url,String name){
this.name = name;
this.url = url;
}
@Override
public void run() {
fileLoad fileLoad = new fileLoad();
fileLoad.download(url,name);
System.out.println("下载完成");
}
public static void main(String[] args) {
downLoad load1 = new downLoad("https://blog.kuangstudy.com/usr/themes/handsome/usr/img/sj/1.jpg", "one.jpg");
downLoad load2 = new downLoad("https://blog.kuangstudy.com/usr/themes/handsome/usr/img/sj/2.jpg", "two.jpg");
downLoad load3 = new downLoad("https://blog.kuangstudy.com/usr/themes/handsome/usr/img/sj/3jpg","three.jpg");
new Thread(load1).start();
new Thread(load2).start();
new Thread(load3).start();
}
class fileLoad{
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载异常");
}
}
}
}