Java之多线程

一、线程的引入:
定义:同时对多项任务加以控制

我们上下代码:

1.未使用线程

 1 package com.learn.chap08.sec01;
 2 /**
 3  * 未使用线程--同步执行
 4  * @author Administrator
 5  *
 6  */
 7 public class Demo1 {
 8     
 9     /**
10      * 听音乐
11      */
12     public static void music(){
13         for (int i = 0; i < 5; i++) {
14             System.out.println("听音乐");
15         }
16     }
17     
18     /**
19      * 吃饭
20      */
21     public static void eat(){
22         for (int i = 0; i < 5; i++) {
23             System.out.println("吃饭");
24         }
25     }
26     
27     public static void main(String[] args) {
28         music();
29         eat();
30     }
31 }

2. 使用线程

 1 package com.learn.chap08.sec01;
 2 /**
 3  * 使用多线程--异步执行
 4  * @author Administrator
 5  *
 6  */
 7 public class Eat extends Thread{
 8 
 9     @Override
10     public void run() {
11         for (int i = 0; i < 5; i++) {
12             try {
13                 Thread.sleep(100); // 100毫秒
14             } catch (InterruptedException e) {
15                 // TODO Auto-generated catch block
16                 e.printStackTrace();
17             }
18             System.out.println("吃饭");
19         }
20     }
21     
22 }
 1 package com.learn.chap08.sec01;
 2 /**
 3  * 使用多线程--异步执行
 4  * @author Administrator
 5  *
 6  */
 7 public class Music extends Thread {
 8 
 9     @Override
10     public void run() {
11         // TODO Auto-generated method stub
12         for (int i = 0; i < 5; i++) {
13             try {
14                 Thread.sleep(100);
15             } catch (InterruptedException e) {
16                 // TODO Auto-generated catch block
17                 e.printStackTrace();
18             }
19             System.out.println("听音乐");
20         }
21     }
22     
23 }
 1 package com.learn.chap08.sec01;
 2 /**
 3  * 使用多线程--异步执行
 4  * @author Administrator
 5  *
 6  */
 7 public class Demo2 {
 8     public static void main(String[] args) {
 9         /**
10          * 利用多线程--实现一边听音乐 一边吃饭
11          */
12         Music musicThread = new Music();
13         Eat eatThread     = new Eat();
14         musicThread.start();
15         eatThread.start();
16     }
17 }

二、使用多线程

1. 继承Thread类

代码如下:

 1 package com.learn.chap08.sec02;
 2 
 3 public class Thread1 extends Thread {
 4     private String threadName;
 5     private int baoZi = 1;
 6 
 7     public Thread1(String threadName) {
 8         super();
 9         this.threadName = threadName;
10     }
11 
12     @Override
13     public void run() {
14         // TODO Auto-generated method stub
15         while(baoZi<=10){
16             System.out.println(this.threadName+" 吃第"+baoZi+"个包子");
17             baoZi++;
18         }
19     }
20     
21     public static void main(String[] args) {
22         System.out.println("张三、李四各自吃10个包子");
23         Thread1 t1=new Thread1("张三线程");
24         Thread1 t2=new Thread1("李四线程");
25         t1.start();
26         t2.start();
27     }
28     
29 }

2. 实现Runnable接口

