Java多线程编程(一)Java多线程技能
一、进程和多线程的概念以及线程的优点
打开Windo任务管理器可以看到很多正在运行着的exe程序,完全可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元。
线程可以理解成在进程中独立运行的子任务。比如,QQ.exe运行时就有很多的子任务在同时运行。
使用线程,可以最大限度地利用CPU的空闲时间来处理其他的任务,CPU在人物之间不停地切换,由于切换速度非常快,所以人们感觉这些任务似乎在同时运行。也就是说看,可以在同一时间内运行更多不同种类的任务,可以大幅增加CPU的利用率。
二、使用多线程
一个进程正在运行时至少会有一个线程在运行。
1 package test; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 System.out.println(Thread.currentThread().getName()); 7 } 8 9 }
输出为:main。这里输出的main其实就是一个名称叫做main的线程在执行main()方法中的代码。
1.继承Thread类
实现多线程编程的方式主要有两种:一种是继承Thread类,另一种是实现Runnable接口。从Thread类的结构来看,Thread类实现了Runnable接口,它们之间具有多台关系。为了支持多继承,完全可以实现Runnable接口的形式,一边实现一边继承。
1.1线程的调用的随机性
1 public class MyThread extends Thread { 2 @Override 3 public void run() { 4 super.run(); 5 System.out.println("MyThread"); 6 } 7 }
1 package test; 2 3 import com.mythread.www.MyThread; 4 5 public class Run { 6 7 public static void main(String[] args) { 8 MyThread mythread = new MyThread(); 9 mythread.start(); 10 System.out.println("运行结束"); 11 } 12 13 }
运行结束 MyThread
从输出结果可以看出,代码的运行结果与代码执行顺序或调用顺序是无关的,线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
1.2 演示线程的随机性
1 package mythread; 2 3 public class MyThread extends Thread { 4 @Override 5 public void run() { 6 try { 7 for (int i = 0; i < 10; i++) { 8 int time = (int) (Math.random() * 1000); 9 Thread.sleep(time); 10 System.out.println("run=" + Thread.currentThread().getName()); 11 } 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 }
1 package test; 2 3 import mythread.MyThread; 4 5 public class Test { 6 public static void main(String[] args) { 7 try { 8 9 MyThread thread = new MyThread(); 10 thread.setName("myThread"); 11 thread.start(); 12 13 for (int i = 0; i < 10; i++) { 14 int time = (int) (Math.random() * 1000); 15 Thread.sleep(time); 16 System.out.println("main=" + Thread.currentThread().getName()); 17 } 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 }
main=main main=main run=myThread main=main run=myThread run=myThread main=main main=main run=myThread main=main main=main run=myThread main=main main=main run=myThread run=myThread run=myThread main=main run=myThread run=myThread
从输出的20个结果可以看出,Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用thread.run()方法就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码。
1.3 start()方法的顺序并不代表线程启动的顺序
1 package extthread; 2 3 public class MyThread extends Thread { 4 5 private int i; 6 7 public MyThread(int i) { 8 super(); 9 this.i = i; 10 } 11 12 @Override 13 public void run() { 14 System.out.println(i); 15 } 16 17 }
1 package test; 2 3 import extthread.MyThread; 4 5 public class Test { 6 7 public static void main(String[] args) { 8 MyThread t11 = new MyThread(1); 9 MyThread t12 = new MyThread(2); 10 MyThread t13 = new MyThread(3); 11 MyThread t14 = new MyThread(4); 12 MyThread t15 = new MyThread(5); 13 MyThread t16 = new MyThread(6); 14 MyThread t17 = new MyThread(7); 15 MyThread t18 = new MyThread(8); 16 MyThread t19 = new MyThread(9); 17 MyThread t110 = new MyThread(10); 18 MyThread t111 = new MyThread(11); 19 MyThread t112 = new MyThread(12); 20 MyThread t113 = new MyThread(13); 21 22 t11.start(); 23 t12.start(); 24 t13.start(); 25 t14.start(); 26 t15.start(); 27 t16.start(); 28 t17.start(); 29 t18.start(); 30 t19.start(); 31 t110.start(); 32 t111.start(); 33 t112.start(); 34 t113.start(); 35 36 } 37 38 }
2 3 1 4 5 7 6 8 10 9 11 12 13
从输出结果可以看出,start()方法的顺序并不代表线程启动的顺序。
2.实现Runnable接口
如果想要创建的线程类已经有一个父类了,这时就不能再继承自Thread类了,因为Java不支持多继承,所以就需要实现Runnable接口。
1 package myrunnable; 2 3 public class MyRunnable implements Runnable { 4 @Override 5 public void run() { 6 System.out.println("myrunnable类中的run!"); 7 } 8 }
1 package test; 2 3 import myrunnable.MyRunnable; 4 5 public class Run { 6 7 public static void main(String[] args) { 8 Runnable runnable=new MyRunnable(); 9 Thread thread=new Thread(runnable); 10 thread.start(); 11 System.out.println("run中的main!"); 12 } 13 14 }
run中的main! myrunnable类中的run!
Thread.java类的8个构造函数中,有两个可以传递Runnable接口的对象,并且由于Thread类也实现了Runnable接口,所以也就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象, 还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用。
3.实例变量与线程安全
自定义线程类中的实例变量针对其他线程可以有共享与不共享之分。
3.1 不共享数据
1 package mythread; 2 3 public class MyThread extends Thread { 4 5 private int count = 5; 6 7 public MyThread(String name) { 8 super(); 9 this.setName(name); 10 } 11 12 @Override 13 public void run() { 14 super.run(); 15 while (count > 0) { 16 count--; 17 System.out.println("由" + this.currentThread().getName() + " 计算, count=" + count); 18 } 19 } 20 }
1 package test; 2 3 import mythread.MyThread; 4 5 public class Run { 6 public static void main(String[] args) { 7 MyThread a = new MyThread("A"); 8 MyThread b = new MyThread("B"); 9 MyThread c = new MyThread("C"); 10 a.start(); 11 b.start(); 12 c.start(); 13 } 14 }
由B 计算, count=4 由C 计算, count=4 由A 计算, count=4 由A 计算, count=3 由C 计算, count=3 由B 计算, count=3 由C 计算, count=2 由A 计算, count=2 由C 计算, count=1 由C 计算, count=0 由B 计算, count=2 由B 计算, count=1 由B 计算, count=0 由A 计算, count=1 由A 计算, count=0
一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享。
3.2 共享数据
共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理同一个人的票数。
3.2.1“非线程安全”的问题
1 package mythread; 2 3 public class MyThread extends Thread { 4 5 private int count=5; 6 7 @Override 8 public void run() { 9 super.run(); 10 count--; 11 System.out.println("由"+this.currentThread().getName()+"计算,count="+count); 12 } 13 }
1 package test; 2 3 import mythread.MyThread; 4 5 public class Run { 6 public static void main(String[] args) { 7 MyThread mythread=new MyThread(); 8 9 Thread a=new Thread(mythread,"A"); 10 Thread b=new Thread(mythread,"B"); 11 Thread c=new Thread(mythread,"C"); 12 Thread d=new Thread(mythread,"D"); 13 Thread e=new Thread(mythread,"E"); 14 a.start(); 15 b.start(); 16 c.start(); 17 d.start(); 18 e.start(); 19 } 20 }
由B计算,count=3 由A计算,count=3 由C计算,count=2 由D计算,count=1 由E计算,count=0
从输出结果可以看出,线程B和Acount的值都是3,说明A和B同时对count进行处理,产生了“非线程安全”问题。
3.2.2解决“非线程安全”的问题
将MyThread类修改一下即可。
1 package mythread; 2 3 public class MyThread extends Thread { 4 5 private int count=5; 6 7 @Override 8 synchronized public void run() { 9 super.run(); 10 count--; 11 System.out.println("由"+this.currentThread().getName()+"计算,count="+count); 12 } 13 }
由A计算,count=4 由B计算,count=3 由E计算,count=2 由D计算,count=1 由C计算,count=0
通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果。
synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。当一个线程想要执行同步方法里面的代码时,线程首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就可以执行synchronized里面的代码。如果不能拿到这把锁,那么这个线程就会不断地尝试拿这把锁,直到能拿到为止,而且是多个线程同时去争抢这把锁。
3.2.3另一个解决“非线程安全”的示例
1 package controller; 2 3 //生成一个servlet组件 4 public class LoginServlet { 5 6 private static String usernameRef; 7 private static String passwordRef; 8 9 public static void doPost(String username, String password) { 10 try { 11 usernameRef = username; 12 if (username.equals("a")) { 13 Thread.sleep(5000); 14 } 15 passwordRef = password; 16 17 System.out.println("username=" + usernameRef + " password="+ passwordRef); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 }
1 package extthread; 2 3 import controller.LoginServlet; 4 5 public class ALogin extends Thread { 6 @Override 7 public void run() { 8 LoginServlet.doPost("a", "aa"); 9 } 10 }
1 package extthread; 2 3 import controller.LoginServlet; 4 5 public class BLogin extends Thread { 6 @Override 7 public void run() { 8 LoginServlet.doPost("b", "bb"); 9 } 10 }
1 package test; 2 3 import extthread.ALogin; 4 import extthread.BLogin; 5 6 public class Run { 7 8 public static void main(String[] args) { 9 ALogin a = new ALogin(); 10 a.start(); 11 BLogin b = new BLogin(); 12 b.start(); 13 } 14 15 }
username=b password=bb username=b password=aa
由于没有使用synchronized关键字,从而产生了“非线程安全”的问题。即在另一个线程没有等待sleep(5000)这个过程就执行了。修改代码使用synchronized关键字就可以解决这个问题。
1 package controller; 2 3 //生成一个servlet组件 4 public class LoginServlet { 5 6 private static String usernameRef; 7 private static String passwordRef; 8 9 synchronized public static void doPost(String username, String password) { 10 try { 11 usernameRef = username; 12 if (username.equals("a")) { 13 Thread.sleep(5000); 14 } 15 passwordRef = password; 16 17 System.out.println("username=" + usernameRef + " password="+ passwordRef); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 }
username=a password=aa username=b password=bb
4.留意i--与System.out.println()的异常
println()方法与i++联合使用时“有可能”出现的另外一种异常情况。
1 package extthread; 2 3 public class MyThread extends Thread { 4 5 private int i = 5; 6 7 @Override 8 public void run() { 9 System.out.println("i=" + (i--) + " threadName="+ Thread.currentThread().getName()); 10 } 11 12 }
1 package test; 2 3 import extthread.MyThread; 4 5 public class Run { 6 7 public static void main(String[] args) { 8 9 MyThread run = new MyThread(); 10 11 Thread t1 = new Thread(run); 12 Thread t2 = new Thread(run); 13 Thread t3 = new Thread(run); 14 Thread t4 = new Thread(run); 15 Thread t5 = new Thread(run); 16 17 t1.start(); 18 t2.start(); 19 t3.start(); 20 t4.start(); 21 t5.start(); 22 23 } 24 25 }
i=5 threadName=Thread-4 i=5 threadName=Thread-1 i=4 threadName=Thread-3 i=3 threadName=Thread-5 i=2 threadName=Thread-2
需要说明的是,虽然println()方法在内部是同步的(有synchronized关键字),但是i--的操作却是在进入println()之间发生的,所以有发生“非线程安全”问题的概率。所以还是应该使用synchronized关键字。
1 package extthread; 2 3 public class MyThread extends Thread { 4 5 private int i = 5; 6 7 @Override 8 synchronized public void run() { 9 System.out.println("i=" + (i--) + " threadName="+ Thread.currentThread().getName()); 10 } 11 12 }
i=5 threadName=Thread-1 i=4 threadName=Thread-2 i=3 threadName=Thread-3 i=2 threadName=Thread-5 i=1 threadName=Thread-4
三、currentThread()方法
currentThread()方法可返回代码段正在被哪个线程调用的信息。
示例1:结果说明main方法被名为main的线程调用。
1 package run; 2 3 public class Run1 { 4 public static void main(String[] args) { 5 System.out.println(Thread.currentThread().getName()); 6 } 7 }
main
示例2:MyThread.java类的构造函数是被main线程调用的,而run方法时被名称为Thread-0的线程调用的,run方法是自动调用的方法。
1 package mythread; 2 3 public class MyThread extends Thread { 4 5 public MyThread() { 6 System.out.println("构造方法的打印:" + Thread.currentThread().getName()); 7 } 8 9 @Override 10 public void run() { 11 System.out.println("run方法的打印:" + Thread.currentThread().getName()); 12 } 13 14 }
1 package run; 2 3 import mythread.MyThread; 4 5 public class Run2 { 6 public static void main(String[] args) { 7 MyThread mythread = new MyThread(); 8 mythread.start(); 9 //mythread.run(); 10 } 11 }
构造方法的打印:main run方法的打印:Thread-0
示例3:修改示例2中的Run2.java。从结果中我们可以看出MyThread.java类的构造函数和run方法都是被main线程调用的,这里也就是说明了同步和异步的区别。
package run; import mythread.MyThread; public class Run2 { public static void main(String[] args) { MyThread mythread = new MyThread(); //mythread.start(); mythread.run(); } }
构造方法的打印:main run方法的打印:main
示例4:输出显示了this.getname()方法和currentThread().getname()的区别。
1 package mythread; 2 3 public class CountOperate extends Thread { 4 5 public CountOperate() { 6 System.out.println("CountOperate---begin"); 7 System.out.println("Thread.currentThread().getName()="+ Thread.currentThread().getName()); 8 System.out.println("this.getName()=" + this.getName()); 9 System.out.println(Thread.currentThread() == this); 10 System.out.println("CountOperate---end"); 11 } 12 13 @Override 14 public void run() { 15 System.out.println("run---begin"); 16 System.out.println("Thread.currentThread().getName()="+ Thread.currentThread().getName()); 17 System.out.println("this.getName()=" + this.getName()); 18 System.out.println(Thread.currentThread() == this); 19 System.out.println("run---end"); 20 } 21 22 }
package test; import mythread.CountOperate; public class Run { public static void main(String[] args) { CountOperate c = new CountOperate(); Thread t1 = new Thread(c); t1.setName("test"); t1.start(); } }
CountOperate---begin Thread.currentThread().getName()=main this.getName()=Thread-0 false CountOperate---end run---begin Thread.currentThread().getName()=test this.getName()=Thread-0 false run---end
首先分析构造函数,根据输出可以看出,调用构造函数的是main线程,而此时还没有启动CountOperate子线程,所以打印出this.getName()=Thread-0,并且此时this代表的是CountOperate对象的实例,因此会输出false。
然后分析run方法,从输出上来看,很让人疑惑的是run中的this.getName()=Thread-0这条输出。
通过查看Thread类的源码,可以发现
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
在Thread类的构造方法中,默认地会通过this.name=name给name赋值。也就是说,在Thread源码中实际上new Thread(c)会将c的应用对象绑定到一个private变量target上,在t1被执行的时候,即t1.run被调用的时候,它会调用target.run方法,也就是说它是直接调用c对象的run方法,也就是说,在run方法被执行的时候,this.getName()实际上返回的是target.getName()方法,而Thread.currentThread().getName()实际上是t1.getName()。
示例5:修改示例4中的main方法
package test; import mythread.CountOperate; public class Run { public static void main(String[] args) { CountOperate c = new CountOperate(); c.setName("test"); c.start(); } }
CountOperate---begin Thread.currentThread().getName()=main this.getName()=Thread-0 false CountOperate---end run---begin Thread.currentThread().getName()=test this.getName()=test true run---end
从输出结果中也可以看出,此时CountOperate构造函数和之前的输出保持一致,因为执行构造方法时线程还没有执行,所以this.getName()=Thread-0。而run方法中CountOperate线程已经执行并且可以获取到线程的名称,所以输出this.getName()=test。这也就是自动调用和被动调用的区别。
四、isAlive()方法
isAlive()方法用于判断当前的线程是否处于活动状态。
示例1:end ==true说明mythread线程还没有执行完毕
package mythread; public class MyThread extends Thread { @Override public void run() { System.out.println("run=" + this.isAlive()); } }
package run; import mythread.MyThread; public class Run { public static void main(String[] args) { MyThread mythread = new MyThread(); System.out.println("begin ==" + mythread.isAlive()); mythread.start(); System.out.println("end ==" + mythread.isAlive()); } }
begin ==false end ==true run=true
示例2:修改示例1程序。end ==false说明mythread对象已经在1秒之内执行完毕。
package run; import mythread.MyThread; public class Run { public static void main(String[] args) throws InterruptedException{ MyThread mythread = new MyThread(); System.out.println("begin ==" + mythread.isAlive()); mythread.start(); Thread.sleep(1000); System.out.println("end ==" + mythread.isAlive()); } }
begin ==false run=true end ==false
示例3:将线程对象以构造参数的方式传递给Thread对象进行start()启动时,运行的结果会有差异。造成这种结果的原因和之前遇到的情况一样,也就是Thread.currentThread()和this的区别。
package mythread; public class CountOperate extends Thread { public CountOperate() { System.out.println("CountOperate---begin"); System.out.println("Thread.currentThread().getName()="+ Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()="+ Thread.currentThread().isAlive()); System.out.println("this.getName()=" + this.getName()); System.out.println("this.isAlive()=" + this.isAlive()); System.out.println("CountOperate---end"); } @Override public void run() { System.out.println("run---begin"); System.out.println("Thread.currentThread().getName()="+ Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()="+ Thread.currentThread().isAlive()); System.out.println("this.getName()=" + this.getName()); System.out.println("this.isAlive()=" + this.isAlive()); System.out.println("run---end"); } }
package test; import mythread.CountOperate; public class Run { public static void main(String[] args) { CountOperate c = new CountOperate(); Thread t1 = new Thread(c); System.out.println("main begin t1 isAlive=" + t1.isAlive()); t1.setName("test"); t1.start(); System.out.println("main end t1 isAlive=" + t1.isAlive()); } }
CountOperate---begin Thread.currentThread().getName()=main Thread.currentThread().isAlive()=true this.getName()=Thread-0 this.isAlive()=false CountOperate---end main begin t1 isAlive=false main end t1 isAlive=true run---begin Thread.currentThread().getName()=test Thread.currentThread().isAlive()=true this.getName()=Thread-0 this.isAlive()=false run---end
五、sleep()方法
sleep()方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是只this.currentThread()返回的线程。
示例1:直接使用mythread.run()方法同步启动线程,这就使得通过main线程来执行MyThread1的线程,通过输出可以发现线程休眠了2秒。
package mythread; public class MyThread1 extends Thread { @Override public void run() { try { System.out.println("run threadName="+ this.currentThread().getName() + " begin"); Thread.sleep(2000); System.out.println("run threadName="+ this.currentThread().getName() + " end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
package run; import mythread.MyThread1; public class Run1 { public static void main(String[] args) { MyThread1 mythread = new MyThread1(); System.out.println("begin =" + System.currentTimeMillis()); mythread.run(); System.out.println("end =" + System.currentTimeMillis()); } }
begin =1525254700337 run threadName=main begin run threadName=main end end =1525254702337
示例2:在main方法中使用mythread.start()方法后,main线程和MyThread2线程异步执行,所以首先打印信息为begin和end。而MyThread2线程是随后运行的,在最后两行打印run begin和run end。
package mythread; public class MyThread2 extends Thread { @Override public void run() { try { System.out.println("run threadName="+ this.currentThread().getName() + " begin ="+ System.currentTimeMillis()); Thread.sleep(2000); System.out.println("run threadName="+ this.currentThread().getName() + " end ="+ System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }
package run; import mythread.MyThread2; public class Run2 { public static void main(String[] args) { MyThread2 mythread = new MyThread2(); System.out.println("begin =" + System.currentTimeMillis()); mythread.start(); System.out.println("end =" + System.currentTimeMillis()); } }
begin =1525254898564 end =1525254898564 run threadName=Thread-0 begin =1525254898564 run threadName=Thread-0 end =1525254900564
六、getId()方法
getId()方法的所用是取得线程的唯一标识。从打印结果看,当前执行代码的线程名称为main,线程id为1。
public class Run1 { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getId()); } }
main 1
七、停止线程
在Java中有一下3中方法可以终止正在运行得线程:
①使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
②使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend即resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。
③使用interrupt方法中断线程。
1.停止不了的线程
示例:通过调用interrupt()方法来停止线程,但interrupt()方法的使用效果并不像for+break语句那样,马上就停止循环。调用interrupt()方法仅仅是在当前线程打了一个停止的标记,并不是真的停止线程。从运行的结果也可以看出,调用interrupt方法并没有停止线程。
package exthread; public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { System.out.println("i=" + (i + 1)); } } }
package test; 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(); } } }
... i=499991 i=499992 i=499993 i=499994 i=499995 i=499996 i=499997 i=499998 i=499999 i=500000
2.判断线程是否是停止状态
判断线程的状态是不是停止的,Thread.java类里提供了两种方法:①this.interrupted()测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程②this.isInterrupted()测试线程是否已经中断
示例1:this.interrupted()使用。从打印情况来看,线程并未停止,这也就证明了interrupted()方法是测试当前线程是否已经中断,这个“当前线程”是main,它从未中断过,所以打印的结果是两个false。
package exthread; public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { System.out.println("i=" + (i + 1)); } } }
package test; import exthread.MyThread; import exthread.MyThread; 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()); System.out.println("是否停止2?="+thread.interrupted()); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } }
i=125615 i=125616 i=125617 是否停止1?=false i=125618 i=125619 i=125620 i=125621 i=125622 是否停止2?=false end! i=125623 i=125624 i=125625
示例2:修改示例1中的run方法。Thread.currentThread().interrupt();确实使main线程产生了中断效果。但是令人困惑的是,是否停止2?=false这是因为,如果连续两次调用interrupt()方法,则第一次调用已经清楚了其中断状态后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外,其他情况下都会返回false。
package test; import exthread.MyThread; import exthread.MyThread; public class Run2 { public static void main(String[] args) { Thread.currentThread().interrupt(); System.out.println("是否停止1?=" + Thread.interrupted()); System.out.println("是否停止2?=" + Thread.interrupted()); System.out.println("end!"); } }
是否停止1?=true 是否停止2?=false end!
示例3:isInterrupted()方法并未清除状态标志,所以打印了两个true,和前面interrupted()方法的区别就是是否清除标志。
package test; import exthread.MyThread; import exthread.MyThread; public class Run3 { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); System.out.println("是否停止1?="+thread.isInterrupted()); System.out.println("是否停止2?="+thread.isInterrupted()); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } }
i=143424 i=143425 是否停止1?=true i=143426 i=143427 i=143428 i=143429 i=143430 i=143431 i=143432 i=143433 i=143434 i=143435 i=143436 i=143437 i=143438 i=143439 i=143440 i=143441 i=143442 i=143443 i=143444 i=143445 i=143446 i=143447 i=143448 i=143449 i=143450 i=143451 i=143452 是否停止2?=true i=143453
3.能停止的线程--异常法
可以在for语句来判断一下线程是否是停止状态,如果是停止状态,则后面的代码不再运行即可。
示例1:虽然停止了线程,但是如果for语句下面还有语句,还是会继续运行的。
package exthread; 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)); } } }
package test; 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!"); } }
i=252895 i=252896 i=252897 i=252898 end! 已经是停止状态了!
示例2:验证如果for语句下面还有语句时,还是会继续运行的。
package exthread; 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("如果此代码是for又继续运行,线程并未停止!"); } }
package test; 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!"); } }
i=251852 i=251853 i=251854 已经是停止状态了! 如果此代码是for又继续运行,线程并未停止! end!
示例3:解决语句继续运行的问题,通过异常语句来停止线程。
package exthread; 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("进run方法中的catch了"); e.printStackTrace(); } } }
i=238006 i=238007 end! 已经是停止状态了! 进run方法中的catch了 java.lang.InterruptedException at exthread.MyThread.run(MyThread.java:11)
4.在沉睡中停止
示例1:在sleep()状态下停止线程(先sleep然后再interrupt()),会进入catch语句,并且清除停止状态值,使之变成false。
package exthread; public 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(); } } }
package test; import exthread.MyThread; import exthread.MyThread; public class Run { 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!"); } }
run begin end! 在沉睡中被停止!进入了catch!false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at exthread.MyThread.run(MyThread.java:9)
示例2:先执行interrupt停止线程,因此输出end!然后for循环执行完成,然后进入sleep,产生异常,进入catch。
package exthread; public class MyThread extends Thread { @Override public void run() { super.run(); try { for(int i=0;i<100000;i++){ System.out.println("i="+(i+1)); } System.out.println("run begin"); Thread.sleep(200000); System.out.println("run end"); } catch (InterruptedException e) { System.out.println("先执行interrupt停止线程,再遇到sleep!进入catch!"); e.printStackTrace(); } } }
package test; import exthread.MyThread; import exthread.MyThread; public class Run { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); thread.interrupt(); System.out.println("end!"); } }
end! i=1 i=2 i=3 i=4 i=5 i=6 i=7 ... i=99998 i=99999 i=100000 run begin 先执行interrupt停止线程,再遇到sleep!进入catch! java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at exthread.MyThread.run(MyThread.java:12)
5.能停止的线程--暴力停止
示例:使用stop()方法停止线程则是非常暴力的。从输出可以看出,main线程停止了8秒然后被stop掉了,然后子线程每隔一秒执行输出一个数字。
package testpackage; public class MyThread extends Thread { private int i = 0; @Override public void run() { try { while (true) { i++; System.out.println("i=" + i); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
package test.run; import testpackage.MyThread; public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(8000); thread.stop(); } catch (InterruptedException e) { e.printStackTrace(); } } }
i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8
6.方法stop()与java.lang.ThreadDeath异常
示例:调用stop()方法会抛出java.lang.ThreadDeath异常,但在通常情况下,次异常不需要显示地捕捉。不过需要注意的是,stop()方法已经被作废,因为如果强制让线程停止则有可能使一些请理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。
package testpackage; public class MyThread extends Thread { @Override public void run() { try { this.stop(); } catch (ThreadDeath e) { System.out.println("进入run方法中的catch了"); e.printStackTrace(); } } }
package test.run; import testpackage.MyThread; public class Run { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
进入run方法中的catch了 java.lang.ThreadDeath at java.lang.Thread.stop(Unknown Source) at testpackage.MyThread.run(MyThread.java:7)
7.释放锁的不良后果
示例:使用stop()释放锁将会给数据造成不一致的结果。如果出现这样的情况,程序处理的数据就有可能遭到破坏,最终导致程序执行的流程错误。这里的意思就是还没来得及给password赋值就把线程stop掉了。由于stop()方法已经作废了,所以不建议使用。
package testpackage; public class SynchronizedObject { private String username = "a"; private String password = "aa"; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } synchronized public void printString(String username, String password) { try { this.username = username; Thread.sleep(100000); this.password = password; } catch (InterruptedException e) { e.printStackTrace(); } } }
package testpackage; public class MyThread extends Thread { private SynchronizedObject object; public MyThread(SynchronizedObject object) { super(); this.object = object; } @Override public void run() { object.printString("b", "bb"); } }
package test.run; import testpackage.MyThread; import testpackage.SynchronizedObject; public class Run { public static void main(String[] args) { try { SynchronizedObject object = new SynchronizedObject(); MyThread thread = new MyThread(object); thread.start(); Thread.sleep(500); thread.stop(); System.out.println(object.getUsername() + " "+ object.getPassword()); } catch (InterruptedException e) { e.printStackTrace(); } } }
b aa
8.使用return停止线程
示例:将方法interrupt()与return结合使用也能实现停止线程的效果。线程运行了2秒才停止。不过还是建议用“抛异常”的方法来实现线程的停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播。
package extthread; public class MyThread extends Thread { @Override public void run() { while (true) { if (this.isInterrupted()) { System.out.println("ֹͣ停止了!"); return; } System.out.println("timer=" + System.currentTimeMillis()); } } }
package test.run; import extthread.MyThread; public class Run { public static void main(String[] args) throws InterruptedException { MyThread t=new MyThread(); t.start(); Thread.sleep(2000); t.interrupt(); } }
timer=1525267712005
timer=1525267712006
timer=1525267712006
timer=1525267712006
timer=1525267712006
...
timer=1525267714005
timer=1525267714005
timer=1525267714005
timer=1525267714005
timer=1525267714005
ֹͣ停止了!
八、暂停线程
暂停线程意味着次线程还可以恢复运行。在Java中,使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。
1.suspend与resume方法的使用
示例:通过输出我们可以看出,四行输出的时间间隔都是5秒,并且暂停和恢复控制线程中的run方法中的getI()方法的暂停和执行。
package mythread; public class MyThread extends Thread { private long i = 0; public long getI() { return i; } public void setI(long i) { this.i = i; } @Override public void run() { while (true) { i++; } } }
package test.run; import mythread.MyThread; public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(5000); // A thread.suspend(); System.out.println("A= " + System.currentTimeMillis() + " i="+ thread.getI()); Thread.sleep(5000); System.out.println("A= " + System.currentTimeMillis() + " i="+ thread.getI()); // B thread.resume(); Thread.sleep(5000); // C thread.suspend(); System.out.println("B= " + System.currentTimeMillis() + " i="+ thread.getI()); Thread.sleep(5000); System.out.println("B= " + System.currentTimeMillis() + " i="+ thread.getI()); } catch (InterruptedException e) { e.printStackTrace(); } } }
A= 1525268875079 i=2770802031 A= 1525268880079 i=2770802031 B= 1525268885079 i=5533965062 B= 1525268890079 i=5533965062
2.suspend与resume方法的缺点--独占
示例1:在使用suspend与resume方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。因为线程a被锁定,所以线程的方法也被锁定,其他线程就无法访问这个方法。即独占并锁死了printString方法。
package testpackage; public 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"); } }
package test.run; import testpackage.SynchronizedObject; public class Run { 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方法"); System.out.println("因为printString方法被a线程锁定并且永远被suspend了"); object.printString(); System.out.println("你猜我会不会执行"); } }; thread2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
begin a线程永远suspend了 thread2启动了,但进入不了printString方法 因为printString方法被a线程锁定并且永远被suspend了
示例2:注意这里的main end!可以正常打印出来。将和之后的示例3作对比。
package mythread; public class MyThread extends Thread { private long i = 0; @Override public void run() { while (true) { i++; //System.out.println(i); } } }
package test.run; import mythread.MyThread; public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(1000); thread.suspend(); System.out.println("main end!"); } catch (InterruptedException e) { e.printStackTrace(); } } }
main end!
示例3:修改示例2中的MyThread。控制台将不再打印main end!,这是因为当程序运行到println()方法内部停止时,同步锁未被释放。下面看一下println()方法的源码
public void println(long x) { synchronized (this) { print(x); newLine(); } }
这导致当前PringStream的println()方法一直呈“暂停”状态,并且“锁未释放”,而main()方法当中的代码 System.out.println("main end!"); 迟迟不能执行打印!也就是线程中使用了println()方法然后被暂停,由于println()方法需要同步,即两个println()方法不能分别执行,所以就造成了第一个println()方法再暂停的情况下第二个不能执行的情况。
package mythread; public class MyThread extends Thread { private long i = 0; @Override public void run() { while (true) { i++; System.out.println(i); } } }
...121036 121037 121038 121039 121040 121041
3.suspend与resume方法的缺点--不同步
示例:和前面的stop()方法使得数据不同步的情况一样,使用suspend与resume方法也会造成数据不同步的情况。线程a被暂停了导致password字段的值不能被更新,所以线程2在执行的时候保留默认值。
package myobject; public class MyObject { private String username = "1"; private String password = "11"; public void setValue(String u, String p) { this.username = u; if (Thread.currentThread().getName().equals("a")) { System.out.println("ֹͣ停止a线程"); Thread.currentThread().suspend(); } this.password = p; } public void printUsernamePassword() { System.out.println(username + " " + password); } }
package test; import myobject.MyObject; public class Run { public static void main(String[] args) throws InterruptedException { final MyObject myobject = new MyObject(); Thread thread1 = new Thread() { public void run() { myobject.setValue("a", "aa"); }; }; thread1.setName("a"); thread1.start(); Thread.sleep(500); Thread thread2 = new Thread() { public void run() { myobject.printUsernamePassword(); }; }; thread2.start(); } }
ֹͣ停止a线程 a 11
九、yield()方法
yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。
示例1:不添加yield()方法的情况。即线程独占CPU时间片来执行线程。用时21毫秒!
package test; import extthread.MyThread; import extthread.MyThread; public class Run { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
package extthread; public class MyThread extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { //Thread.yield(); count = count + (i + 1); } long endTime = System.currentTimeMillis(); System.out.println("用时" + (endTime - beginTime) + "毫秒!"); } }
用时21毫秒!
示例2:添加yield()方法的情况。将CPU时间片让给其他资源导致速度变慢很多。用时13017毫秒!
package extthread; public class MyThread extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { //Thread.yield(); count = count + (i + 1); } long endTime = System.currentTimeMillis(); System.out.println("用时" + (endTime - beginTime) + "毫秒!"); } }
用时13017毫秒!
十、线程的优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级高的线程对象中的任务。设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
Java中的线程优先级分为1~10这10个等级,使用MIN_PRIORITY/NORM_PRIORITY/MAX_PRIORITY三个常量来预置优先级的值。设置线程的优先级使用setPriority()方法,源码如下
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
1.线程优先级的继承特性
在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
示例1:优先级被继承,线程1启动线程2,两个线程的优先级是一样的,同时和mian线程也是一样的优先级。
package extthread; public class MyThread1 extends Thread { @Override public void run() { System.out.println("MyThread1 run priority=" + this.getPriority()); MyThread2 thread2 = new MyThread2(); thread2.start(); } }
package extthread; public class MyThread2 extends Thread { @Override public void run() { System.out.println("MyThread2 run priority=" + this.getPriority()); } }
package test; import extthread.MyThread1; public class Run { 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(); } }
main thread begin priority=5 main thread end priority=5 MyThread1 run priority=5 MyThread2 run priority=5
示例2:修改main方法。修改后main线程的优先级比原始的5高,通过main线程启动1线程,然后通过1线程启动2线程,优先级都是一样的。
package test; import extthread.MyThread1; public class Run { 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(); } }
main thread begin priority=5 main thread end priority=6 MyThread1 run priority=6 MyThread2 run priority=6
2.优先级具有规则性
示例:高优先级的线程总是大部分先执行完,但并不代表高优先级的线程全部先执行完。另外,并不是MyThread先被main线程调用就会先执行完,当优先级等级差距很大时,谁先执行完和代码的调用顺序无关。即线程的优先级与代码执行顺序无关。
package extthread; import java.util.Random; public class MyThread1 extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); long addResult = 0; for (int j = 0; j < 10; j++) { for (int i = 0; i < 50000; i++) { Random random = new Random(); random.nextInt(); addResult = addResult + i; } } long endTime = System.currentTimeMillis(); System.out.println("******线程1 use time=" + (endTime - beginTime)); } }
package extthread; import java.util.Random; public class MyThread2 extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); long addResult = 0; for (int j = 0; j < 10; j++) { for (int i = 0; i < 50000; i++) { Random random = new Random(); random.nextInt(); addResult = addResult + i; } } long endTime = System.currentTimeMillis(); System.out.println("%%%%%%线程2 use time=" + (endTime - beginTime)); } }
package test; import extthread.MyThread1; import extthread.MyThread2; public class Run { public static void main(String[] args) { for (int i = 0; i < 5; i++) { MyThread1 thread1 = new MyThread1(); thread1.setPriority(1); thread1.start(); MyThread2 thread2 = new MyThread2(); thread2.setPriority(10); thread2.start(); } } }
%%%%%%线程2 use time=239 %%%%%%线程2 use time=256 %%%%%%线程2 use time=311 %%%%%%线程2 use time=312 %%%%%%线程2 use time=319 ******线程1 use time=173 ******线程1 use time=211 ******线程1 use time=212 ******线程1 use time=180 ******线程1 use time=477
3.优先级具有随机性
优先级较高的线程不一定每一次都先执行完。
示例:修改2中示例中的main方法,修改优先级为5和6,从结果可以看出,不要把线程的优先级与运行结果的顺序作为衡量的标准,优先级较高的线程不一定每一次都先执行完run()方法中的任务,也就是说,线程优先级与打印顺序无关,不要讲这两者的关系相关联,它们的关系具有不确定性和随机性。
package extthread; import java.util.Random; public class MyThread1 extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Random random = new Random(); random.nextInt(); } long endTime = System.currentTimeMillis(); System.out.println("**********1 use time=" + (endTime - beginTime)); } }
%%%%%%%%%%2 use time=2 **********1 use time=6 **********1 use time=5 %%%%%%%%%%2 use time=0 **********1 use time=0 **********1 use time=1 %%%%%%%%%%2 use time=1 %%%%%%%%%%2 use time=0 **********1 use time=0 %%%%%%%%%%2 use time=1
4.看谁运行得快
示例:从输出结果可以看出,优先级高的线程运行得快,b的优先级为8,a的优先级为2,b运行得快得多,这里要注意的是,int类型的数一直加超过范围后会变成负数!!!
package extthread; public class ThreadA extends Thread { private int count = 0; public int getCount() { return count; } @Override public void run() { while (true) { count++; } } }
package extthread; public class ThreadB extends Thread { private int count = 0; public int getCount() { return count; } @Override public void run() { while (true) { count++; } } }
package test; import extthread.ThreadA; import extthread.ThreadB; public class Run { public static void main(String[] args) { try { ThreadA a = new ThreadA(); a.setPriority(Thread.NORM_PRIORITY - 3); a.start(); ThreadB b = new ThreadB(); b.setPriority(Thread.NORM_PRIORITY + 3); b.start(); Thread.sleep(20000); a.stop(); b.stop(); System.out.println("a=" + a.getCount()); System.out.println("b=" + b.getCount()); } catch (InterruptedException e) { e.printStackTrace(); } }
a=1197537357 b=-1459844607
十一、守护线程
在Java线程中有两种线程,一种是用户线程,另一种是守护线程。守护线程就是当进程中不存在非守护线程了,则守护线程自动销毁。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),它就是一个很称职的守护者。
示例:main线程休眠5秒后,最后一个非守护线程结束,守护线程也一同结束。
package testpackage; public class MyThread extends Thread { private int i = 0; @Override public void run() { try { while (true) { i++; System.out.println("i=" + (i)); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
package test.run; import testpackage.MyThread; public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.setDaemon(true); thread.start(); Thread.sleep(5000); System.out.println("我离开thread对象也不再打印了,也就是停止了!"); } catch (InterruptedException e) { e.printStackTrace(); } } }
i=1 i=2 i=3 i=4 i=5 我离开thread对象也不再打印了,也就是停止了!