一、线程的创建
1.创建Thread类或者其子类
重写run方法,该run()方法是线程执行体。
2.实现Runnable接口(更多使用)
实现run方法。用new Thread(Runnable target).start()方法来启动。(Thread类实现了Runnable接口)
3.通过实现callable接口
4.使用线程池创建线程
创建线程方式的比较:
- 继承方式和接口方式,后者属于组合的技术,耦合性更低
- 后者的一个Runnable实例可以被多个线程实例共享
- 继承的方式创建线程,Java虚拟机会为其分配调用栈空间、内核线程等资源,成本更加昂贵
public static void main(String[] args) { Thread t1 = new MyThread(); t1.start();//start是直接调用run方法 运行的是thread0(t1线程) new Thread(new MyRunnable()).start();//Thread1线程 System.out.println(Thread.currentThread().getName());//main线程 final int numberOfProcess= Runtime.getRuntime().availableProcessors();//获得处理器个数 //三步进行Runnable的线程 //Runnable r1 = new MyRunnable(); //Thread t2 = new Thread(r1); //t1.start(); //Thread t3 = new Thread(r1); //t2.start(); //t1和t2共享一个线程,适用于线程比较大的情况 //一、创建执行线程的方式三:实现Callable接口。相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常 //二、执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果 // 1.执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果 MyCallable td = new MyCallable(); FutureTask<Integer> result = new FutureTask<>(td); new Thread(result).start(); // 2.接收线程运算后的结果 Integer sum; try { //等所有线程执行完,获取值,因此FutureTask 可用于 闭锁 sum = result.get(); System.out.println("-----------------------------"); System.out.println(sum); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } public class MyThread extends Thread{ //创建Thread类或者其子类,重写run方法,该run()方法是线程执行体。 @Override public void run(){ //run方法结束,那么线程也就结束了,被JVM回收 System.out.println("run1"); System.out.println(Thread.currentThread().getName());//Thread.currentThread().getName()得到当前线程名字 } } class MyRunable implements Runnable{ int i = 1; @Override public void run() { while(i<10){ System.out.println(Thread.currentThread().getName()+"run"+(i++)); } System.out.println(Thread.currentThread().getName()+"正在运行"); } } class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100000; i++) { System.out.println(i); sum += i; } return sum; } }
为什么不直接调用run方法?
- 如果在某处代码中直接调用某个线程的run方法,那么这个线程的run方法将在当前线程中运行,而不是在其自身线程中运行,违背了创建线程的初衷。
- 但是,是允许直接调用run方法的。
线程具有随机性
- 如果有多个线程,那么不确定哪个线程的顺序哪个先哪个后,都是CPU决定的 。
start方法调用结束并不意味着相应的线程已经开始运行,运行时间有线程调度器决定
运行结束的线程所占用的资源(如内存空间)会如同其他Java对象一样被JVM虚拟机垃圾回收
二、线程属性
线程的属性包括:名字(默认为Thread0)、编号(编号只在JVM运行时候唯一)、线程类别(是否是守护线程(精灵))、优先级(1-10级)。
线程类别:守护线程和用户线程
- 用户线程通常是其父类线程创建来用于专门执行某项特定任务的线程;
- 用户线程会阻止Java虚拟机的正常停止,一个Java虚拟机只有在其所有的用户线程都运行结束后才能正常停止;
- 守护线程则不会阻止Java虚拟机的正常停止,一般用来执行一些重要性不是很高的任务,例如用于监视其它线程的运行情况。
- 守护线程必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常
- 通常情况下,一个线程是否是守护线程或者是用户线程,和其父线程保持一致。
public class DaemonDemo { public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { @Override public void run() { while (true) { try { System.out.println("i am alive"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("finally block"); } } } }); daemonThread.setDaemon(true); daemonThread.start(); //确保main线程结束前能给daemonThread能够分到时间片 try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } } } //> i am alive //> finally block //> i am alive
三、线程常用方法
start() :
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
run():
Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。
sleep(int millsecond):
优先级高的线程可以在它的run()方法中调用sleep方法来使自己放弃CPU资源,休眠一段时间。
sleep() VS wait()
两者主要的区别:
1. sleep()方法是Thread的静态方法,而wait是Object实例方法
2. wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
3. sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。
isAlive():
线程处于“新建”状态时,线程调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true.
currentThread():
该方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程。
interrupt() :
一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法“吵醒”自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。
public class InterruptDemo { public static void main(String[] args) throws InterruptedException { //sleepThread睡眠1000ms final Thread sleepThread = new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } super.run(); } }; //busyThread一直执行死循环 Thread busyThread = new Thread() { @Override public void run() { while (true) ; } }; sleepThread.start(); busyThread.start(); sleepThread.interrupt(); busyThread.interrupt(); while (sleepThread.isInterrupted()) ; System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted()); System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted()); } } sleepThread isInterrupted: false busyThread isInterrupted: true 开启了两个线程分别为sleepThread和BusyThread, sleepThread睡眠1s,BusyThread执行死循环。 然后分别对着两个线程进行中断操作,可以看出sleepThread抛出InterruptedException后清除标志位,而busyThread就不会清除标志位。 另外,同样可以通过中断的方式实现线程间的简单交互, while (sleepThread.isInterrupted()) 表示在Main中会持续监测sleepThread,一旦sleepThread的中断标志位清零, 即sleepThread.isInterrupted()返回为false时才会继续Main线程才会继续往下执行。因此,中断操作可以看做线程间一种简便的交互方式。 一般在结束线程时通过中断标志位或者标志位的方式可以有机会去清理资源,相对于武断而直接的结束线程,这种方式要优雅和安全。
getPriority() :
优先级有0-10级,分为最低,最高,普通三个(Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY)
setPriority(int piority) :
设置优先级
setDaemon(boolean on) :
将该线程标记为守护线程或用户线程。
yield() :
线程可以把自己的位置让给其他线程(这只是暗示,并不表绝对)。当前线程让出cpu后,还会进行cpu资源的争夺。(不可靠方法,可能仍在运行)
join() :
如果A线程调用B线程的join方法,那么A会等待直到B线程完成
public class JoinDemo { public static void main(String[] args) { Thread previousThread = Thread.currentThread(); for (int i = 1; i <= 10; i++) { Thread curThread = new JoinThread(previousThread); curThread.start(); previousThread = curThread; } } static class JoinThread extends Thread { private Thread thread; public JoinThread(Thread thread) { this.thread = thread; } @Override public void run() { try { thread.join(); System.out.println(thread.getName() + " terminated."); } catch (InterruptedException e) { e.printStackTrace(); } } } } //> main terminated. //> Thread-0 terminated. //> Thread-1 terminated. //> Thread-2 terminated. //> Thread-3 terminated. //> Thread-4 terminated. //> Thread-5 terminated. //> Thread-6 terminated. //> Thread-7 terminated. //> Thread-8 terminated.