代码如下:

 1 package com.learn.chap08.sec02;
 2 
 3 public class Thread2 implements Runnable{
 4 
 5     private String threadName;
 6     private int baoZi = 1;
 7 
 8     public Thread2(String threadName) {
 9         super();
10         this.threadName = threadName;
11     }
12 
13     @Override
14     public void run() {
15         // TODO Auto-generated method stub
16         while(baoZi<=10){
17             System.out.println(this.threadName+" 吃第"+baoZi+"个包子");
18             baoZi++;
19         }
20     }
21     
22     public static void main(String[] args) {
23         System.out.println("张三、李四各自吃10个包子");
24         Thread1 t1=new Thread1("张三线程");
25         Thread1 t2=new Thread1("李四线程");
26         Thread t11=new Thread(t1);
27         Thread t12=new Thread(t2);
28         t11.start();
29         t12.start();
30     }
31     
32 }
 1 package com.learn.chap08.sec02;
 2 
 3 public class Thread3 implements Runnable{
 4 
 5     private String threadName;
 6     private int baoZi = 1;
 7 
 8     public Thread3(String threadName) {
 9         super();
10         this.threadName = threadName;
11     }
12 
13     @Override
14     public synchronized void run() {
15         // TODO Auto-generated method stub
16         while(baoZi<=10){
17             System.out.println(this.threadName+" 吃第"+baoZi+"个包子");
18             baoZi++;
19         }
20     }
21     
22     public static void main(String[] args) {
23         
24         Thread3 t1=new Thread3("超级张三线程");
25         
26         Thread t11=new Thread(t1);
27         Thread t12=new Thread(t1);
28         Thread t13=new Thread(t1);
29         // 实现资源共享
30         t11.start();
31         t12.start();
32         t13.start();
33     }
34     
35 }

总结: Runnable接口 可以实现资源共享  而Thread不能。

三、线程状态

四、多线程常用的方法

举例代码如下:

 1 package com.learn.chap08.sec04;
 2 
 3 public class Demo1 implements Runnable{
 4 
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         for (int i = 0; i < 10; i++) {
 9             // 获取当前线程
10             Thread t=Thread.currentThread();
11             System.out.println(t.getName()+":"+i);
12         }
13         
14     }
15     
16     public static void main(String[] args) {
17         Demo1 demo1 = new Demo1();
18         new Thread(demo1).start();
19         new Thread(demo1,"线程一").start();
20         new Thread(demo1,"线程二").start();
21         
22         Thread it = new Thread(demo1);
23         System.out.println(it.isAlive());
24         it.start();
25         System.out.println(it.isAlive());
26     }
27 
28 }

运行结果

false
true
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
线程二:0
线程二:1
线程二:2
线程二:3
线程二:4
线程二:5
线程二:6
线程二:7
线程二:8
线程二:9
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
线程一:0
线程一:1
线程一:2
线程一:3
线程一:4
线程一:5
线程一:6
线程一:7
线程一:8
线程一:9

 1 package com.learn.chap08.sec04;
 2 
 3 public class Demo2 implements Runnable{
 4 
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         for (int i = 0; i < 10; i++) {
 9             try {
10                 Thread.sleep(1000); // 线程休眠
11                 // 获取当前线程
12                 Thread t=Thread.currentThread();
13                 System.out.println(t.getName()+":"+i);
14             } catch (InterruptedException e) {
15                 // TODO Auto-generated catch block
16                 e.printStackTrace();
17             }
18             
19         }
20         
21     }
22     
23     public static void main(String[] args) {
24         Demo2 demo1 = new Demo2();
25         new Thread(demo1).start();
26         new Thread(demo1,"线程一").start();
27     }
28 
29 }

运行结果

Thread-0:0
线程一:0
Thread-0:1
线程一:1

.

.

