Java基础之多线程

Java基础之多线程 记一次失败的学习方式
线程的三中创建方式,先上代码:

/* 创建新线程的三种方式: 1、继承Thread类; 2、实现Runable接口; 3、匿名内部类;需求:创建多线程对象,开启多线程。在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。 */ public class Test9 { public static void main(String[] args) { /* 线程启动必须调用start()方法,而非run() //方式一 ThreadTest1 tt1 = new ThreadTest1(); tt1.setPriority(10); //设置线程优先级,默认为5,范围1-10 tt1.start(); */ /* //方式二 ThreadTest2 tt2 = new ThreadTest2(); Thread theTt2Thread = new Thread(tt2); theTt2Thread.start(); */ //方式三 Thread tt3 = new Thread(){ @Override public void run(){ //使用匿名类可以很方便的访问外部变量(这里并未访问外部局部变量) //但是在JDK7以前,就必须使用final修饰 for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println("子线程:" + i); } } }; //tt3.setPriority(10); //线程优先级太高导致子线程打印完主线程才打印┓( ′?` )┏233333(主线程默认5) tt3.start(); //同时,该匿名类可以使用lambda表达式简化 Thread tt4 = new Thread(() -> { for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println("子线程:" + i); } }); //主线程打印奇数 for (int i = 0; i < 100; i++) { if (i % 2 == 1) System.out.println("主线程:" + i); } } }//通过继承Thread类来创建新线程 class ThreadTest1 extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println("子线程:" + i); } } }//通过实现Runnable接口来创建新线程 class ThreadTest2 implements Runnable { public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println("子线程:" + i); } } }

最开始执行该代码时,会产生这样的结果:
Java基础之多线程
文章图片

子线程居然在主线程打印完了才开始打印,这曾一度让我以为没有创建出新线程,刚开始的时候start()方法在主方法下,我以为是顺序的原因,所以我先调用子线程的start()方法,然后在主方法打印(也就是现在的代码),发现结果还是这样,我甚至以为是因为ThreadTest方法不是公共的原因,然后想到之前看C#的时候有讲到优先级问题,然后百度了一下发现了具体原因:

结果居然变了
Java基础之多线程
文章图片

子线程居然又开始工作了,我花了一晚上查线程优先级问题,最后发现最开始的情况居然复现不了了???(绝对不是主线程for循环在上面的原因)不行我得把我查到的结论整理一下o(╥﹏╥)o,等以后再遇到这种情况再说吧
线程优先级
1.在任意时刻,当有多个线程处于可运行状态时,运行系统总是挑选一个优先级最高的线程执行,只有当线程停止、退出或者由于某些原因不执行的时候,低优先级的线程才可能被执行
2.两个优先级相同的线程同时等待执行时,那么运行系统会以round-robin的方式选择一个线程执行(即轮询调度,以该算法所定的)(Java的优先级策略是抢占式调度!)
3.被选中的线程可因为一下原因退出,而给其他线程执行的机会:
1) 一个更高优先级的线程处于可运行状态(Runnable)
2)线程主动退出(yield),或它的run方法结束
3)在支持分时方式的系统上,分配给该线程的时间片结束
4.Java运行系统的线程调度算法是抢占式(preemptive)的,当更高优先级的线程出现并处于Runnable状态时,运行系统将选择高优先级的线程执行
5.例外地,当高优先级的线程处于阻塞状态且CPU处于空闲时,低优先级的线程也会被调度执行
参见这里
放个很详细的博客,不过有些地方还不是特别明白,以后再仔细研读
线程状态及同步处理 线程的状态,这里有个线程状态图:
Java基础之多线程
文章图片

其实上面这张图并不是特别完整,下面对于状态的转换会更完整一点:
Java基础之多线程
文章图片

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed_Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
有三种方式完成同步操作:
  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。
直接上代码:

线程同步来模拟过山洞
/* 模拟多个人通过一个山洞: 1.这个山洞每次只能通过一个人,每个人通过山洞的时间为1秒; 2.随机生成10个人,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。 显示每次通过山洞人的姓名,和通过顺序; */ public class Test9 { public static void main(String[] args) { /* Hole hole1 = new Hole(); for (int i = 0; i < 10; i++) { Thread t = new Thread(hole1); t.start(); } */ //使用锁 int numOfCrossed = 0; int[] theTest = new int[1]; //锁必须是同一个,下面synchronized的参数同理,关于synchronized参数并没有限制 Lock lock = new ReentrantLock(); for (int i = 0; i < 10; i++) { new Thread(() -> { lock.lock(); crossTheHole(theTest); lock.unlock(); }).start(); } }public static void crossTheHole(int[] numOfCrossed){ String name = Thread.currentThread().getName(); System.out.println(name+"正在穿过洞"); try { Thread.sleep(600); }catch (InterruptedException e){ e.printStackTrace(); } numOfCrossed[0]++; System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed[0] +"个"); } }class Hole implements Runnable{ int numOfCrossed = 0; Object lock = new Object(); @Override public void run(){ crossTheHole2(); } //同步代码块 public void crossTheHole(){ synchronized (lock){ String name = Thread.currentThread().getName(); System.out.println(name+"正在穿过洞"); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } numOfCrossed++; System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed +"个"); } } //同步方法 public synchronized void crossTheHole2(){ String name = Thread.currentThread().getName(); System.out.println(name+"正在穿过洞"); try { Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); } numOfCrossed++; System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed +"个"); } }

线程通信及锁 进程间通信使用wait()和notify()进行等待和唤醒,注意使用的锁需要是同一个,并且线程被唤醒后并不是立即进入运行状态,而是从WAITING释放与其他线程进行竞争,如果获取到锁,则进入RUNNABLE状态,否则进入BLOCKED状态(即未获取到锁)。
TIMED_WAITING在API中描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态,最常见的就是sleep()方法,当然,sleep单独一个线程也可以使用,注意sleep()并不会释放当前锁权限
关于wait()和notify():
Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等
每个对象都有一个机锁来控制同步访问。Synchronized关键字可以和对象的机锁交互,来实现线程的同步。
由于sleep()方法是Thread 类的方法,因此它不能改变对象的机锁。所以当在一个Synchronized方法中调用sleep()时,线程虽然休眠了,但是对象的机锁没有被释放,其他线程仍然无法访问这个对象。而wait()方法则会在线程休眠的同时释放掉机锁,其他线程可以访问该对象。
Yield()方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield() 方法将不会起作用。
一个线程结束的标志是:run()方法结束。
一个机锁被释放的标志是:synchronized块或方法结束。
Wait()方法和notify()方法:当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁。当它被一个notify()方法唤醒时,等待池中的线程就被放到了锁池中。该线程从锁池中获得机锁,然后回到wait()前的中断现场。
join()方法使当前线程停下来等待,直至另一个调用join方法的线程终止。
值得注意的是:线程的在被激活后不一定马上就运行,而是进入到可运行线程的队列中。
另外,Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。
关于多线程的更多信息请参见我的另一篇博文:Java多线程之以7种方式让主线程等待子线程结束,里面用到了concurrent这个强大的多线程包。
【Java基础之多线程】转载于:https://www.cnblogs.com/lixin-link/p/10991710.html

    推荐阅读