1.继承Thread类
public class Demo1 { public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); for (int i = 0 ; i < 1000 ; i++){ System.out.println("bbb"); } } } class MyThread extends Thread{ @Override public void run() { for (int i = 0 ; i < 1000 ; i++){ System.out.println("aaaaa"); } } }
2.实现Runnable接口
原理:Thread构造函数中传入了Runnable的引用,成员变量记住了它,调用start()方法时调用了run()方法,判断成员变量Runnable的引用是否为空。不为空时执行。编译时看的是Runnable的run(),运行是执行的是子类的run方法。
public class Demo2 { public static void main(String[] args) { Thread t = new Thread(new MyThread2()); // 父类引用指向子类对象 t.start(); for (int i = 0 ; i < 1000 ; i++){ System.out.println("bbb"); } } } class MyThread2 implements Runnable{ @Override public void run() { for (int i = 0 ; i < 1000 ; i++){ System.out.println("aaaaa"); } } }
3.两种方式的优缺点以及使用场景
-
继承Thread
优点:可以直接使用Thread类中的方法,代码简单
缺点:如果已经有了父类,就不能用这种方法了。
-
实现Runnable接口
优点:即使自定义的线程有了父类也没有关系,因为有了父类也可以实现接口,而且接口是可以多实现的
缺点:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,使用起来比较麻烦
-
建议:
如果没有父类,用Thread,否则用Runnable。
4.用匿名内部类的方式实现上面两种方式
public class Demo3 { public static void main(String[] args) { new Thread() { // 继承Thread类 @Override public void run() { for (int i = 0 ; i < 1000 ; i++){ System.out.println("aaaaa"); } } }.start(); new Thread(new Runnable() { // 将Runnable的子类对象传递给Thread的构造方法 @Override public void run() { for (int i = 0 ; i < 1000 ; i++){ System.out.println("bbb"); } } }).start(); } }
5.给线程设置名称以及获取线程名称
public class Demo3 { public static void main(String[] args) { // 继承Thread的第一种方法 new Thread("a线程") { @Override public void run() { for (int i = 0 ; i < 1000 ; i++){ System.out.println(this.getName() + "---aaaaa"); } } }.start(); // 继承Thread的第二种方法 new Thread() { @Override public void run() { this.setName("c线程"); for (int i = 0 ; i < 1000 ; i++){ System.out.println(this.getName() + "---ccc"); } } }.start(); // 实现Runnable接口的方法(利用Thread的currentThread静态方法获取当前进程) new Thread(new Runnable() { @Override public void run() { for (int i = 0 ; i < 1000 ; i++){ Thread.currentThread().setName("b线程"); System.out.println(Thread.currentThread().getName() + "---bb"); } } }).start(); } }
6.多线程常用的一些方法
static void sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程。
static void yield() 对调度程序的一个暗示,即当前线程愿意让生当前使用的处理器。
void setPriority(int newPriority) 更改此线程的优先级。
除了Thread类的方法,还会结合Object类的某些方法使用:
void notify() 唤醒正在等待对象监视器的单个线程。
void notifyAll() 唤醒正在等待对象监视器的所有线程。
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
几个面试问题:
1.在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法。
2.为什么wait方法和notify方法定义在Object类中?
因为锁对象可以使任意对向,Object是所有类的基类,所以wait方法和notify方法需要定义在Object这个类中。
3.sleep方法和wait方法的区别?
sleep方法必须传入参数,参数就是时间,时间到了自动醒来。
wait方法可以传参数也可以不传参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
sleep方法在同步函数或同步代码块中,不释放锁
wait方法在同步函数或同步代码块中,释放锁
7.互斥锁(ReentrantLock类)
JDK1.5以后的,可以替代wait和notify。
void lock() 获得锁。
void unlock() 尝试释放此锁。
Condition newCondition() 返回Condition用于这种用途实例Lock实例。
Condition的方法:
void await() 导致当前线程等到发信号或 interrupted 。
void signal() 唤醒一个等待线程。
void signalAll() 唤醒所有等待线程。