线程简介
线程就是独立的执行路径在程序运行时,即使没有自己创建线程,后台也会有多个线程,
如主线程,gc线程;main称之为主线程, 为系统的入口,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制线程会带来额外的开销,如cpu调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程的创建方式
继承Thread类 (重点) //通过查看源码得出Thread是实现Runnable接口
实现Runnable接口 (重点)
实现Callable (了解)
测试线程的第一种方式继承
//第一步:继承Thread类
public class TestThread01 extends Thread{
//第二步:重写run方法
@Override
public void run() {
//第三步:编写方法体
for (int i = 0; i < 500; i++) {
System.out.println("我在听歌"+i);
}
}
public static void main(String[] args) {
//第四步:创建线程对象,开启线程
new TestThread01().start();
for (int i = 0; i < 300; i++) {
System.out.println("我在看电影----------"+i);
}
}
}
多线程下载图片案例解析
package com.softeem;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* 用多线程同时下载多张图片,以下为思路
* 第一步:导入commons-io包
* 第二步:编写下载器类,编写下载
* 第三步:在测试线程类定义url和name属性并构建构造方法来传递值
* 第四步:实现Runnable接口创建多线程并重写run(),方法体中调用下载器
* 第五步:实例化Thread对象,参数为本类,调用start()启动线程
*/
public class TestThread implements Runnable {
//3.定义两个参数 一个用于接收url name用来接收传递名称,并编写有参构造传递值
private String url;
private String name;
public TestThread(String url, String name) {
this.url = url;
this.name = name;
}
public void run() {
//4.实现了了Runnable必须重写run(),调用Test类的copy方法,
//这里需要哪些线程执行就需要编写对应的方法体
new WebDownloader().copy(this.url, this.name);
System.out.println("图片下载成功!图片名称为:" + this.name);
}
public static void main(String[] args) {
//启动多根线程
new Thread(new TestThread("https://tse1-mm.cn.bing.net/th/id/OIP-C.30Ri-r8Y7dBxlWeaX4o35QHaE2?w=237&h=180&c=7&r=0&o=5&dpr=1.3&pid=1.7", "t1.jpg")).start();
new Thread(new TestThread("https://tse3-mm.cn.bing.net/th/id/OIP-C.ZkoPhpKfJwsvGmpm8RsragHaFp?w=204&h=180&c=7&r=0&o=5&dpr=1.3&pid=1.7", "t2.jpg")).start();
new Thread(new TestThread("https://tse1-mm.cn.bing.net/th/id/OIP-C.hmZ03NXiKgsHSshVcrU26wHaH8?w=180&h=193&c=7&r=0&o=5&dpr=1.3&pid=1.7", "t3.jpg")).start();
}
}
//下载器
class WebDownloader {
//1.导入包后:2。定义copy下载方法,用来接收线程传递信息
public void copy(String url, String name) {
try {
//调用文件工具包中的copy方法,传入的值为两个对象
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("图片下载异常");
}
}
}
继承Thread和实现Runnable
实现Runnable案例如下:推荐(Java可以实现多个接口,避免单继承的局限性,方便同一方法被多条线程调用)
/**
* 第一步:实现Runnable接口并重写run()方法
* 第二步:写一个或多个要让线程执行的方法
* 第三步:实例化Thread线程,参数为为本类并start开始线程
*/
public class Test01 implements Runnable {
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我在看书------");
}
}
//main线程需要注意:其他线程要在方法体上方,不然默认先把main线程执行完再执行其他线程
public static void main(String[] args) {
new Thread(new Test01()).start();
for (int i = 0; i < 1200; i++) {
System.out.println("我在吃饭"+i);
}
}
}
继承Thread类案例如下:
/**
* 第一步:实现Runnable接口并重写run()方法
* 第二步:写一个或多个要让线程执行的方法
* 第三步:实例化本类然后.start()执行线程即可
*/
public class Test01 extends Thread {
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我在看书------");
}
}
//main线程需要注意:其他线程要在方法体上方,不然默认先把main线程执行完再执行其他线程
public static void main(String[] args) {
new Test01().start();
for (int i = 0; i < 1200; i++) {
System.out.println("我在吃饭"+i);
}
}
}
====================================手动分割线=================================================
package com.softeem;
//练习:模拟三个人抢100张票,三条线程执行同一方法(未加锁所以会有重复)
public class Test02 implements Runnable {
//规定票的总数为100
private int piao = 100;
public void run() {
//重写run方法并循环直到票为0结束
while (true) {
if (piao <= 0) {
break;
}
//Thread.currentThread().getName()方法得到当前线程名称
System.out.println(Thread.currentThread().getName() + "拿到了第" + piao-- + "张票");
}
}
public static void main(String[] args) {
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Test02(), "小明").start();
new Thread(new Test02(), "小红").start();
new Thread(new Test02(), "黄牛").start();
}
}
经典案例,龟兔赛跑
package com.softeem;
//练习:模拟龟兔赛跑,定义赛道距离,先到的打印胜利,并且结束比赛
public class Test02 implements Runnable {
//定义一个胜利者,因为结果只有一个所以可以使用static静态
private static String name;
@Override
public void run() {
//假设赛道为100米
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "跑了第" + i + "米");
//如果线程名为兔子并且每次都跑了十步,就让它的线程睡眠0.1毫秒
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果胜负已分,则结束
if (number1(i)) {
break;
}
}
}
//判断到终点了没有,确定冠军名称
public boolean number1(int a) {
//如果不等于空说明冠军已分,结束比赛
if (name != null) {
return true;
}
if (a == 100) {
this.name = Thread.currentThread().getName();
System.out.println("比赛结束,胜利者为:" + name);
return true;
}
return false;
}
public static void main(String[] args) {
new Thread(new Test02(), "乌龟").start();
new Thread(new Test02(), "兔子").start();
}
}
Lambda表达式
为什么使用Lambda表达式?可以让代码看起来更简洁。
去掉了一堆没有意义的代码,只留下了核心的逻辑。
Lambda表达式的使用场景:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。案例如下:
package com.softeem;
public class Test03 {
public static void main(String[] args) {
//调用接口,给接口传递需要的参数
Ilove ilove = (int a) -> {
for (int i = 1; i <= a; i++) {
System.out.println("这不就是重写方法吗?" + i);
}
};
ilove.iove(20);
}
}
//定义接口,编写一个带参方法
interface Ilove {
void iove(int a);
}
小结:lambda表达式只能用于抽象类并且只有一个抽象方法且方法需要重写的情况
实际上lambda表达式本质上就是重写的一个实现类,只是只保留了重要的部分(个人理解)
package com.softeem;
//简化Lambda表达式
public class Test04 {
public static void main(String[] args) {
TestLambda testLambda = null;
//方式1:去掉参数类型
testLambda = (a) -> { System.out.println("方式1"); };
//方式2:简化括号
testLambda = a -> {System.out.println("方式1"); };
//方式3:
testLambda = a -> System.out.println("方式1");
}
}
interface TestLambda {
void test(int a);
}
总结: 1.lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包。 2./前提是接口为函数式接口