一、第一种:继承Thread类
【1】在学习多线程一章之前,以前的代码是单线程的吗?
不是,出了有main方法对应的线程,还有处理异常的线程和垃圾收集器的线程。以前也是三个线程同时执行的。
【2】现在我想自己制造多线程----->创建线程
线程类----->线程对象,创建线程说到底就是创建对象,因为java是面向对象的语言
想要将类具备争夺资源的能力,那么必须继承一个类:Thread,才能具备争夺资源的能力。
PS:(1)需要注意,子线程要使用的话,要先启动线程,使用start()方法。
(2)因为main方法是从上到下顺序执行的,所以启动子线程的代码,要写在主线程的前面。
package com.wxj.test01; public class TestThread extends Thread{ @Override public void run() { //输出1-10 for(int i=0;i<=10;i++){ System.out.println(i); } } }
package com.wxj.test01; public class Test { public static void main(String[] args) { for(int i=0;i<=10;i++){ System.out.println("main1----"+i); } //制造其他线程,要跟主线程争夺资源 //具体的线程对象 TestThread tt = new TestThread(); //tt.run();//这个run方法不能直接调用,直接调用就被当作一个普通的方法。 //想要tt这个子线程真正起作用,必须要启动这个线程: tt.start();//start()方法是Thread类中的方法 //主线程中也输出十个数: for(int i=0;i<=10;i++){ System.out.println("main2----"+i); } } }
输出结果:
设置读取线程名字
【1】setName,getName方法来进行设置读取:
Thread.currentThread() 这个方法的作用就是获取当前正在执行的线程
package com.wxj.test01; public class Test { public static void main(String[] args) { //给main方法这个主线程设置名字 Thread.currentThread().setName("主线程"); for(int i=0;i<=10;i++){ System.out.println(Thread.currentThread().getName()+i); } //制造其他线程,要跟主线程争夺资源 //具体的线程对象 TestThread tt = new TestThread(); //tt.run();//这个run方法不能直接调用,直接调用就被当作一个普通的方法。 //想要tt这个子线程真正起作用,必须要启动这个线程: tt.setName("子线程"); tt.start();//start()方法是Thread类中的方法 //主线程中也输出十个数: for(int i=0;i<=10;i++){ System.out.println("main2----"+i); } } }
【2】通过构造器设置名字:
这个方法也是通过父类来完成的,定义一个有参的构造器,然后调用父类的有参构造器,来实现对线程名字的设置。
package com.wxj.test01; public class TestThread extends Thread{ public TestThread(String name){ super(name); } @Override public void run() { //输出1-10 for(int i=0;i<=10;i++){ System.out.println(this.getName()+i); } } }
【3】习题:买火车票
有北京到哈尔滨的10张票,现在有3个窗口,每个窗口100人,一起抢票。
(1)原理:每个窗口都是一个线程对象
(2)代码:
package com.wxj.test02; public class BuyTicketThread extends Thread{ //每个窗口都是一个线程对象,每个对象执行的代码都要放到重写的run方法中 static int Ticket = 10;//多个窗口共享10张票 public BuyTicketThread(String name){ super(name); } @Override public void run() { //每个窗口后面有100人在抢票 for(int i=1;i<=100;i++){ if(Ticket>0){System.out.println("我在"+this.getName()+"抢到了第"+ Ticket-- +"张车票");} } } }
package com.wxj.test02; public class Test { public static void main(String[] args) { BuyTicketThread b1 = new BuyTicketThread("窗口1"); b1.start(); BuyTicketThread b2 = new BuyTicketThread("窗口2"); b2.start(); BuyTicketThread b3 = new BuyTicketThread("窗口3"); b3.start(); } }
二、第二种:实现Runnable接口
【1】习题:买火车票
共享一个线程对象
package com.wxj.test03; public class BuyTicketThread implements Runnable{ int Ticket = 10; @Override public void run() { for(int i=1;i<=100;i++){ if(Ticket>0){System.out.println("我在"+Thread.currentThread().getName()+"抢到了第"+ Ticket-- +"张车票");} } } }
package com.wxj.test03; public class Test { public static void main(String[] args) { //定义一个线程对象 BuyTicketThread t = new BuyTicketThread(); Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口1"); t1.start(); Thread t3 = new Thread(t,"窗口1"); t3.start(); } }
【2】实际开发中,方式1继承Thread类 用的多 还是 方式2 实现Runnable接口这种方式多呢?-->方式2
(1)方式1 有单继承的局限性,因为继承了Thread类,就不能继承其他类了
(2)方式2 的共享资源的能力也会强一些,不需要非得加个static来修饰
【3】Thread类和Runnable接口有什么关系?
三、第三种:实现Callable接口
对比第一种和第二种创建线程的方式,发现无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要一个run方法,但是这个run方法有不足:
基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:
实现Callable接口的好处:(1)有返回值(2)能抛出异常
缺点:线程的创建比较麻烦
package com.wxj.test04; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class RandomNum implements Callable<Integer> { //1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型 //2.如果带泛型,那么call的返回值就是泛型对应的类型 //3.从call方法看到:这个方法有返回值,还可以抛出异常 @Override public Integer call() throws Exception { return new Random().nextInt(10);//返回10以内的随机数 } } class Test{ public static void main(String[] args) throws ExecutionException, InterruptedException { //定义一个线程对象 RandomNum trn = new RandomNum(); FutureTask f = new FutureTask(trn); Thread t = new Thread(f); t.start(); Object obj = f.get(); System.out.println(obj); } }