一、线程与进程
- 线程是程序最小的执行单元。
- 进程是操作系统进行资源分配和调度的一个基本单位。
- 一个程序至少有一个进程,一个进程又至少包含一个线程,在程序运行时,即使自己没有创建线程,后台也会存在多个线程。如GC线程、主线程等。main线程称为主线程,为系统的入口点,用于执行整个程序。
- 多个线程的执行是由操作系统进行调度的,执行顺序是不能人为干预的。操作系统为线程分配CPU时间片,线程之间进行争夺。
- 每个线程都有自己的工作内存,但是加载和存储主内存(共享内存)控制不当会造成数据不一致。因此对同一份共享资源操作时,需要加入并发控制。
- 引入线程的目的是为了充分利用CPU资源,使其可以并行处理多个任务,减少时间消耗,提升效率。但线程会带来额外的开销,如线程的创建和销毁时间,cpu调度时间,并发控制开销。因此,并不是线程创建的越多越好,为此引入了线程池,更好的进行资源控制。
- 多线程并不一定快,受硬件、网络等其它因素的影响,比如网络带宽为2m,下载速度为1m/s,启动10个线程也不会达到10m/s。
案例:多线程下载图片
public class TDownLoader extends Thread {
private String url;
private String name;
public TDownLoader(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownLoader downLoader = new WebDownLoader();
downLoader.downLoad(url, name);
System.out.println(name);
}
public static void main(String[] args) {
TDownLoader t1=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566549&di=9dfa479ec43c80d34ea4a84fc2c3f866&imgtype=0&src=http%3A%2F%2Fgss0.baidu.com%2F94o3dSag_xI4khGko9WTAnF6hhy%2Fzhidao%2Fpic%2Fitem%2F14ce36d3d539b6002ac5706de850352ac75cb7e4.jpg","C:\Users\Administrator\Desktop\ttt\t1.jpg");
TDownLoader t2=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566548&di=f901ee991c205196f80ca1f03508d31b&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fpic%2F6%2Fa6%2F39b2375640.jpg","C:\Users\Administrator\Desktop\ttt\t2.jpg");
TDownLoader t3=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566547&di=a3caf55c682e66b095a4f0990ea3c0f7&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fpic%2F8%2F09%2Fa615556832.jpg","C:\Users\Administrator\Desktop\ttt\t3.jpg");
t1.start();
t2.start();
t3.start();
}
}
二、线程的分类
线程分为用户线程和守护线程:
用户线程(User Thread):一般是程序中创建的线程
守护线程(Daemon Thread):为用户线程服务的线程,当所有用户线程结束时才会终止,如GC线程。通过Thread的setDaemon(true)方法将一个用户线程转为守护线程。
三、线程的创建
线程的创建方式主要有四种:
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口,可以有返回值,需要结合FutureTask一块使用
4. 通过线程池来进行任务执行
方式一:继承Thread类
声明一个Thread类的子类,这个子类重写run方法。使用时必须创建此子类的实例,调用start()方法启动线程
package com.whw.thread.generic;
/**
* @description 线程的创建:继承Thread类
*/
public class MyThread01 extends Thread {
@Override
public void run() {
System.out.println("我是MyThread01");
}
}
方式二:实现Runnable接口
声明一个类实现Runnable接口,这个类实现run方法。使用时必须new一个Thread类,然后创建此子类的实例作为Thread类的参数。调用new出来的Thread类的start()方法
package com.whw.thread.generic;
/**
* @description 线程的创建:实现Runnable接口
*/
public class MyThread02 implements Runnable{
@Override
public void run() {
System.out.println("我是MyThread02");
}
}
使用:start()方法启动线程,并不是立即执行线程run方法,具体什么时候,需cpu调度,人为无法干预
public class TestThread {
public static void main(String[] args) {
//通过继承Thread类创建线程,直接new一个子类实例,调用start()方法启动线程
MyThread01 myThread01=new MyThread01();
myThread01.start();
//通过实现Runnable接口创建线程,需先new一个子类实例,先后再new一个Thread类,
//将子类实例作为参数传入后调用start()方法启动线程
MyThread02 myThread02=new MyThread02();
new Thread(myThread02).start();
}
}
注意:直接调用run()方法,将以普通方法执行,依然是单线程顺序调用。
方式三、实现Callable接口
class Thread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("线程开始执行...");
int i = 10 / 2;
System.out.println("执行结果i=:" + i);
System.out.println("线程执行结束...");
return i;
}
}
调用Callbale接口的线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Thread3());
new Thread(futureTask).start();
Integer i = futureTask.get();// 获取返回值
System.out.println(i);
}
方式四:使用线程池
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new Thread1());
}
四种方式的比较:
1只能单继承,2、3可以多继承,扩展性更好
1、2实现的方法是run(),3实现的方法是call()
1、2不能得到返回值,不能抛异常,3可以获取返回值,可以抛异常
1、2、3都不能控制资源,4可以直接控制资源,性能稳定
四、线程创建的简化方式
1、常规使用
package com.whw.thread.generic;
public class NormalThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我是MyThread01---" + i);
}
}
public static void main(String[] args) {
new Thread(new NormalThread()).start();
}
}
2、使用静态内部类
package com.whw.thread.generic;
public class TestThread {
// 定义静态内部类
static class Test implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我是MyThread01---" + i);
}
}
}
public static void main(String[] args) {
// 使用静态内部类
new Thread(new Test()).start();
}
}
3、使用局部内部类
package com.whw.thread.generic;
public class TestThread {
public static void main(String[] args) {
// 定义局部内部类
class Test implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我是MyThread01---" + i);
}
}
}
// 使用局部内部类
new Thread(new Test()).start();
}
}
4、使用匿名内部类
package com.whw.thread.generic;
public class TestThread {
public static void main(String[] args) {
// 使用匿名内部类:必须借助接口或者父类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我是MyThread01---" + i);
}
}
}).start();
}
}
5、jdk8,使用Lambda表达式
package com.whw.thread.generic;
public class TestThread {
public static void main(String[] args) {
// 使用Lambda表达式
new Thread(() -> {
for (int i = 0; i < 20; i++) {
System.out.println("我是MyThread01---" + i);
}
}).start();
new Thread(() -> {
for (int j = 0; j < 20; j++) {
System.out.println("我是MyThread02---" + j);
}
}).start();
}
}
lambda表达式基础使用:
package com.whw.thread.generic;
class Love {
public static void main(String[] args) {
//此段代码相当于创建了一个ILove的实现类,{}内的为实现类重写方法的方法体
/*ILove love = (String name) -> {
System.out.println("I Love -->" + name);
};*/
//一个参数时可以不用写参数类型
/*ILove love = (name) -> {
System.out.println("I Love -->" + name);
};*/
//一个参数时可以不用写括号
/*ILove love = name -> {
System.out.println("I Love -->" + name);
};*/
//一个参数时,方法体只有一行内容时,可以不用写{},但要写在一行
//ILove love = name -> System.out.println("I Love -->" + name);
//love.lambda("王伟");
//接口两个参数,并且带返回值
IInterest interest = (a, b) -> {
return a + b;
};
Integer result = interest.lambda(1, 2);
System.out.println(result);
}
}
interface ILove {
void lambda(String name);
}
interface IInterest {
Integer lambda(Integer a, Integer b);
}
五、线程的常用方法
1、currentThread()
获取当前正在被调用的线程
2、getId()
获取线程的唯一id号
3、getName()
获取线程的名字
4、getState()
获取线程的状态
5、isDaemon()
判断是否是守护线程
6、setDaemon(boolean on)
设为守护线程,只能在线程启动之前把它设为后台线程
7、isAlive()
判断当前线程是否处于活动状态
8、activeCount()
判断当前线程组中存活的线程数
9、getThreadGroup()
获取当前线程所在的线程组
10、sleep(long)
让线程休眠指定的毫秒数,休眠过程中资源不释放
11、停止线程
stop():已被废弃
interrupt():
并不会终止正在运行的线程,需要加入一个判断才可以停止线程
判断线程是否被中断this.isInterrupted()
run():运行结束
异常法停止线程:
public class StopThread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 停止线程
myThread.interrupt();
}
}
class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 1000000; i++) {
System.out.println(i);
if (this.isInterrupted()) {
System.out.println("我被终止了");
// 抛出异常,中断for循环下面代码的执行
throw new InterruptedException();
}
}
System.out.println("我被输出,说明线程并未停止");
} catch (InterruptedException e) {
System.out.println("异常法结束线程的执行");
e.printStackTrace();
}
}
}
标记法停止线程:
package com.whw.thread.state;
/**
* @description 终止线程
* 1、线程正常执行完毕-->次数
* 2、外部干涉-->加入标识
* 不要使用stop,destroy方法
*/
public class TerminateThread implements Runnable {
//1、加入标识,标记线程体是否可以运行
private boolean flag = true;
private String name;
public TerminateThread(String name) {
this.name = name;
}
@Override
public void run() {
int i = 0;
//2、关联标识,true-->运行 false-->停止
while (flag) {
System.out.println(name + "-->" + i++);
}
}
//3、对外提供方法改变标识
public void terminate() {
this.flag = false;
}
public static void main(String[] args) throws InterruptedException {
TerminateThread tt = new TerminateThread("A");
new Thread(tt).start();
for (int i = 0; i <= 99; i++) {
if (i == 88) {
tt.terminate();//线程终止,解决死锁
System.out.println("tt game over");
}
System.out.println("main-->" + i);
}
}
}
12、暂停与恢复线程
暂停suspend():已被弃用,使用不当容易造成独占锁和数据不同步
恢复resume():已被弃用,使用不当容易造成独占锁和数据不同步
13、yield()
线程礼让,不一定每次都成功,只是让当前线程放弃本次CPU资源的争夺,有可能刚放弃马上又得到了cpu资源
14、setPriority()
设定线程的优先级,默认是5,1~10个等级,有助于线程规划器尽可能多的将资源分给优先级高的线程
具有继承性
高优先级的不一定每次都先执行完
15、join(long millis)
join合并线程,插队线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。他是一个成员方法