java - 线程 - 常用方法
线程基础
https://www.cnblogs.com/clamp7724/p/11648308.html
多线程实现
https://www.cnblogs.com/clamp7724/p/11649719.html
join:
join的底层源码:
while (isAlive()) 用这个判断join进来的线程是否还在执行
当A join( x )到 B中时,
B等待A x秒,然后停止等待。两个线程继续并行
如果join中没参数,则B一直等待直到A不再执行。
用这个方法可以控制A,B执行的顺序。
package joinTest; public class JoinTest { public static void main(String[] args){ FirstThread t1 = new FirstThread(); SecondThread t2 = new SecondThread(); //我们无法直接控制线程的执行顺序, // 有时候我们希望控制线程的顺序只能通过一些技巧和调用一些方法。 // t1.start(); // t2.start(); //正常情况下,虽然t1.start在t2前面,但是不一定谁先执行谁先结束。有时候我们希望t2必须比t1先结束,这时候可以用join, // 把t2线程加到t1中,变成t1的一部分,这样t2必定比t1先结束。 //join:把两个线程合并成一个,运行到join时先把join的线程执行完再继续执行被jion的线程。 t1.start(); //第一个线程开始 //第二个线程开始 //第二个线程结束 //第一个线程结束 } }
package joinTest; public class FirstThread extends Thread{ @Override //注解,表示这个方法是重写的。可以理解为给计算机看的注释。 public void run() { System.out.println("第一个线程开始"); SecondThread t2 = new SecondThread(); t2.start(); try { t2.join(); ////join中可以添加参数 //t2.join(2000); //表示t2添加入t1中,给2秒的执行时间,如果没有执行完毕,则继续t1和t2并行,因为t2中等待了5秒,所以执行结果为 //第一个线程开始 //第二个线程开始 //第一个线程结束 //第二个线程结束 //测试:t2.join(4999); //发现结果有时第一个先结束,有时第二个,说明t2结束后是两者继续一起并行,并不是切换。 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("第一个线程结束"); } }
package joinTest; public class SecondThread extends Thread{ @Override public void run() { System.out.println("第二个线程开始"); try { Thread.sleep(5000); //sleep是静态方法,固定Thread.sleep()就好 //让第二个线程执行的时候等5秒,这样可以体现出一个执行的过程,如果两个线程没有先后关系,FirstThread必然先执行完。 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("第二个线程结束"); } }
join:
A在准备把B踢出自身线程的时候需要操作B,所以需要访问B。如果B被访问时处于被锁状态则A无法踢出B只能继续等待。
修改SecondThread,加入ThirdThread
package joinTest; public class SecondThread extends Thread{ @Override public void run() { System.out.println("第二个线程开始"); ThirdThread t3 = new ThirdThread(this);//把当前线程二传进去,让线程三锁定当前线程二 t3.start();//t3开始运行,这样保证了t1先开始,然后t2,然后t3 //第一个线程开始 //第二个线程开始 //第三个线程开始 //锁定线程二 //-----5秒(t2执行了5秒,t3也并行了5秒) //第二个线程结束 //-----5秒(t3总共执行了10秒结束) //释放线程二 //第三个线程结束 //-----线程二被释放,所以线程一得以继续进行 //第一个线程结束 //因为t3没有join进t2,所以并不影响t2运行,只是让t1无法踢出t2只能一直等着t2结束,所以结束顺序是t2,t3,t1. // try { // t3.join(); // } catch (InterruptedException e) { // e.printStackTrace(); // } //如果加了join,则t2会等到t3结束(等10秒)才继续进行(再等5秒,然后t1才能继续),可以看出t3 jion t2, t2 join t1 后, t1的等待时间是t2和t3运行时间之和,3个线程合并成1条。 //第一个线程开始 //第二个线程开始 //第三个线程开始 //锁定线程二 //-----10秒 //释放线程二 //第三个线程结束 //-----5秒 //第二个线程结束 //第一个线程结束 try { Thread.sleep(5000); //sleep是静态方法,固定Thread.sleep()就好 //让第二个线程执行的时候等5秒,这样可以体现出一个执行的过程,如果两个线程没有先后关系,FirstThread必然先执行完。 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("第二个线程结束"); } }
package joinTest; import com.sun.source.tree.SynchronizedTree; public class ThirdThread extends Thread{ private SecondThread t2; public ThirdThread(SecondThread t2){ this.t2 = t2;//把t2对象作为参数传进来,这样可以保证线程三操作的和线程一的线程二是同一个线程对象。 } @Override public void run() { System.out.println("第三个线程开始"); try { synchronized(t2){//让线程三执行时锁定线程二 System.out.println("锁定线程二"); Thread.sleep(10000);//锁定线程二10秒,看看等待线程二2秒后线程一是否还会继续执行 System.out.println("释放线程二"); } }catch (Exception e){ e.printStackTrace(); } System.out.println("第三个线程结束"); } }
上面可以知道,t3锁住t2后,t1需要等t3释放了t2才能踢出t2继续运行。
如果t1执行时锁住了t3,t2执行时锁住了t1,t3执行时锁住了t2,那么三个人都要等到对方释放才能继续进行,形成“死锁”。
哲学家吃饭:
4个哲学家有4根筷子,每个人吃饭先拿左手筷子,再拿右手筷子,就可能形成死锁
死锁的出现条件:
1.有共用资源
2.执行条件相互牵制
所以解决死锁的办法:
1.防止共有资源 (尽量。。。因为很多时候无法避免。。。)
2.用sleep等错开执行时间,防止相互牵制。(比如让哲学家们,按顺序执行,设置sleep时间把他们错开。或者用jion安排好执行顺序。)
timer: 可以理解为定时器
package timer; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerTest { private static int num = 0;//为了记录匿名内部类的执行次数,匿名内部类为了防止数据不一致,只能加入static或者final的外部变量。 public static void main(String[] args){ ArrayList<String> a = new ArrayList<String>(); a.add("a"); a.add("b"); a.add("c"); a.add("d"); Date d = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); //设置时间格式 try { d = simpleDateFormat.parse("2019-10-11 09:30:00");//给Date d 赋值,用来作为程序开始执行的时间,其实是只要过了这个时间就会开始,比如你设成当前时间之前10分钟,它会直接启动 } catch (ParseException e) { e.printStackTrace(); } Timer t = new Timer(); //public void schedule(TimerTask task, Date firstTime, long period) 3个参数: //1 //一个TimerTask类型的abstract类,需要重写run方法。这里用的匿名内部类,也可以写个class继承TimerTask然后作为参数传进来。 //看不懂的话参考内部类笔记:https://www.cnblogs.com/clamp7724/p/11609138.html //2 //一个Data类型参数,控制开始执行的时间 //3 //一个Long类型的参数,表示每过多少毫秒执行一次。 System.out.println("定时器启动!"); System.out.println("当前时间:" + simpleDateFormat.format(new Date())); t.schedule( new TimerTask() { @Override public void run() { System.out.println("第" + num++ + "次执行"); System.out.println(simpleDateFormat.format(new Date()));//显示执行的时间 } }, d, 3000); //上面的这局表示,从d 时间开始每过3秒执行一次 run 内部的代码。 //定时器启动! //当前时间:2019-10-11 09:29:22 //第0次执行 //2019-10-11 09:30:00 //第1次执行 //2019-10-11 09:30:03 //第2次执行 //2019-10-11 09:30:06 //第3次执行 //2019-10-11 09:30:09 //... } }