java多线程(一)

进程与线程

进程是操作系统结构的基础,是一次程序的执行;简单理解,进程就是一个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。

注意,问题来了!

未完待续....

原文地址:https://www.cnblogs.com/ouym/p/6991816.html