.

 1 package com.learn.chap08.sec04;
 2 
 3 public class Demo3 implements Runnable{
 4 
 5     @Override
 6     public void run() {
 7         // TODO Auto-generated method stub
 8         for (int i = 0; i < 10; i++) {
 9             // 获取当前线程
10             Thread t=Thread.currentThread();
11             System.out.println(t.getName()+":"+i);
12             
13         }
14         
15     }
16     
17     public static void main(String[] args) {
18         Demo3 demo1 = new Demo3();
19         Thread t1 = new Thread(demo1,"线程一");
20         Thread t2 = new Thread(demo1,"线程二");
21         Thread t3 = new Thread(demo1,"线程三");
22         t1.setPriority(Thread.MAX_PRIORITY); // 更改线程的优先级
23         t2.setPriority(Thread.NORM_PRIORITY);
24         t3.setPriority(Thread.MIN_PRIORITY);
25         t1.start();
26         t2.start();
27         t3.start();
28     }
29 
30 }
 1 package com.learn.chap08.sec04;
 2 
 3 public class Demo4 implements Runnable{
 4 
 5     @SuppressWarnings("static-access")
 6     @Override
 7     public void run() {
 8         // TODO Auto-generated method stub
 9         for (int i = 0; i < 10; i++) {
10             try {
11                 Thread.sleep(100);
12                 // 获取当前线程
13                 Thread t=Thread.currentThread();
14                 System.out.println(t.getName()+":"+i);
15                 if(i==5){
16                     System.out.println("线程礼让:");
17                     Thread.currentThread().yield();// 出现@SuppressWarnings("static-access")
18                 }
19             } catch (InterruptedException e) {
20                 // TODO Auto-generated catch block
21                 e.printStackTrace();
22             }
23         }
24     }
25     
26     public static void main(String[] args) {
27         Demo4 demo1 = new Demo4();
28         Thread t1 = new Thread(demo1,"线程一");
29         Thread t2 = new Thread(demo1,"线程二");
30         t1.start();
31         t2.start();
32     }
33 
34 }

运行结果

线程一:0
线程二:0
线程一:1
线程二:1
线程一:2
线程二:2
线程二:3
线程一:3
线程一:4
线程二:4
线程一:5
线程礼让:
线程二:5
线程礼让:
线程一:6
线程二:6
线程一:7
线程二:7
线程一:8
线程二:8
线程一:9
线程二:9

五、线程同步
1. 同步方法
2. 同步锁(锁机制)

 上下代码:

 1 package com.learn.chap08.sec05;
 2 
 3 public class Demo2 implements Runnable{
 4 
 5     private int baoZi=10;
 6     
 7     @Override
 8     /**
 9      * 同步方法
10      */
11     public synchronized void run() { // 用synchronized标识 的方法为同步方法  不加锁的话,下面的张三、李四、王五可能会同时进入run()方法
12         // TODO Auto-generated method stub
13         while(baoZi>0){
14             System.out.println(Thread.currentThread().getName()+"吃了第"+baoZi+"个包子");
15             baoZi--;
16         }
17     }
18     
19     public static void main(String[] args) {
20         Demo2 demo1=new Demo2();
21         new Thread(demo1,"张三").start();
22         new Thread(demo1,"李四").start();
23         new Thread(demo1,"王五").start();
24     }
25 
26 }
 1 package com.learn.chap08.sec05;
 2 
 3 public class Demo3 implements Runnable{
 4 
 5     private int baoZi=10;
 6     
 7     @Override
 8     public void run() {
 9         /**
10          * 同步块
11          */
12         synchronized (this) { // 同步块
13             while(baoZi>0){
14                 System.out.println(Thread.currentThread().getName()+"吃了第"+baoZi+"个包子");
15                 baoZi--;
16             }
17         }
18     }
19     
20     public static void main(String[] args) {
21         Demo3 demo1=new Demo3();
22         new Thread(demo1,"张三").start();
23         new Thread(demo1,"李四").start();
24         new Thread(demo1,"王五").start();
25     }
26 
27 }

下面举例:java synchronized同步方法调用另一个同步方法,锁机制问题

1 public synchronized void methodA(int a, int b);
2 
3 public synchronized void methodB(int a){
4     methodA(a, 0);
5 }

