之前线程的概念已经有总结过,这里主要总结 Java 线程类的创建方式,以及实现同步和互斥
另外也会剖析一些源码
1、继承 Thread 类创建线程
start 用于启动线程,当调用 start 后,线程并不会马上运行,而是处于就绪状态,是否要运行取决于cpu给的时间片
run用于子类重写来实现线程的功能,我们一般调用的是start方法,系统调用的是run方法
应用:下面我们实现一个多线程的应用,同时从网络上下载三张图片
需要一个工具包,网上搜索下载到本地,然后导入到 idea 项目中
在 IDEA 中创建一个 package,命名为 lib ,然后右击 lib ,选择 add as 属性,最后点击确定就可以了
下面是线程代码
package myPthread; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; /** * @Title: * @Package * @Description: 实现一个下载器 * @author: maze * @date 2020/10/22下午 15:18 */ public class myPthread2 extends Thread{ private String url; //网络地址 private String name; //保存的文件名 public myPthread2(String url,String name){ this.name = name; this.url = url; } @Override public void run() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载了文件名为:"+name); } public static void main(String[] args) { myPthread2 mypthread1 = new myPthread2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=172771859,2215530038&fm=26&gp=0.jpg","小姐姐1.jpg"); myPthread2 mypthread2 = new myPthread2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=214719225,1330378216&fm=26&gp=0.jpg","小姐姐2.jpg"); myPthread2 mypthread3 = new myPthread2("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3584899235,2312422629&fm=26&gp=0.jpg","小姐姐3.jpg"); mypthread1.start(); mypthread2.start(); mypthread3.start(); } } // 下载器 class WebDownloader{ // 下载方法 public void downloader(String url,String name){ try{ FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e){ e.printStackTrace(); System.out.println("io异常,downloader方法出现异常"); } } }运行完之后,我们可以看到红色框内多了三种图片, idea 也是可以打开的
作为一个程序猿,就应该多去看美女,才有动力学习!!
另外我们发现,黄色框内的下载顺序并不是我们代码的顺序
这个是和 CPU 调度有关的,说明:多线程之间是并发执行的,这个和进程的优先级有关,当然也和图片大小有关,比如有的图片大,所以需要更多的时间才能下载好…
小总结 1、自定义线程继承 Thread 类 2、重写 run 方法,编写线程执行体 3、创建线程对象,调用 start 方法启动线程
2、 实现 Runnable 接口来创建线程
由于 thread 继承了 runnable 接口,所以使用 Runnable 接口创建线程,首先需要先创建 runnbale 对象,然后把对象丢到 thread 中进行创建线程
public class myPhread3 implements Runnable{ public static void main(String[] args) { // 创建实现类对象 myPhread3 phread3 = new myPhread3(); // 创建代理类对象 Thread thread = new Thread(phread3); thread.start(); for(int i = 0;i<20;++i){ System.out.println("我在写代码--"+i); } } @Override public void run() { for(int i = 0;i<20;++i){ System.out.println("我在听课!-------"+i); } } }小总结 1、不建议使用继承 Thread 类创建对象,因为 java 是单继承的 2、建议使用 Runnable 接口,灵活方便,避免单继承的局限性,方便同一个对象被多个线程使用 3、启动方式:Thread 是子对象.start , Runnable 是传入一个目标对象+Thread对象.start 来启动线程
下面模拟龟兔赛跑,设计一个程序实现 Runnable 接口,如何让乌龟赢
public class myPthread5 implements Runnable{ // 胜利者 private static String winner; public static void main(String[] args) { myPthread5 pthread5 = new myPthread5(); new Thread(pthread5,"乌龟").start(); new Thread(pthread5,"兔子").start(); } // 判断比赛是否结束 private boolean gameOver(int steps){ if(winner != null){ return true; } if(steps >= 100){ winner = Thread.currentThread().getName(); System.out.println("winner is"+winner); return true; } return false; } @Override public void run() { for(int i = 1;i<=101;++i) { if(Thread.currentThread().getName().equals("兔子") && i%10==0){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步"); //判断比赛是否结束 boolean flag = gameOver(i); if(flag){ break; } } } }3、实现 Callable 接口(了解即可)
实现接口,需要返回值类型重写 call 接口,需要抛出异常创建目标对象创建执行服务提交执行获取结果关闭服务 package myPthread; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; /** * @Title: myPthread6 * @Package myPthread * @Description: * @author: maze * @date 2020/10/22下午 22:38 */ public class myPthread6 implements Callable<Boolean> { private String url; //网络地址 private String name; //保存的文件名 public myPthread6(String url,String name){ this.name = name; this.url = url; } @Override public Boolean call() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载了文件名为:"+name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { myPthread6 mypthread1 = new myPthread6("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=172771859,2215530038&fm=26&gp=0.jpg","小姐姐1.jpg"); myPthread6 mypthread2 = new myPthread6("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603387219628&di=dd7f0c62bcd9f2c3a7af43e5dd1fc6fe&imgtype=0&src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201110%2F28%2F084714m51zkooi5omrcinx.jpg","小姐姐2.jpg"); myPthread6 mypthread3 = new myPthread6("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603387219625&di=0d053377748ed62367bb5565656a3d42&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20181209%2F38467a58f9264ca68eefa37719b4b739.jpeg","小姐姐3.jpeg"); //创建执行服务 ExecutorService pool = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> submit1 = pool.submit(mypthread1); Future<Boolean> submit2 = pool.submit(mypthread2); Future<Boolean> submit3 = pool.submit(mypthread3); // 获取结果 boolean res1 = submit1.get(); boolean res2 = submit2.get(); boolean res3 = submit3.get(); //关闭服务 pool.shutdown(); } } // 下载器 class WebDownloader { // 下载方法 public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("io异常,downloader方法出现异常"); } } }要求:真实角色,代理角色;真实角色和代理角色要实现同一个接口,代理角色要持有真实角色的引用
在 Java 中线程的设计就使用了静态代理设计模式,其中自定义线程类实现 Runable 接口,Thread 类也实现了 Runalbe 接口,在创建子线程的时候,传入了自定义线程类的引用,再通过调用 start()方法,调用自定义线程对象的 run()方法实现了线程的并发执行
package myPthread; /** * @Title: myPthread7Static * @Package * @Description: 静态代理模式:真实对象和代理对象都要实现同一个接口 * 好处:(1)代理对象对象可以做很多真实对象做不了的事情 (2) 真实对象专注做自己的事情 * @author: maze * @date 2020/10/22下午 23:24 */ public class myPthread7Static { public static void main(String[] args) { // 这个也相当于一个代理, Thread 实现了 Runnable 接口 new Thread(()-> System.out.println("我爱你")).start(); // You 传入给代理 WeddingCompay 类 new WeddingCompay(new You()).HappyMarry(); } } interface Marry{ void HappyMarry(); } // 真实角色 class You implements Marry{ @Override public void HappyMarry() { System.out.println("要结婚了,超开心"); } } // 代理角色 class WeddingCompay implements Marry{ //要结婚的对象 private Marry marry; public WeddingCompay(Marry marry) { this.marry = marry; } @Override public void HappyMarry() { before(); this.marry.HappyMarry(); after(); } private void after() { System.out.println("结婚之后,收尾款"); } private void before() { System.out.println("结婚之前布置现场"); } }把函数作为参数传递到方法中,使用 lambda 可以是代码简洁紧凑
lambda 表达式特点
不需要声明参数类型,编译器可识别一个参数不需要定义圆括号,但是多个参数必须定义圆括号主体中如何有一行语句,可以省略大括号,否则不可省略如果主体只有一个表达式返回值,则编译器会自动返回值,大括号需要指明表达式返回一个数值类的实现方式有,外部实现类,静态内部类,局部内部类,匿名内部类
下面是通过 lambda 表达式来实现
public class Test1Lambda { public static void main(String[] args) { ILike like = new Like(); // lambda 表达式 like = ()->{ System.out.println("i like lambda5"); }; like.lambda(); } } interface ILike{ void lambda(); } class Like implements ILike{ @Override public void lambda() { System.out.println("i like lambda1"); } }不推介使用 JDK 提供的 stop ,destroy 方法来停止线程,更好的办法是自己写一个标志位来让线程主动停止
public class myPthread8 implements Runnable{ private boolean flag = true; public static void main(String[] args) throws InterruptedException { myPthread8 pthread8 = new myPthread8(); new Thread(pthread8).start(); for(int i = 0;i<1000;++i){ System.out.println("main"+i); if(i == 900){ // 调用 stop pthread8.stopPthread(); System.out.println("线程停止..."); } } } @Override public void run() { int num = 0; while(flag){ System.out.println("run....thread"+num++); } } public void stopPthread(){ this.flag = false; } }思考:为什么 JDK 文档不建议使用 stop ,destroy 方法来停止线程呢? 答:stop 方法是从外部强行 终止一个线程,是一种粗暴的线程终止行为,在线程终止之前没有对其做任何的清除操作,例如:使用IO流时不能关流,或者会有其它不可预知的错误
下面我们模拟网络延时和倒计时
public class myPthread9Sleep implements Runnable{ private int ticknum = 10; public static void main(String[] args) { myPthread9Sleep pthread9Sleep = new myPthread9Sleep(); new Thread(pthread9Sleep,"小明").start(); new Thread(pthread9Sleep,"小亮").start(); new Thread(pthread9Sleep,"小铭").start(); } @Override public void run() { while(true){ if(ticknum < 0){ break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknum-- + "票"); } } }倒计时
public class myPthread9Sleep2 { private int num = 10; public void tendo() throws InterruptedException { while(true){ if(num <=0){ break; } Thread.sleep(1000); System.out.println(num--); } } public static void main(String[] args) { try { new myPthread9Sleep2().tendo(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class myPthread9Sleep2 { // 打印系统当前时间 public static void main(String[] args) { Date startime = new Date(System.currentTimeMillis()); while(true){ try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startime)); } catch (InterruptedException e) { e.printStackTrace(); } startime = new Date(System.currentTimeMillis()); } } }new 状态:线程对象一旦被创建就进入到新生状态
当调用 start 方法,线程立即进入就绪状态,但不意味着立刻调度执行
线程进入运行状态,线程才真正的执行线程体的代码块
当调用 sleep ,wait 或者同步锁定时,线程进入阻塞状态,阻塞解除后,重新进入就绪态,等待 CPU 调度运行
线程运行完毕后,进入死亡状态,无法再次启动线程
public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ for (int i = 0; i < 5; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(""); }); //观察状态 Thread.State state = thread.getState(); System.out.println(state); // NEW 新生 thread.start(); state = thread.getState(); System.out.println(state); // Run 启动 //只要线程不终止,就一直输出状态 while(state != Thread.State.TERMINATED){ Thread.sleep(100); state = thread.getState(); System.out.println(state); } } }Java 提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定调用哪个线程来执行
public class myPthreadPriority { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); MyPriority priority = new MyPriority(); Thread thread1 = new Thread(priority); Thread thread2 = new Thread(priority); Thread thread3 = new Thread(priority); //设置优先级再启动 thread1.start(); thread2.setPriority(1); thread2.start(); thread3.setPriority(10); thread3.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); } }文章字数太多,线程的同步与安全在下一篇总结
