1.创建和启动线程的两种传统方式:
Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:
◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法;
◆实现Runnalbe接口,重载Runnalbe接口中的run()方法。
它们都有哪些区别?相比而言,哪一种方法更好呢?
在Java中,类仅支持单继承,也就是说,当定义一个新的类的时候,它只能扩展一个外部类.这样,如果创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。
(1)通过继承Thread类的方法实现多线程:
package toheima; /** * 通过扩张Thread类实现的多线程程序 * @author Ch */ public class ThreadDemo extends Thread {//通过集成Thread类的方式 public ThreadDemo(String name){ super(name);//调用父类带参数的构造方法 } public void run(){//重写父类run方法 for(int i = 0;i<5;i++){ for(long k= 0; k <360000000;k++);//用来模拟一个非常耗时的操作的。 System.out.println(this.getName()+" :"+i); } } public static void main(String[] args) { Thread t1=new ThreadDemo("熊大");//多态 Thread t2=new ThreadDemo("熊二"); t1.start(); t2.start(); } }
(2)通过实现Runnable接口来实现:
package toheima; /** * 实现Runnable接口的类 * @author Ch */ public class ThreadDemo implements Runnable {//通过集成Thread类的方式 private String name; public ThreadDemo(String name) { this.name = name; } public void run(){//重写父类run方法 for(int i = 0;i<5;i++){ for(long k= 0; k <360000000;k++);//用来模拟一个非常耗时的操作的。 System.out.println(name+" :"+i); } } public static void main(String[] args) { ThreadDemo td1=new ThreadDemo("熊大"); ThreadDemo td2=new ThreadDemo("熊二"); Thread t1=new Thread(td1); Thread t2=new Thread(td2); t1.start(); t2.start(); } }
(3)Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
package toheima; /** * 多线程共享数据需实现Runnbale接口。 * @author Ch */ public class Thread1 implements Runnable{ private int count=15; @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "运行 count= " + count--); try { Thread.sleep((int) Math.random() * 10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread1 my = new Thread1(); new Thread(my, "C").start();//同一个mt,但是在Thread中就不可以,如果用同一个实例化对象mt,就会出现异常 new Thread(my, "D").start(); new Thread(my, "E").start(); } }
输出结果:
C运行 count= 15
D运行 count= 14
E运行 count= 13
C运行 count= 11
C运行 count= 9
C运行 count= 8
C运行 count= 7
E运行 count= 10
D运行 count= 12
E运行 count= 6
D运行 count= 5
E运行 count= 4
D运行 count= 3
E运行 count= 2
D运行 count= 1
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
2.关于线程间状态的转换:
其实线程状态的转换问题已经很显而易见了,这里我就不赘述了,上图:
4.常见线程名词注解:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
5.线程同步:
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
学习过程中,要注意的问题:
在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。