java多线程技能
前言:本系列将从零开始讲解java多线程相关的技术,内容参考于《java多线程核心技术》与《java并发编程实战》等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂。
并发历史
-
在没有操作系统的时候,一台计算机只执行一个程序,在那个时候,对珍贵的计算机资源来说是一种浪费
-
为了提高资源利用率(比如在等待输入的时候,可以执行其他程序),为了提高公平性(不同用户和程序对计算机上的资源有平等的使用权),为了提高便利性(实现多个任务的时候,可以通过多个程序,而不用一个程序实现多个任务)计算机加入了操作系统
-
同样,相同的原因,线程诞生了。线程可以共享进程的资源。
线程优势
发挥多处理器的强大功能
-
随着技术的发展,多处理器系统越来越普及。在一个双处理器系统上,如果只用一个线程,那么无疑浪费了资源。
线程状态
-
新建(New):创建后尚未启动的线程
-
运行(Runanle):包括了操作系统线程中的Running和Ready,处于此状态的线程可能正在执行或者等待cpu分配时间片
-
无限期等待(Waiting):等待被其他线程显式唤醒,执行wait或者join方法或者LockSupport的park方法
-
限期等待(Timed Waiting):一定时间后会由系统自动唤醒
-
阻塞(Blocked):等待获取到一个排它锁
-
结束(Terminated):线程执行结束
多线程编程的两种方式
-
继承Thread
-
实现Runnable接口
通过继承Thread
代码的执行顺序与代码的顺序无关
public class T1 { public static void main(String[] args) { MyThread myThread=new MyThread(); myThread.start(); System.out.println("代码的执行结果与代码的顺序无关"); } } class MyThread extends Thread { public void run() { System.out.println("创建的线程"); } }
如果直接执行run方法是同步(主线程调用),start方法是让系统来找一个时间来调用run方法(子线程调用),
public class T1 { public static void main(String[] args) { MyThread myThread=new MyThread(); myThread.run(); System.out.println("如果是直接执行run方法,肯定是按代码顺序执行的,因为是通过主线程调用的"); } } class MyThread extends Thread { public void run() { System.out.println("创建的线程"); } }
通过实现Runnable接口
比继承Thread的方式更有优势
-
java不能多继承,所以如果线程类已经有一个父类,那么无法再继承Thread类
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("运行中!"); } } public class Run { public static void main(String[] args) { Runnable runnable=new MyRunnable(); Thread thread=new Thread(runnable); thread.start(); System.out.println("运行结束!"); } }
线程数据非共享
public static void main(String[] args) { MyThread a = new MyThread("A"); MyThread b = new MyThread("B"); MyThread c = new MyThread("C"); a.start(); b.start(); c.start(); } class MyThread extends Thread { private int count = 5; public MyThread(String name) { super(); this.setName(name); } @Override public void run() { super.run(); while (count > 0) { count--; System.out.println("由 " + this.currentThread().getName() + " 计算,count=" + count); } } }
-
这里的i并不共享,每一个线程维护自己的i变量
线程数据共享
public static void main(String[] args) { MyThread mythread=new MyThread(); //线程a b c启动的时候,执行的是myThread的方法,此时数据共享 Thread a=new Thread(mythread,"A"); Thread b=new Thread(mythread,"B"); Thread c=new Thread(mythread,"C"); a.start(); b.start(); c.start(); }
-
由于i++不是原子操作(先获取i的值,让后再加一,再把结果赋给i),所以输出的值会有重复的情况,比如4 4 2
synchronized关键字让i++同步执行
public synchronized void run() { super.run(); count--; System.out.println("由 "+this.currentThread().getName()+" 计算,count="+count);//输出的一定是4 3 2 }
-
synchronized 关键字,给方法加上锁,多个线程尝试拿到锁,拿到锁的线程执行方法,拿不到的不断的尝试拿到锁
Thread方法
-
currentThread(),获得当前线程
-
isLive() 线程是否活跃状态(启动还未运行或者启动了正在运行即为活跃状态)
-
sleep()方法,让线程休眠
-
getId()方法 获得该线程的唯一标识
-
suspeend()方法,让线程暂停(已报废)
-
ressume()方法,让线程恢复(已报废)
-
stop()方法,让线程终止(已报废)
停止线程的方法
-
线程自己执行完后自动终止
-
stop强制终止,不安全
-
使用interrupt方法
interrupt方法
-
线程对象有一个boolean变量代表是否有中断请求,interrupt方法将线程的中断状态设置会true,但是并没有立刻终止线程,就像告诉你儿子要好好学习一样,但是你儿子怎么样关键看的是你儿子。
public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(200); thread.interrupt(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { System.out.println("i=" + (i + 1)); } } }
-
上面的代码虽然执行了interrupt方法,但是并没有中断run方法里面的程序,并且run方法全部执行完,也就是一直执行到500000
判断线程是否中断
-
interrupted方法判断当前线程是否中断,清除中断标志
-
isInterrupt 方法判断线程是否中断,不清除中断标志
interrupted方法
public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { System.out.println("i=" + (i + 1)); } } } public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); //Thread.currentThread().interrupt(); System.out.println("是否停止1?="+thread.interrupted());//false System.out.println("是否停止2?="+thread.interrupted());//false main线程没有被中断!!! //......
-
这里thread线程执行了interrupt方法,按到里thread的中断状态应该为true,但是因为interrupted方法判断的是当前线程的中断状态,也就是main线程(main线程执行thread.interrupted方法),所以他的中断状态是false
public class Run { public static void main(String[] args) { try { Thread.currentThread().interrupt(); System.out.println("是否停止1?="+Thread.interrupted());//true System.out.println("是否停止2?="+Thread.interrupted());//false //......
-
主线程执行interrupt方法,第一次执行interrupted方法的时候,中断状态为true,但是interrupted方法有清除中断标志的作用,所以再执行的时候输出的是false
isInterrupt方法
public static void main(String[] args) { MyThread thread=new MyThread(); thread.start(); thread.interrupt(); System.out.println(thread.isInterrupted());//true System.out.println(thread.isInterrupted());//true }
-
这里也有判断线程中断的作用,而判断的是他的调用者的中断状态,而且没有清除中断标志的作用,所以两次都是true
停止线程
-
在上面的代码中,我们虽然执行了interrupt方法,但是并没有中断进程,那么我们如果来中断呢?我们可以在run方法中进行判断,判断中断状态,状态为true,那么就停止run方法。
import exthread.MyThread; import exthread.MyThread; public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(2000); thread.interrupt(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } } public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了!我要退出了!"); break; } System.out.println("i=" + (i + 1)); } System.out.println("666"); } }
-
还有一个问题,我们要中断进程,通过上面的的操作我们终止了for循环,但是后面的输出666仍然执行,这并不是我们想要的中断。于是我们可以顺便抛出异常,然后直接捕获,这样的话后面的代码就不执行了。
异常法停止线程
public class MyThread extends Thread { @Override public void run() { super.run(); try { for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了!我要退出了!"); throw new InterruptedException(); } System.out.println("i=" + (i + 1)); } System.out.println("我在for下面"); } catch (InterruptedException e) { System.out.println("进MyThread.java类run方法中的catch了!"); e.printStackTrace(); } } }
-
当然我们也可以直接return,但是抛出异常比较好,因为后面可以继续将异常抛出,让线程中断事件得到传播
return 停止线程
for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了!我要退出了!"); return; } System.out.println("i=" + (i + 1)); }
sleep与interrupt
-
中断状态,进入sleep抛出异常
-
睡眠进入中断状态,抛出异常
public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(200); thread.interrupt(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } } class MyThread extends Thread { @Override public void run() { super.run(); try { System.out.println("run begin"); Thread.sleep(200000); System.out.println("run end"); } catch (InterruptedException e) { System.out.println("在沉睡中被停止!进入catch!"+this.isInterrupted()); e.printStackTrace(); } } }*/
暂停线程
-
suspend (作废)会让同步方法直接锁住
public static void main(String[] args) { try { final SynchronizedObject object = new SynchronizedObject(); Thread thread1 = new Thread() { @Override public void run() { object.printString(); } }; thread1.setName("a"); thread1.start(); Thread.sleep(1000); Thread thread2 = new Thread() { @Override public void run() { System.out .println("thread2启动了,但进入不了printString()方法!只打印1个begin"); System.out .println("因为printString()方法被a线程锁定并且永远的suspend暂停了!"); object.printString(); } }; thread2.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class SynchronizedObject { synchronized public void printString() { System.out.println("begin"); if (Thread.currentThread().getName().equals("a")) { System.out.println("a线程永远 suspend了!"); Thread.currentThread().suspend(); } System.out.println("end"); } }
-
当thread1执行了suspend方法后,printString方法直接就被锁住了,也就是thread1把这个锁占住了,但是却不工作
public void println(String x) { synchronized (this) { print(x); newLine(); } }
-
用System.out.println() 方法替换pringString方法同样也会锁住,因为这个方法也加锁了,当thread1暂停之后,同样占住了这个锁
yield方法
-
放弃当前cpu资源,让其他任务去占用,但是什么时候放弃不知道,因为放弃后,可能又开始获得时间片
线程优先级
-
cpu先执行优先级高的线程的对象任务, setPriority方法可以设置线程的优先级
-
线程优先级具有继承性,也就是说A线程启动B线程,二者优先级一样,同样main主线程启动线程A,main和A的优先级也是一样的
public static void main(String[] args) { System.out.println("main thread begin priority=" + Thread.currentThread().getPriority()); Thread.currentThread().setPriority(6); System.out.println("main thread end priority=" + Thread.currentThread().getPriority()); MyThread1 thread1 = new MyThread1(); thread1.start(); } class MyThread1 extends Thread { @Override public void run() { System.out.println("MyThread1 run priority=" + this.getPriority()); MyThread2 thread2 = new MyThread2(); thread2.start(); } }
优先级特性
-
规则性,cpu尽量将资源给优先级高的
-
随机性,优先级较高的不一定先执行完run方法
守护线程
-
线程有两种一种是用户线程,一种是守护线程
-
垃圾回收线程是典型的守护线程,当jvm中还有非守护线程,守护线程就一直还在,知道非守护线程不存在了,守护线程才销毁
总结
-
线程提高了资源利用率
-
线程的实现可以通过继承Thread类,也可以通过实Runnable接口
-
线程中断方式有3种,常用的是interrupt方法,该方法并没有立即中断线程,只是做了一个中断标志
-
interrupted和isInterrupt两种方法都可以查看线程中断状态,第一种查看的是当前线程的中断状态,第二种查看的该方法调用者的中断状态
-
interrupted方法会清除中断状态,isInterrupt不会清除中断状态
-
interrupt方法没有真正中断线程,所以可以在run方法里面判断中断状态,然后通过抛出异常或者return来中断线程
-
当线中断状态为true,再进入sleep会抛出异常,反之一样,sleep状态执行interrupt方法,同样会抛出异常
-
线程暂停容易将锁占住
-
线程具有优先级,可以通过方法设置线程优先级,cpu会将资源尽量给优先级高的线程,但是当优先级差别不大的时候,优先级高的不一定先执行完run方法
-
线程有两种,一种用户线程,一种守护线程,直到用户线程都销毁,守护线程才销毁
-
作者:jiajun 出处: http://www.cnblogs.com/-new/
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/15338.html