前言
近期抽空在学习多线程技术,在图书馆借了一本书放在了家里看,在公司就找来了Java多线程核心技术来学习,现在就学习过程做的笔记写到了这里。笔记只是简单整理,没有细粒度的描述,估计也没有很强的逻辑在里面,只是把重要的基础知识点罗列了出来,以此来巩固下多线程基础吧。
目标
从这篇读书笔记中,我们可以大概学习到以下知识:
- 线程的创建
- 线程的启动
- 如何使线程暂停
- 如何使线程停止
- 线程的优先级
- 线程安全相关问题
线程创建
在Java的JDK开发包中,为我们提供了两种实现多线程编程的方式,一种是集成Thread类,另一种是实现Runnable接口。因为Java单继承的特性,为了满足各种业务的需要,可以通过实现Runnable的方式来创建线程,其实Thread类的具体实现也是实现Runnable接口的。
public class Thread implements Runnable
1、继承Thread类的方式
1 public class MyThread01 extends Thread { 2 3 @Override 4 public void run() { 5 System.out.println(Thread.currentThread().getName() + " is a new thread."); 6 } 7 8 public static void main(String[] args) { 9 MyThread01 myThread01 = new MyThread01(); 10 myThread01.start(); 11 } 12 }
2、实现Runnable接口的方式
1 public class MyThread02 implements Runnable { 2 3 @Override 4 public void run() { 5 System.out.println("This is a thread implement Runnable:" 6 + Thread.currentThread().getName()); 7 } 8 9 public static void main(String[] args) { 10 Runnable myThread02 = new MyThread02(); 11 Thread thread = new Thread(myThread02); 12 thread.start(); 13 } 14 }
currentThread()方法
该方法可返回代码段正在被哪个线程所调用的信息。我们可以通过下面的方法,来熟悉这个方法的使用。
1 public class MyThread01 extends Thread { 2 3 public MyThread01() { 4 System.out.println("构造函数Current thread:" + Thread.currentThread().getName()); 5 } 6 7 @Override 8 public void run() { 9 System.out.println("run方法:" + Thread.currentThread().getName()); 10 } 11 12 public static void main(String[] args) { 13 // main线程调用 14 MyThread01 myThread01 = new MyThread01(); 15 // 独立线程,start方法才是启动一个线程 16 myThread01.start(); 17 // main线程调用 18 //myThread01.run(); 19 } 20 }
isAlive()方法
该方法用于判断当前的线程是否处于活动状态,活动状态就是指线程已经启动且尚未停止,处于运行或者准备开始的状态,都被认为是活动状态。
1 public class MyThread03 extends Thread { 2 3 @Override 4 public void run() { 5 System.out.println("run=" + this.isAlive()); 6 } 7 8 public static void main(String[] args) { 9 MyThread03 thread03 = new MyThread03(); 10 // false 11 System.out.println("begin == " + thread03.isAlive()); 12 // true 13 thread03.start(); 14 // true 15 System.out.println("end == " + thread03.isAlive()); 16 } 17 18 }
sleep()方法
该方法用于让当前线程休眠一段时间,不释放当前对象持有的锁,方法会抛出异常所以需要声明捕获。
getId()方法
该方法用于取得线程的唯一标识。
1 public class MyThread04{ 2 3 public static void main(String[] args) { 4 Thread thread = Thread.currentThread(); 5 try { 6 Thread.sleep(3000L); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.println(thread.getName() + " -- " + thread.getId()); 11 } 12 }
停止线程
停止一个线程可以使用Thread.stop()方法,但是最好不用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的(unsafe),而且是已被弃用作废的(deprecated)。如果强制使用stop()方法来停止一个线程,则可能使得一些清理性的工作得不到完成,另外一种情况就是对锁定的对象进行了“解锁”,致使程序流程错误,导致最终得不到同步的处理而出现数据不一致的问题,所以在后续的版本中将不可用或者不被支持。
大多数时候停止一个线程使用Thread.interrupt()方法,尽管方法的名称是“停止,中止”的意思,但是这个方法不会终止一个正在运行的线程,还需要加入一个判断才会完成线程的停止。
在Java中有以下3种方法可以终止正在运行的线程:
- 使用退出标志使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法,不推荐,因为这个方法可能会带来线程不可预料的结果。
- 使用interrupt方法中断线程。
interrupt()方法并不会马上终止一个线程,而是给线程设置上一个停止的标记。
判断线程是否是停止状态
- this.interrupted():判断当前线程是否已经终端,会撤销中断状态标记。
- this.isInterrupted():判断当前线程是否已经终端。
如果线程抛出了InterruptedException异常并被捕获,那么线程的中断标记也会被清除。
1 public class MyThread05 extends Thread { 2 3 @Override 4 public void run() { 5 super.run(); 6 try { 7 Thread.sleep(20000L); 8 } catch (InterruptedException e) { 9 // false == InterruptedException被catch之后,将会清除中断标记 10 System.out.println("Thread status:" + this.isInterrupted()); 11 e.printStackTrace(); 12 } 13 System.out.println("run start..."); 14 } 15 16 public static void main(String[] args) { 17 MyThread05 thread = new MyThread05(); 18 thread.start(); 19 thread.interrupt(); 20 //System.out.println(thread.isInterrupted()); 21 } 22 }
暂停线程
暂停线程意味着线程还可以恢复运行,我们可以使用suspend()方法来暂停线程,使用resume()方法来恢复线程的执行。
但这里有个坑,就是在持有锁的情况下访问公共资源时使用了suspend与resume方法,就会造成公共的同步对象锁被独占,使得其他的线程无法访问到公共的同步对象。
suspend与resume方法和stop方法一样暴力,执行的结果也容易出现因为线程的暂停而导致数据不同步。
yield()方法
yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间,但是放弃的时间不确定,有可能刚刚放弃又马上获得CPU时间。
线程的优先级
在操作系统中,线程可以划分优先级,高优先级的线程可以得到更多CPU执行的机会,设置线程优先级有助于帮助“线程规划器”确定下一个可以选择执行的线程,线程的优先级默认都是5。
1 public static void main(String[] args) { 2 Runnable myThread02 = new MyThread02(); 3 Thread thread = new Thread(myThread02); 4 thread.setPriority(Thread.MAX_PRIORITY); 5 thread.start(); 6 }
线程的优先级分为1~10级,如果在这个范围之外则抛异常,数值越大等级越高。其中Thread中内置了几个表示等级的常量:
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
线程的优先级具有传递性,比如A线程启动B线程,则B线程的优先级与A是一样的。
线程优先级只会影响线程获得CPU执行时间的可能性,但是不能保证高优先级的一定都比低优先级的先执行。
守护线程
在Java线程中有两种线程,一种是用户线程,一种是守护线程。
守护线程是一种特殊的线程,他的作用就是为其他非守护线程的运行提供便利服务,守护线程最典型的应用就是GC。当进程中不存在非守护线程时,则守护线程自动销毁。
1 public class MyThread07 extends Thread { 2 3 private int a = 0; 4 5 @Override 6 public void run() { 7 try { 8 while (true) { 9 System.out.println("Print:" + a++); 10 Thread.sleep(1000L); 11 } 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 17 public static void main(String[] args) { 18 try { 19 MyThread07 thread = new MyThread07(); 20 // 设置该线程为守护线程,必须启动线程之前设置 21 thread.setDaemon(true); 22 thread.start(); 23 24 Thread.sleep(5000L); 25 System.out.println("用户线程到此就执行结束了,守护线程也会退出。。"); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31 打印输出: 32 Print:0 33 Print:1 34 Print:2 35 Print:3 36 Print:4 37 用户线程到此就执行结束了。
参考资料
1、Java多线程编程核心技术 / 高洪岩著.—北京:机械工业出版社,2015.6