要明白两个问题,1.锁的对象是谁,2.谁持有了锁。
假设方法A和B是在同一个类Test中的两个方法。
Test t=new Test();
t.methodB();
这个时候,methodB方法被调用时,因为加了synchronized ,需要先获得一个锁,这个锁的对象应该是t,也就是当前的这个Test类的实例,而获得锁的东西是线程,也就是说当前线程拿到了t的锁(而不是你说的B方法获得锁),这个时候B方法内调用methodA,因为A也加了synchronized,也需要获得一个锁,因为A和B都是Test类中的方法,所以当前线程要获得的锁的对象也是t。由于当前线程在执行B方法时已经持有了t对象的锁,因此这时候调用methodA是没有任何影响的,相当于方法A上没有加synchronized。

另一种情况:假设现在有两个Test类
Test t1=new Test();
Test t2=new Test();
t1.methodB();//此时当前线程持有了t1对象的锁
t2.methodB();//此时当前线程也持有了t2对象的锁
当前线程持有了两把锁,锁的对象分别是两个不同的Test类的实例t1和t2,互相没有影响。

再一种情况:假设在多线程环境下,两个线程都可以访问Test t=new Test();
此时假设thread1里调用t.methodB();同时thread2里调用t.methodB()

这时假设thread1先抢到t对象的锁,那么thread2需要等待thread1释放t对象的锁才可以执行B方法。
结果像这样:
thread1获得t的锁--thread1执行methodB--thread1执行methodA--释放t的锁---thread2获得t的锁--thread2执行methodB--thread2执行methodA--释放t的锁。

synchronized还有很多种使用方法,但只有明白是那条线程获得哪个对象的锁,就很容易明白了。

