电影网站模板下载,信用网企业查询,中国十大上市装修公司,网站制作设计正规公司线程是操作系统中的概念#xff0c;操作系统内核实现了线程这样的机制#xff0c;并提供了一些API供外部使用。
JAVA中 Thread类 将系统提供的API又近一步进行了抽象和封装#xff0c;所以如果想要使用多线程就离不开 Thread 这个类。
线程的创建(Thread类)
在JAVA中 创建…线程是操作系统中的概念操作系统内核实现了线程这样的机制并提供了一些API供外部使用。
JAVA中 Thread类 将系统提供的API又近一步进行了抽象和封装所以如果想要使用多线程就离不开 Thread 这个类。
线程的创建(Thread类)
在JAVA中 创建线程可以有多种方法这里简单介绍几种。
方法一我们自己编写一个类使这个类继承自Thread类然后重写里面的 run() 方法。 class MeThread extends Thread {//必须要实现这个方法此方法是新线程执行的入口方法告诉线程应该做什么Overridepublic void run() {System.out.println(这是新线程执行的任务);}}public static void main(String[] args) {//创建一个线程对象MeThread t new MeThread();//调用系统API启动线程t.start();}
当我们运行程序之后就会执行 run() 方法中的打印操作。
new 一个Thread类只是创建出了一个线程并不会调用系统API创建线程只有当调用了start() 方法之后才会调用系统API在系统中创建出线程并启动。此时不理解可以因为在介绍isAlive()方法时会验证
这个重写的 run() 方法可以理解为一个任务这个方法会在线程启动时自动被调用执行当线程执行完这个方法中的内容时该线程就会被销毁并且无法再次使用start()方法唤醒。
此时我们为了可以更好的呈现多线程并发编程的效果对上述代码进行了一些细微修改。 class MeThread extends Thread {//必须要重写这个方法此方法是新线程执行的入口告诉线程应该做什么Overridepublic void run() {//此处让这个新线程每隔0.1s执行一次打印操作while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(这是创建的新线程执行的任务);}}}public static void main(String[] args) {MeThread t new MeThread();//调用系统API启动线程t.start();//让主线程每隔0.1s执行一次打印操作while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(主线程);}} 此时可以看到两个while循环是“同时执行的” 每个线程都是一个独立的执行流。
代码执行之后可以看到程序在无规律的进行打印其主要原因是系统对线程的调度是随机的。 方法二我们自己编写一个类使实现 Runnable接口 然后重写里面的 run() 方法。
Thread类 实现了 Runnable接口Thread类 中的 run() 方法也是重写的 Runnable接口中的 因为在 Thread类 中提供了一个这样的构造方法 class MeRunnable implements Runnable{//必须要重写这个方法此方法是新线程执行的入口Overridepublic void run() {System.out.println(这是一个新线程执行的任务);}}public static void main(String[] args) {MeRunnable runnable new MeRunnable();Thread t new Thread(runnable);//调用系统API启动线程t.start();}
使用 Runnable 接口和直接继承 Thread 的区别就是可以帮我们降低代码的耦合性也就是“解耦合”。
Runnable 它表示一个可以执行的任务 而它并不关心这个任务是啥在哪里执行这个任务也不一定和线程强相关因为这个代码可能使用单线程、多线程还是不使用线程或者是用其他方法例线程池协程……执行都没有任何区别。
而此时使用 Runnable 就可以将这个任务单独的提取出来这样就可以随时改变这个任务是使用什么方法进行执行例如后面如果不想用线程了就可以直接在main方法中进行调用例 class MeRunnable implements Runnable{Overridepublic void run() {System.out.println(这是一个任务);}}public static void main(String[] args) {MeRunnable runnable new MeRunnable();//此时不想使用线程执行这个任务runnable.run();} 方法三使用匿名内部类。 public static void main(String[] args) {Thread t new Thread(){Overridepublic void run() {System.out.println(这是创建的新线程执行的任务);}};//调用系统API启动线程t.start();}
因为在 Thread类 中提供了一个这样的构造方法
所以我们可以写成过这样 public static void main(String[] args) {Thread t new Thread(new Runnable(){Overridepublic void run() {System.out.println(这是一个新线程执行的任务);}});//调用系统API启动线程t.start();} 方法四因为Runnable接口是一个函数式接口所以可以利用 Lambda表达式 来创建线程。 public static void main(String[] args) {Thread t new Thread(() - System.out.println(这是创建的新线程执行的任务));//调用系统API启动线程t.start();}
除了观看控制台的输出结果来观察多线程之外还可以使用JDK中带有的工具 jconsole 来更形象的观测具体方法可以跳转这里http://t.csdnimg.cn/b8Wca
Thread类的一些常见构造方法
方法说明Thread()创建线程对象Thread(Runnable target)使用 Runnable 对象创建线程对象Thread(String name)创建线程对象并命名Thread(Runnable target, String name)使用 Runnable 对象创建线程对象并命名Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理分好的组即为线程组这 个目前我们了解即可
Thread类中的一些常见属性
属性获取方法IDgetId()名称getName()状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAlive()是否被中断isInterrupted()
getId()
获取当前线程的 id
id 是线程的唯一身份标识这个 id 是 JAVA 为这个线程分配的并不是系统 API 分配的 ID 更不是 PCB 的 ID 。 public static void main(String[] args) {Thread t new Thread(() - {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println(这是创建的新线程执行的任务);}});Thread t1 new Thread(() - {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(这是创建的新线程执行的任务);}});t.start();t1.start();System.out.println(t.getId());//获取并打印当前线程的IDSystem.out.println(t1.getId());//获取并打印当前线程的ID} isDaemon()
判断当前线程是否为后台线程。
线程默认都是前台线程。
前台线程只要该进程中还有前台线程未执行完那么该进程就不会结束前台线程会影响进程的结束与否后台线程只要该进程中的前台线程都执行完毕那么此时无论是否有未执行完的后台线程进程都会结束后台线程不会影响进程的结束与否。 public static void main(String[] args) {Thread t new Thread(() - {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println(这是创建的新线程执行的任务);}});t.start();System.out.println(t.isDaemon());//获取并打印当前线程是否为后台线程} 此时将上述代码改成后台线程。 public static void main(String[] args) {Thread t new Thread(() - {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println(这是创建的新线程执行的任务);}});//将当前线程设置为后台线程t.setDaemon(true);t.start();System.out.println(t.isDaemon());} 此时因为主线程main线程飞快的执行完了所以没有任何打印。
isAlive()
分别在线程启动前后打印判断线程是否存活注意线程对象存活时线程并不一定会存活 public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {try {Thread.sleep(1000);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println(新线程执行完毕);});System.out.println(线程启动前);System.out.println(线程是否存活t.isAlive());t.start();System.out.println(线程已启动);System.out.println(线程是否存活t.isAlive());Thread.sleep(2000);System.out.println(线程是否存活t.isAlive());} 根据结论可以得知当我们创建线程对象之后并不会调用系统API创建线程只有当调用了start() 方法之后才会调用系统API在系统中创建出线程并启动。
打断线程
在JAVA中打断线程的方法是比较唯一的本质上都是让run()方法尽快执行结束而在C中是有办法可以在线程执行过程中直接销毁该线程但是这样做有个坏处比如这个线程在写文章时突然中断了那就会令这篇文章有头无尾而在JAVA中就可以允许在此处进行一些收尾工作。
而现实中令run()方法迟迟无法结束的原因一般都是应为循环所以只要 结束循环就可以让线程尽快执行完run()方法从而达到打断线程的效果。
此处介绍两种方法
第一种方法
可以手动创建出一个标志位用来控制 run() 方法中循环的终止条件。 //创建一个成员变量用来控制循环的终止条件,默认值为 false;private static boolean isQuit;public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {while(!isQuit) {System.out.println(新线程正在工作);try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace(); }}System.out.println(新线程执行完毕);});t.start();Thread.sleep(500);isQuit true;System.out.println(打断新线程);} 但是有以下两个问题
需要手动创建标志位如果循环正处在sleep状态程序将不能进行及时的响应。
第二种方法
在JAVA中默认就有一个标志位我们可以利用JAVA中默认的标志位来进行快速结束run()方法的操作。
好处是这样我们就不用再单独创建一个变量不用再思考变量捕获的问题了。
interrupt() :该方法可以将线程中默认的标志位设置为trueisInterrupt():判断对象关联的线程的标志位是否被设置调用后不清除标志位。还可以使sleep()方法抛出InterruptedException异常来强行中断sleep()方法。
利用这两个方法就可以实现线程的打断 public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {//Thread.currentThread()该方法是用来得到该线程的实例也就是t哪个线程调用该方法就返回哪个线程的实例//因为此时t还没有被创建所以不能写为t.isInterrupted()while(!Thread.currentThread().isInterrupted()) {System.out.println(新线程正在工作);try {Thread.sleep(1000);} catch (InterruptedException e) {//打印异常e.printStackTrace();}}System.out.println(新线程执行完毕);});t.start();Thread.sleep(5000);//设置标志位为truet.interrupt();System.out.println(打断新线程);} 结果可以看出来此时标志位确实被设置了sleep()方法也抛出了异常可是循环并没有被终止。
原因是sleep()方法在抛出异常之后会自动将标志位清除而此引起的结果就和没有设置标志位是相同的。
而JAVA如此设计的原因其实就是扩大程序员的可操作空间可以再sleep()方法抛出异常之后进行一些收尾工作。 public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {//Thread.currentThread()该方法是用来得到该线程的实例也就是t哪个线程调用该方法就返回哪个线程的实例//因为此时t还没有被创建所以不能写为t.isInterrupted()while(!Thread.currentThread().isInterrupted()) {System.out.println(新线程正在工作);try {Thread.sleep(1000);} catch (InterruptedException e) {//此处可以写一些收尾工作的代码break;}}System.out.println(新线程执行完毕);});t.start();Thread.sleep(500);//设置标志位为truet.interrupt();System.out.println(打断新线程);} 线程等待
再多线程的代码中由于线程的调度是随机的所以也就会导致每个线程的结束时间也是无法预测的而这种情况下就会使得在有些场景下代码出现BUG。
而线程等待就是让一个线程来等待另一个线程执行结束本质上就是来控制线程结束的顺序。
join()
实现线程等待的效果让一个线程阻塞等待另一个线程执行结束之后再执行。
等的线程在哪个线程中调用 join 方法哪个线程就阻塞等待被等的线程调用的哪个线程对象的 join 方法哪个线程就是被等的线程当这个线程执行完毕等的线程才会执行。
我们创建一个线程 t 让这个线程每隔一秒打印一次数据让主线程等待该线程。
Thread t new Thread(()-{for (int i 0; i 4; i) {System.out.println(t线程执行中);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
});
t.start();
System.out.println(等待开始);
t.join();
System.out.println(等待结束); 注意如果 t 线程已经执行结束此时再调用 join 就会直接返回执行不会发生阻塞等待。
join(等待时间)
上面的 join() 方法是一种“死等”的方法只要被等待的线程不结束那么就会一直等待下去。
但是一般情况下我们并不会死等而是等待超过了一定时间之后就不会再继续等待了因为没有意义。
join(long millis) 最多等待 millis 毫秒join(long millis, int nanos) 和上面的方法一样 就是时间精度更高精确到了纳秒
休眠线程
sleep(long millis) 让线程休眠 millis 毫秒sleep(long millisint nanos) 和上面的功能一样就是精度更高。
但是下面的这一种其实意义不大因为 sleep() 本身就存在一定的误差并不是你写 sleep(1000) 就真的刚好等精确 1000 ms 它还有一个调度的开销。系统会按照 1000 这个时间来休眠线程当时间到了之后系统会唤醒该线程阻塞 - 就绪而且并不是线程进入就绪状态就能立即进入CPU执行。
//获取系统当前时间戳
long a System.currentTimeMillis();
Thread.sleep(1000);
//获取系统当前时间戳
long b System.currentTimeMillis();
System.out.println(时间(b - a) ms); 而且每次运行之后结果都是不同的。 线程的状态
JAVA中线程的所有状态都存储在一个枚举类型中Thread.State
for (Thread.State str:Thread.State.values()) {System.out.println(str);
}
可以通过上述代码来打印所有的线程状态 通过 getState() 方法可以获取线程的状态
NEWThread对象已经有了但还没有调用 start() 方法
Thread t new Thread(()-{});
System.out.println(t.getState()); RUNNABLE就绪状态线程已经在CPU上执行了或者排队准备执行
Thread t new Thread(()-{while(true) {}
});
t.start();
System.out.println(t.getState()); BLOCKED阻塞由于锁竞争导致的阻塞
Object lock1 new Object();
Object lock2 new Object();Thread t1 new Thread(()-{synchronized(lock1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized(lock2) {}}
});
Thread t2 new Thread(()-{synchronized(lock2) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized(lock1) {}}
});
t1.start();
t2.start();
//让主线程等待 2 秒
Thread.sleep(2000);
//此时t1和t2两个线程会因为互相争对方的锁而导致死锁
System.out.println(t1.getState());
System.out.println(t2.getState()); WAITING阻塞由 wait 这种不固定时间的方式引起的阻塞
Object lock1 new Object();Thread t1 new Thread(()-{synchronized(lock1) {try {//调用wait方法让线程阻塞lock1.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}
});t1.start();
Thread.sleep(1000);
System.out.println(t1.getState()); TIMED_WAITING由 sleep 这种固定时间限制的方式引起的阻塞
Thread t1 new Thread(()-{try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}
});t1.start();
Thread.sleep(1000);
System.out.println(t1.getState()); TERMINATEDThread对象还在可是线程已经没了
Thread t1 new Thread(()-{});t1.start();
Thread.sleep(1000);
System.out.println(t1.getState());