进程与线程
进程是操作系统结构的基础,是一次程序的执行;简单理解,进程就是一个window操作系统中运行的exe程序。
线程可以理解为进程中独立运行的子任务。例如,QQ.exe程序运行时,可以有很多的子任务同时运行,下载文件线程、文字聊天线程、好友视频线程等。
多线程的优点
同时处理多个任务可以最大限度地利用CPU资源,减少等待时间。
注意:多线程是异步的,线程被调用的时机是随机的。
线程状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
实现多线程编程的方式
1继承Thread类(Thread类实现了Runnable接口)
1 public class MyThread extends Thread { 2 @Override 3 public void run() { 4 super.run(); 5 System.out.println("MyThread"); 6 } 7 }
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread myThread = new MyThread(); 4 myThread.start(); 5 System.out.println("end"); 6 } 7 }
运行结果:
end
MyThread
上例可看出,多线程技术中代码的运行结果与代码的执行顺序或调用顺序无关。
那么问题来了,对同一个线程,多次调用start()方法,为出现什么现象?
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread myThread = new MyThread(); 4 myThread.start(); 5 myThread.start(); 6 System.out.println("end"); 7 } 8 }
运行结果如下:
MyThread Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:705) at mult_thread.MyRun.main(MyRun.java:11)
第一次调用start方法,线程正确执行,第二次调用start方法报错IllegalThreadStateException。
线程的随机性
1 public class MyThread extends Thread { 2 @Override 3 public void run() { 4 for (int i = 0 ; i < 5 ; i++){ 5 int sleepTime = (int) (Math.random()*1000); 6 try { 7 Thread.sleep(sleepTime); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 System.out.println(Thread.currentThread().getName()); 12 } 13 } 14 }
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread myThread = new MyThread(); 4 myThread.setName("myThread"); 5 myThread.start(); 6 for (int i = 0 ; i < 5 ; i++){ 7 int sleepTime = (int) (Math.random()*1000); 8 try { 9 Thread.sleep(sleepTime); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName()); 14 } 15 System.out.println("end"); 16 } 17 }
运行结果
myThread
myThread
main
myThread
myThread
main
myThread
main
main
main
end
Thread类中的start方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run方法。让系统安排一个时间来调用run方法。
那么问题来了?若是不通过start方法,而是直接调用run方法会怎样呢?
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread myThread = new MyThread(); 4 myThread.setName("myThread"); 5 myThread.run(); //注意直接调用run 6 for (int i = 0 ; i < 5 ; i++){ 7 int sleepTime = (int) (Math.random()*1000); 8 try { 9 Thread.sleep(sleepTime); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName()); 14 } 15 System.out.println("end"); 16 } 17 }
运行结果:
main
main
main
main
main
main
main
main
main
main
end
此时只有一个线程运行,那就是main线程。这里是同步执行,而不是异步的,也就是说代码会顺序执行。这里看不出来,小改一下run方法就可以看出来
System.out.println(Thread.currentThread().getName()); //改成 System.out.println("run="+Thread.currentThread().getName());
运行结果
1 run=main 2 run=main 3 run=main 4 run=main 5 run=main 6 main 7 main 8 main 9 main 10 main 11 end
看出来了吧!
2实现Runna接口
1 public class MyRunnable implements Runnable { 2 @Override 3 public void run() { 4 System.out.println("运行中!"); 5 } 6 }
1 public class MyTest { 2 public static void main(String[] args) { 3 Runnable runnable = new MyRunnable(); 4 Thread thread = new Thread(runnable); 5 thread.start(); 6 System.out.println("end"); 7 } 8 }
运行结果
end
运行中!
通过实现Runna接口的方式实现多线程编程,是将一个Runnable实例作为一个target传给Thread对象,再通过调用Thread的start方法实现。
那么问题来了,实现runnable接口比继承Thread类的方式有什么好处?
java是单继承的,多实现的,如果采用继承Thread类的方式实现多线程,那么这个类就不可以继承其他的类了,这并不是人们所期待的,为了改变这种局限性,可以使用实现接口的方式。
注意:Thread类实现了Runnable接口,是不是也就意味着构造函数Thread(Runnable target)不仅可以传入Runnable接口对象,还可以传入Thread类的对象。也就是说,可以将一个Thread对象中的run方法交由其他线程进行调用。
1 public class MyTest { 2 public static void main(String[] args) { 3 //Runnable runnable = new MyRunnable(); 4 Thread myThread = new MyThread(); //这里是上文的MyThread类 5 Thread thread = new Thread(myThread,"myThread"); 6 thread.start(); 7 System.out.println("end"); 8 } 9 }
构造函数Thread(Runnable target,String name),运行结果:
1 end 2 run=myThread 3 run=myThread 4 run=myThread 5 run=myThread 6 run=myThread
实例变量与线程安全
自定义线程类中,实例变量针对其他线程可以有共享与不共享之分。这是一个很重要的技术
数据不共享的情况
1 public class MyThread extends Thread { 2 3 private int count = 5; 4 public MyThread(String name){ 5 super(); 6 this.setName(name);//设置线程名字 7 } 8 @Override 9 public void run() { 10 super.run(); 11 while (count>0){ 12 count--; 13 System.out.println("由"+Thread.currentThread().getName()+"计算:"+"count="+count); 14 } 15 } 16 }
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread a = new MyThread("A"); 4 MyThread b = new MyThread("B"); 5 MyThread c = new MyThread("C"); 6 a.start(); 7 b.start(); 8 c.start(); 9 System.out.println("end"); 10 } 11 }
运行结果
1 end 2 由B计算:count=4 3 由C计算:count=4 4 由A计算:count=4 5 由A计算:count=3 6 由C计算:count=3 7 由B计算:count=3 8 由C计算:count=2 9 由A计算:count=2 10 由A计算:count=1 11 由C计算:count=1 12 由B计算:count=2 13 由C计算:count=0 14 由A计算:count=0 15 由B计算:count=1 16 由B计算:count=0
如果想实现三个线程对同一个count变量进行减法操作,该如何设计呢?
1 public class MyThread extends Thread { 2 3 private int count = 5; 4 @Override 5 public void run() { 6 super.run(); 7 while (count>0){ 8 count--; 9 System.out.println("由"+Thread.currentThread().getName()+"计算:"+"count="+count); 10 } 11 } 12 }
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread myThread = new MyThread(); 4 Thread a = new Thread(myThread,"a"); 5 Thread b = new Thread(myThread,"b"); 6 Thread c = new Thread(myThread,"c"); 7 a.start(); 8 b.start(); 9 c.start(); 10 System.out.println("end"); 11 } 12 }
运行结果
1 end 2 由b计算:count=3 3 由b计算:count=1 4 由b计算:count=0 5 由a计算:count=3 6 由c计算:count=2
结果表明,不仅顺序是乱的,而且当count值为3时,a和b同时对count值进行处理。非线程安全问题产生了~
在某些JVM中,i--并不是原子的,分为三步:1取得原有的i值;2计算i-1;3对i进行赋值。
在上述三个步骤中,如果有多个线程同时访问,就会出现非线程安全问题。
解决办法:在run方法前加synchronized关键字,其他不变
1 public class MyThread extends Thread { 2 3 private int count = 5; 4 @Override 5 public synchronized void run() { 6 super.run(); 7 while (count > 0) { 8 count--; 9 System.out.println("由" + Thread.currentThread().getName() + "计算:" + "count=" + count); 10 } 11 System.out.println("由" + Thread.currentThread().getName() + "打印:for循环之后"); 12 } 13 }
运行结果
1 end 2 由a计算:count=4 3 由a计算:count=3 4 由a计算:count=2 5 由a计算:count=1 6 由a计算:count=0 7 由a打印:for循环之后 8 由c打印:for循环之后 9 由b打印:for循环之后
加synchronized关键字后,使多个线程在处理run方法时,以排队的方式进行处理。synchronized可以在任意对象或方法上加锁,而加锁的这段代码称为“互斥区”或者“临界区”。
那么问题来了,当线程a进来持有锁后,线程b和c就等待,直到a运行完所有代码后,释放锁。这时b或c线程进来发现count的值等于0,不进while循环。相当于a完成减所有操作,b和c只能看着。怎么猜才能让a b c三个线程协作呢?
一个简单解决办法:一个线程执行一次减操作后,让其释放锁并等待,让其他线程继续操作。
1 public class MyThread extends Thread { 2 3 private int count = 5; 4 @Override 5 public synchronized void run() { 6 super.run(); 7 while (count > 0) { 8 count--; 9 System.out.println("由" + Thread.currentThread().getName() + "计算:" + "count=" + count); 10 try { 11 wait(100); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 System.out.println("由" + Thread.currentThread().getName() + "打印:for循环之后"); 17 } 18 }
运行结果
1 end 2 由a计算:count=4 3 由b计算:count=3 4 由c计算:count=2 5 由a计算:count=1 6 由b计算:count=0 7 由c打印:for循环之后 8 由a打印:for循环之后 9 由b打印:for循环之后
wait(100)就是让线程释放锁,并等待0.1s后,自动唤醒。
currentThread()方法
该方法可返回代码段正在被哪个线程调用的信息
区别Thread.currentThread().getName()和this.getName()
isAlive()方法
判断当前线程是否处于活动状态。
什么是活动状态?线程处于正在运行或者准备运行的状态。
sleep()方法
静态方法,Thread.sleep(long times)毫秒
让正在执行的线程休眠(暂停执行),正在执行的线程是指this.currentThread()返回的线程
getId()方法
取得线程的唯一标识
停止线程
终止正在运行的线程的三种方法:
使用对出标识,使线程正常退出,也就是当run方法运行完成后线程终止。
使用stop()方法强行终止,该方法已被废弃,不推荐使用。
使用interrupt方法中断线程(配合异常使用)
1 public class MyThread extends Thread { 2 3 private int count = 5; 4 @Override 5 public void run() { 6 super.run(); 7 for (int i = 0 ; i < 1000 ; i++){ 8 System.out.println("i = " + (i+1)); 9 } 10 } 11 }
1 public class MyRun { 2 public static void main(String[] args) { 3 MyThread myThread = new MyThread(); 4 myThread.start(); 5 try { 6 Thread.sleep(10); 7 myThread.interrupt(); 8 //System.out.println(myThread.interrupted()); 9 System.out.println(myThread.isInterrupted()); 10 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 System.out.println("end"); 15 } 16 }
运行结果
1 i = 0 2 .... 3 i = 892 4 true 5 end 6 i = 893 7 ... 8 i = 1000
interrupt方法后,线程并不是真的停止,而是在当前线程中打一个停止标记。所以for循环不会中断。
注意isInterrupted()方法和interrupted()方法的区别,上述例子中,使用isInterrupted()方法返回true,而使用interrupted()方法返回false,why?
isInterrupted():成员函数,myThread.isInterrupted()测试myThread线程是否中断
interrupted():静态方法,测试当前线程是否中断
上例中,interrupted()表示的是main线程,当然false,而isInterrupted()表示myThread线程,因为myThread调用了interrupt方法,标记为true。
注意,问题来了!
未完待续....