2016年10月31号偶然看到了crossoverjie整理的多线程知识点(原文链接:http://www.jianshu.com/p/72d53d4f833a),感觉不错,于是在这顺便记录下,补充下知识,呵呵!

进程与线程的区别

进程

进程简单的来说就是在内存中运行的应用程序,一个进程可以启动多个线程。
比如在windows中一个运行EXE文件就是一个进程。

线程

同一进程内的线程共享此进程的地址空间,同时共享进程所拥有的内存和其他资源。

--------------------------------------------------------------------------------------------------

线程Demo-继承Thread类

首先我们我们继承java.lang.Thread类来创建线程。

 1 package top.crosssoverjie.study.Thread;
 2 
 3 public class TestThread {
 4     public static void main(String[] args) {
 5         System.out.println("主线程ID是:" + Thread.currentThread().getId());
 6         MyThread my = new MyThread("线程1");
 7         my.start() ;
 8 
 9         MyThread my2 = new MyThread("线程2") ;
10         /**
11          * 这里直接调用my2的run()方法。
12          */
13         my2.run() ;
14     }
15 
16 }
17 
18 class MyThread extends Thread {
19     private String name;
20 
21     public MyThread(String name) {
22         this.name = name;
23     }
24 
25     @Override
26     public void run() {
27         System.out.println("名字:" + name + "的线程ID是="
28                 + Thread.currentThread().getId());
29     }
30 
31 }

输出结果:

主线程ID是:1
名字:线程2的线程ID是=1
名字:线程1的线程ID是=9

由输出结果我们可以得出以下结论:

  • my和my2的线程ID不相同,my2和主线程ID相同。说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用的run()方法,和普通的方法调用没有区别。
  • 虽然my的start()方法是在my2的run()方法之前调用,但是却是后输出内容,说明新建的线程并不会影响主线程的执行。

----------------------------------------------------------------------------------------------------

线程Demo-实现Runnable接口

除了继承java.lang.Thread类之外,我们还可以实现java.lang.Runnable接口来创建线程。

 1 package top.crosssoverjie.study.Thread;
 2 
 3 public class TestRunnable {
 4     public static void main(String[] args) {
 5         System.out.println("主线程的线程ID是"+Thread.currentThread().getId());
 6         MyThread2 my = new MyThread2("线程1") ;
 7         Thread t = new Thread(my) ;
 8         t.start() ;
 9 
10         MyThread2 my2 = new MyThread2("线程2") ;
11         Thread t2 = new Thread(my2) ;
12         /**
13          * 方法调用,并不会创建线程,依然是主线程
14          */
15         t2.run() ;
16     }
17 }
18 
19 class MyThread2 implements Runnable{
20     private String name ;
21     public MyThread2(String name){
22         this.name = name ;
23     }
24 
25     @Override
26     public void run() {
27         System.out.println("线程"+name+"的线程ID是"+Thread.currentThread().getId());
28     }
29 
30 
31 }

输出结果:

主线程的线程ID是1
线程线程2的线程ID是1
线程线程1的线程ID是9

notes:

  • 实现Runnable的方式需要将实现Runnable接口的类作为参数传递给Thread,然后通过Thread类调用Start()方法来创建线程。
  • 这两种方式都可以来创建线程,至于选择哪一种要看自己的需求。直接继承Thread类的话代码要简洁一些,但是由于java只支持单继承,所以如果要继承其他类的同时需要实现线程那就只能实现Runnable接口了,这里更推荐实现Runnable接口。

实际上如果我们查看Thread类的源码我们会发现Thread是实现了Runnable接口的:


线程中常用的方法:

方法详解- public static void sleep(long mills)

 1 package top.crosssoverjie.study.Thread;
 2 
 3 public class TestSleep {
 4 
 5     private int i = 10 ;
 6     private Object ob = new Object() ;
 7 
 8     public static void main(String[] args) {
 9         TestSleep t = new TestSleep() ;
10         MyThread3 thread1 = t.new MyThread3() ;
11         MyThread3 thread2 = t.new MyThread3() ;
12         thread1.start() ;
13         thread2.start() ;
14     }
15 
16     class MyThread3 extends Thread{
17         @Override
18         public void run() {
19             synchronized (ob) {
20                 i++ ;
21                 System.out.println("i的值:"+i);
22                 System.out.println("线程:"+Thread.currentThread().getName()+"进入休眠状态");
23                 try {
24                     Thread.currentThread().sleep(1000) ;
25                 } catch (Exception e) {
26                     e.printStackTrace();
27                 }
28                 System.out.println("线程:"+Thread.currentThread().getName()+"休眠结束");
29                 i++;
30                 System.out.println("i的值>:"+i);
31             }
32         }
33     }
34 
35 }

输出结果:

i的值:11
线程:Thread-0进入休眠状态
线程:Thread-0休眠结束
i的值>:12
i的值:13
线程:Thread-1进入休眠状态
线程:Thread-1休眠结束
i的值>:14
 

由输出结果我们可以得出:

     当Thread0进入休眠状态时,Thread1并没有继续执行,而是等待Thread0休眠结束释放了对象锁,Thread1才继续执行。
     当调用sleep()方法时,必须捕获异常或者向上层抛出异常。当线程休眠时间满时,并不一定会马上执行,因为此时有可能CPU正在执行其他的任务,所以调用了sleep()方法相当于线程进入了阻塞状态。

方法详解- public static void yield()

 1 package top.crosssoverjie.study.Thread;
 2 
 3 public class Testyield {
 4     public static void main(String[] args) {
 5         MyThread4 my = new MyThread4() ;
 6         my.start() ;
 7     }
 8 }
 9 class MyThread4 extends Thread{
10     @Override
11     public void run() {
12         long open = System.currentTimeMillis();
13         int count= 0 ;
14         for(int i=0 ;i<1000000;i++){
15             count= count+(i+1);
16 //            Thread.yield() ;
17         }
18         long end = System.currentTimeMillis();
19         System.out.println("用时:"+(end-open)+"毫秒");
20     }
21 }

输出结果:
用时:1毫秒
如果将 Thread.yield()注释取消掉,输出结果:
用时:116毫秒

总结:

  • 调用yield()方法是为了让当前线程交出CPU权限,让CPU去执行其他线程。它和sleep()方法类似同样是不会释放锁。但是yield()不能控制具体的交出CUP的时间。并且它只能让相同优先级的线程获得CPU执行时间的机会。
  • 调用yield()方法不会让线程进入阻塞状态,而是进入就绪状态,它只需要等待重新获取CPU的时间,这一点和sleep()方法是不一样的。

方法详解- public final void join()

在很多情况下我们需要在子线程中执行大量的耗时任务,但是我们主线程又必须得等待子线程执行完毕之后才能结束,这就需要用到 join()方法了。join()方法的作用是等待线程对象销毁,如果子线程执行了这个方法,那么主线程就要等待子线程执行完毕之后才会销毁,请看下面这个例子:

 1 package top.crosssoverjie.study.Thread;
 2 
 3 public class Testjoin {
 4     public static void main(String[] args) throws InterruptedException {
 5         new MyThread5("t1").start() ;
 6         for (int i = 0; i < 10; i++) {
 7             if(i == 5){
 8                 MyThread5 my =new MyThread5("t2") ;
 9                 my.start() ;
10                 my.join() ;
11             }
12             System.out.println("main当前线程:"+Thread.currentThread().getName()+" "+i);
13         }
14     }
15 }
16 class MyThread5 extends Thread{
17 
18     public MyThread5(String name){
19         super(name) ;
20     }
21     @Override
22     public void run() {
23         for (int i = 0; i < 5; i++) {
24             System.out.println("当前线程:"+Thread.currentThread().getName()+" "+i);
25         }
26     }
27 }

输出结果:

main当前线程:main 0
当前线程:t1 0
当前线程:t1 1
main当前线程:main 1
当前线程:t1 2
main当前线程:main 2
当前线程:t1 3
main当前线程:main 3
当前线程:t1 4
main当前线程:main 4
当前线程:t2 0
当前线程:t2 1
当前线程:t2 2
当前线程:t2 3
当前线程:t2 4
main当前线程:main 5
main当前线程:main 6
main当前线程:main 7
main当前线程:main 8
main当前线程:main 9


如果我们把join()方法注释掉之后:
main当前线程:main 0
当前线程:t1 0
main当前线程:main 1
当前线程:t1 1
main当前线程:main 2
当前线程:t1 2
main当前线程:main 3
当前线程:t1 3
main当前线程:main 4
当前线程:t1 4
main当前线程:main 5
main当前线程:main 6
main当前线程:main 7
main当前线程:main 8
main当前线程:main 9
当前线程:t2 0
当前线程:t2 1
当前线程:t2 2
当前线程:t2 3
当前线程:t2 4


由上我们可以得出以下结论:

  • 在使用了join()方法之后主线程会等待子线程结束之后才会结束。

方法详解- setDaemon(boolean on),getDaemon()

用来设置是否为守护线程和判断是否为守护线程。
notes:

  • 守护线程依赖于创建他的线程,而用户线程则不需要。如果在main()方法中创建了一个守护线程,那么当main方法执行完毕之后守护线程也会关闭。而用户线程则不会,在JVM中垃圾收集器的线程就是守护线程。

优雅的终止线程

有三种方法可以终止线程,如下:

  1. 使用退出标识,使线程正常的退出,也就是当run()方法完成后线程终止。
  2. 使用stop()方法强行关闭,这个方法现在已经被废弃,不推荐使用
  3. 使用interrupt()方法终止线程。

具体的实现代码我将在下一篇博文中将到。。

线程的优先级

在操作系统中线程是分优先级的,优先级高的线程CPU将会提供更多的资源,在java中我们可以通过setPriority(int newPriority)方法来更改线程的优先级。
在java中分为1~10这个十个优先级,设置不在这个范围内的优先级将会抛出IllegalArgumentException异常。
java中有三个预设好的优先级:

  • public final static int MIN_PRIORITY = 1;
  • public final static int NORM_PRIORITY = 5;
  • public final static int MAX_PRIORITY = 10;
 

java多线程思维图:






原文地址:https://www.cnblogs.com/eaglezb/p/6011361.html