Java学习笔记(八)——java多线程
【前面的话】
实际项目在用spring框架结合dubbo框架做一个系统,虽然也负责了一块内容,但是自己的能力还是不足,所以还需要好好学习一下基础知识,然后做一些笔记。希望做完了这个项目可以写一些dubbo框架和spring框架方面的总结。
学习过程中的小知识点总结,基础知识,选择阅读。
【线程定义】
在学习操作系统的时候,学习过什么是进程,什么是线程,下面这只维基百科里面关于线程的定义,大家可以看一下:
定义:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
【实现方法】
1. 第一种继承:Thread类
1 class 类名extends Thread{ 2 方法1; 3 方法2; 4 …… 5 public void run(){ 6 实现代码 7 } 8 }
2. 第二种:实现Runnable接口
1 class 类名 implements Runnable{ 2 方法1; 3 方法2; 4 …… 5 public void run(){ 6 实现代码 7 } 8 }
【三段代码】
第一段代码,就是一个很简单,就是和多线程没有关系的一段测试代码。
第二段代码,使用继承Thread的方法实现多线程。
第三段代码,使用实现Runnable接口的方法实现多线程。
第一段代码:
1 public class NoThreadTest { 2 private String noThread1; 3 public NoThreadTest(){ 4 } 5 public NoThreadTest(String noThread1){ 6 this.noThread1=noThread1; 7 } 8 public void run(){ 9 for(int i=0;i<3;i++){ 10 System.out.println(noThread1+"非多线程运行结果 "+i); 11 } 12 } 13 public static void main(String[] args){ 14 NoThreadTest maintest1=new NoThreadTest("我是A"); 15 NoThreadTest maintest2=new NoThreadTest("我是B"); 16 maintest1.run(); 17 maintest2.run(); 18 }
执行结果:
1 我是A非多线程运行结果 0 2 我是A非多线程运行结果 1 3 我是A非多线程运行结果 2 4 我是B非多线程运行结果 0 5 我是B非多线程运行结果 1 6 我是B非多线程运行结果 2
第二段代码:
1 public class ThreadTest extends Thread { 2 private String Thread1; 3 public ThreadTest(){ 4 } 5 public ThreadTest(String Thread1){ 6 this.Thread1=Thread1; 7 } 8 public void run(){ 9 for(int i=0;i<3;i++){//可以参看评论5进行修改,效果会好一些。 10 System.out.println(Thread1+"多线程运行结果 "+i); 11 } 12 } 13 public static void main(String[] args){ 14 ThreadTest maintest1=new ThreadTest("我是A"); 15 ThreadTest maintest2=new ThreadTest("我是B"); 16 maintest1.run(); 17 maintest2.run(); 18 System.out.println("..............我是分割线........................"); 19 maintest1.start(); 20 maintest2.start(); 21 } 22 }
执行结果:(每一次执行的结果都是不一样的,这只是其中的某一种)
1 我是A多线程运行结果 0 2 我是A多线程运行结果 1 3 我是A多线程运行结果 2 4 我是B多线程运行结果 0 5 我是B多线程运行结果 1 6 我是B多线程运行结果 2 7 ..............我是分割线........................ 8 我是A多线程运行结果 0 9 我是B多线程运行结果 0 10 我是B多线程运行结果 1 11 我是B多线程运行结果 2 12 我是A多线程运行结果 1 13 我是A多线程运行结果 2
第三段代码:
1 public class ThreadTest2 implements Runnable { 2 private String Thread1; 3 public ThreadTest2(){ 4 } 5 public ThreadTest2(String Thread1){ 6 this.Thread1=Thread1; 7 } 8 public void run(){ 9 for(int i=0;i<3;i++){//可以参看评论5,效果会好一些 10 System.out.println(Thread1+"多线程运行结果 "+i); 11 } 12 } 13 public static void main(String[] args){ 14 ThreadTest2 maintest1=new ThreadTest2("我是A"); 15 ThreadTest2 maintest2=new ThreadTest2("我是B"); 16 Thread maintest4=new Thread(maintest1); 17 Thread maintest5=new Thread(maintest2); 18 maintest1.run(); 19 maintest2.run(); 20 System.out.println("..............我是分割线1........................"); 21 maintest4.run(); 22 maintest5.run(); 23 System.out.println("..............我是分割线2........................"); 24 maintest4.start(); 25 maintest5.start(); 26 } 27 }
执行结果:(每一次执行的结果都是不一样的,这只是其中的某一种)
1 我是A多线程运行结果 0 2 我是A多线程运行结果 1 3 我是A多线程运行结果 2 4 我是B多线程运行结果 0 5 我是B多线程运行结果 1 6 我是B多线程运行结果 2 7 ..............我是分割线1........................ 8 我是A多线程运行结果 0 9 我是A多线程运行结果 1 10 我是A多线程运行结果 2 11 我是B多线程运行结果 0 12 我是B多线程运行结果 1 13 我是B多线程运行结果 2 14 ..............我是分割线2........................ 15 我是B多线程运行结果 0 16 我是B多线程运行结果 1 17 我是B多线程运行结果 2 18 我是A多线程运行结果 0 19 我是A多线程运行结果 1 20 我是A多线程运行结果 2
【代码分析】
1. run方法和start方法的区别:
java核心技术中有这样一段话:不要调用Thread类或Runnable对象的run方法。直接调用run方法,只会执行同一个线程中的任务,而不会启动新线程。应该调用Thread.start方法。这个方法将创建一个执行run方法的新线程。
2. 在上面第三段代码中,ThreadTest2类,Thread类和Runnerable接口都实现了run方法。
1)Runnerable接口实现run方法
1 public 2 interface Runnable { 3 /** 4 * When an object implementing interface <code>Runnable</code> is used 5 * to create a thread, starting the thread causes the object's 6 * <code>run</code> method to be called in that separately executing 7 * thread. 8 * <p> 9 * The general contract of the method <code>run</code> is that it may 10 * take any action whatsoever. 11 * 12 * @see java.lang.Thread#run() 13 */ 14 public abstract void run(); 15 }
2)Thread类实现run方法。
1 public void run() { 2 if (target != null) { 3 target.run(); 4 } 5 }
3)为什么要说这个?因为这个实现是使用了代理模式。
代理:一个角色代表另一个角色来完成某些特定的功能。
举个例子:大家都买过衣服,所以在买衣服的时候,一般有下面的角色:
购物者:我们一般是从代理商那里买衣服,我们并不和制造商进行交涉,我们不关心衣服是如何生产出来的。
代理商:代理商是从制造商那里拿衣服,并且代理商可以提供一些简单的服务,比如裁剪裤子等。
制造商:制造衣服,并且批发衣服给代理商。
我们从上面的行为中可以抽象出,一个行为就是卖衣服这个行为在代理商和制造商都有,如果购物者要买衣服,也需要以代理商和制造商卖衣服为前提。
从上面我们可以抽象出三个角色,并不是和上面对应的哈。
抽象主题角色:这个使我们可以抽象出来的角色。就是卖衣服这个行为。
代理主题角色:中间商。
实际被代理角色:制造商。
代码:
- SellClothes.java(需要和接口名一样)
1 //抽象主题角色:买衣服 2 public interface SellClothes { 3 void sellClothes(); 4 }
- Middleman.java
1 //代理主题角色:中间商 2 public class Middleman implements SellClothes{ 3 private SellClothes t; 4 public Middleman(SellClothes t) { 5 super(); 6 this.t = t; 7 } 8 public void sellClothes() { 9 t.sellClothes(); 10 System.out.println("我是中间商,我买的是制造商的衣服"); 11 System.out.println("我是中间商,我还提供对裤子不合适的进行裁剪服务"); 12 } 13 }
-
SellClothes.java
1 //实际被代理角色 2 public class Manufacturer implements SellClothes{ 3 public void sellClothes() { 4 System.out.println("我是制造商,我提供批发衣服服务"); 5 } 6 }
- Test.java
1 public class Test { 2 public static void main(String[] args) { 3 Manufacturer t = new Manufacturer(); 4 Middleman sellclothes = new Middleman(t); 5 sellclothes.sellClothes(); 6 } 7 }
运行结果:
1 我是制造商,我提供批发衣服服务 2 我是中间商,我买的是制造商的衣服 3 我是中间商,我还提供对裤子不合适的进行裁剪服务
得出结论:
抽象主题角色:Runnable,提供run方法
代理主题角色:Thread类,提供run方法
实际被代理角色:ThreadTest2,也实现了run 方法
这就是代理主题模式,我希望我讲清楚了,哈哈
3. native关键字
在看到start()实现方法的时候,看到了如下一段代码:
1 public synchronized void start() { 2 /** 3 * This method is not invoked for the main method thread or "system" 4 * group threads created/set up by the VM. Any new functionality added 5 * to this method in the future may have to also be added to the VM. 6 * 7 * A zero status value corresponds to state "NEW". 8 */ 9 if (threadStatus != 0) 10 throw new IllegalThreadStateException(); 11 group.add(this); 12 start0(); 13 if (stopBeforeStart) { 14 stop0(throwableFromStop); 15 } 16 } 17 private native void start0();
然后我好奇,native是什么关键字:
一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现
【实现区别】
1. 三段代码
1)第一段:
1 public class ThreadTest6 extends Thread { 2 private int num=3;//定义飞机票的张数 3 public void run(){ 4 for(int i=0;i<10;i++){ 5 if(num>0){ 6 System.out.println(Thread.currentThread().getName()+"飞机票还剩余num= "+num--); 7 } 8 } 9 } 10 public static void main(String[] args){ 11 ThreadTest6 threadtest1=new ThreadTest6(); 12 ThreadTest6 threadtest2=new ThreadTest6(); 13 ThreadTest6 threadtest3=new ThreadTest6(); 14 threadtest1.start(); 15 threadtest2.start(); 16 threadtest3.start(); 17 18 } 19 }
结果:
1 Thread-1飞机票还剩余num= 3 2 Thread-2飞机票还剩余num= 3 3 Thread-2飞机票还剩余num= 2 4 Thread-2飞机票还剩余num= 1 5 Thread-0飞机票还剩余num= 3 6 Thread-0飞机票还剩余num= 2 7 Thread-0飞机票还剩余num= 1 8 Thread-1飞机票还剩余num= 2 9 Thread-1飞机票还剩余num= 1
2)第二段:
1 public class ThreadTest5 implements Runnable { 2 private int num=3;//定义飞机票的张数 3 public void run(){ 4 for(int i=0;i<10;i++){ 5 if(num>0){ 6 System.out.println(Thread.currentThread().getName()+"飞机票还剩余num= "+num--); 7 } 8 } 9 } 10 public static void main(String[] args){ 11 ThreadTest5 threadtest1=new ThreadTest5(); 12 ThreadTest5 threadtest2=new ThreadTest5(); 13 ThreadTest5 threadtest3=new ThreadTest5(); 14 Thread thread1=new Thread(threadtest1,"窗口1"); 15 Thread thread2=new Thread(threadtest2,"窗口2"); 16 Thread thread3=new Thread(threadtest3,"窗口3"); 17 thread1.start(); 18 thread2.start(); 19 thread3.start(); 20 } 21 }
结果:
1 窗口2飞机票还剩余num= 3 2 窗口2飞机票还剩余num= 2 3 窗口2飞机票还剩余num= 1 4 窗口1飞机票还剩余num= 3 5 窗口1飞机票还剩余num= 2 6 窗口1飞机票还剩余num= 1 7 窗口3飞机票还剩余num= 3 8 窗口3飞机票还剩余num= 2 9 窗口3飞机票还剩余num= 1
3)第三段:
1 public class ThreadTest7 implements Runnable { 2 private int num=3;//定义飞机票的张数 3 public void run(){ 4 for(int i=0;i<10;i++){ 5 if(num>0){ 6 System.out.println(Thread.currentThread().getName()+"飞机票还剩余num= "+num--); 7 } 8 } 9 } 10 public static void main(String[] args){ 11 ThreadTest7 threadtest1=new ThreadTest7(); 12 Thread thread1=new Thread(threadtest1,"窗口1"); 13 Thread thread2=new Thread(threadtest1,"窗口2"); 14 Thread thread3=new Thread(threadtest1,"窗口3"); 15 thread1.start(); 16 thread2.start(); 17 thread3.start(); 18 } 19 }
结果:
1 窗口1飞机票还剩余num= 3 2 窗口1飞机票还剩余num= 2 3 窗口1飞机票还剩余num= 1
2. 分析:
第一和第二段代码不管是使用Runnerable实现还是使用Thread实现,从结果可以看出三个线程每个线程均有num这个资源,如果把num看做是飞机票的话,那么每个线程都有三张飞机票。这不是我们实际中要得到的情况。
要实现这个飞机售票程序,我们只能创建一个资源对象,但要创建多个线程去处理同一个资源对象,并且每个线程上所运行的是相同的程序代码。
这就是第三段代码实现的结果,ThreadTest7只创建了一个资源对象——threadtest1。创建了三个线程,每个线程调用的是同一个threadtest1对象中的run()方法,访问的是同一个对象中的变量(num)的实例,这个程序满足了我们的需求。
3. 结论:
可见,实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
【后面的话】
写一篇文章的过程是充满了乐趣的一次奇妙的旅程,有时“柳暗“,有时”花明“。但是当写完的时候如同一次旅行的结束,总会是有收获的。
分享:
- 10层以下,走楼梯
- 左手刷牙
- 公交车读书
- 持续早起早睡
- 出门的时候记得,“伸手要纸钱“——身份证,手机,钥匙,纸巾,钱包。
——TT