Java基础---多线程
来源:互联网 发布:学安卓要先学c语言吗 编辑:程序博客网 时间:2024/04/28 00:30
一、多线程概述
现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式
1、进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
2、一个进程至少有一个线程。
3、线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
4、“同时”执行是人的感觉,在线程之间实际上轮换执行。
二、创建线程的方式
创建线程共有两种方式:继承方式和实现方式(简单的说)。
1、 继承方式
通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继承Thread类,然后复写其run方法的方式来创建线程。
创建步骤:
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
覆盖run方法的原因:
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
程序示例:
[java] view plaincopy
1. /*
2. 小练习
3. 创建两线程,和主线程交替运行。
4. */
5.
6. //创建线程Test
7. class Test extends Thread
8. {
9. // private String name;
10. Test(String name)
11. {
12. super(name);
13. // this.name=name;
14. }
15. //复写run方法
16. public void run()
17. {
18. for(int x=0;x<60;x++)
19. System.out.println(Thread.currentThread().getName()+"..run..."+x);
20.// System.out.println(this.getName()+"..run..."+x);
21. }
22.}
23.
24.class ThreadTest
25. {
26. public static void main(String[] args)
27. {
28. new Test("one+++").start();//开启一个线程
29.
30. new Test("tow———").start();//开启第二线程
31.
32. //主线程执行的代码
33. for(int x=0;x<170;x++)
34. System.out.println("Hello World!");
35. }
36.}
结果:
如图,执行是随机、交替执行的,每一次运行的结果都会不同。
2、 实现方式
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式。
创建步骤:
a,定义类实现Runnable的接口。
b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
e,调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
程序示例:
/* * 1.继承Thread 步骤: * 定义类 继承 Thread * 重写 run 方法 : run() 就是你要执行的任务 * 把新线程要做的事写在 run 方法中 * 在测试类当中, 创建线程对象 * 开启新线程, 内部会自动执行 run 方法 * * 注意: start() 开启. */package cn.itcast_02_impl;/* * */public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(getName() + "--" + i);}}}public class Demo {public static void main(String[] args) {MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();MyThread mt3 = new MyThread();MyThread mt4 = new MyThread();// mt1.run();mt1.start(); // JVM 会自动调用 run() java.lang.IllegalThreadStateExceptionmt2.start(); // JVM 会自动调用 run()mt3.start(); // JVM 会自动调用 run()mt4.start(); // JVM 会自动调用 run()for (int i = 0; i < 10000; i++) {System.out.println("aaaaaaaaaa");}}}
三、两种方式的区别和线程的几种状态
1、两种创建方式的区别
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码存放在接口子类run方法中,创建自定义的 Runnable 的子类对象 R , 创建 new Thread(R) 对象, 传入 R ,调用 thread.start()开启新线程, 内部会自动调用 Runnable 的run()方法 。
2、几种状态
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
图解:
四、线程安全问题
1、导致安全问题的出现的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
简单的说就两点:
a、多个线程访问出现延迟。
b、线程随机性 。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
2、解决办法——同步
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。
a、同步代码块
用法:
synchronized(对象)
{需要被同步的代码}
同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
示例:
</pre><pre name="code" class="java">package cn.itcast_07_synchronized;/* * */public class T2 extends Thread {Printer p;T2() {}T2(Printer p) {this.p = p;}@Overridepublic void run() {p.printB();}}package cn.itcast_07_synchronized;/* * */public class T1 extends Thread {Printer p;T1() {}T1(Printer p) {this.p = p;}@Overridepublic void run() {p.printA();}}public class Printer {Object o = new Object();static {}// synchronized( 任意对象 ){////// }public void printA() {synchronized (this) {for (int i = 0; i < 1000; i++) {System.out.print("a");System.out.print("a");System.out.print("a");System.out.print("a");System.out.print("a");System.out.println();}}}public void printB() {synchronized (this) {for (int i = 0; i < 1000; i++) {System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.print("b");System.out.println();}}}}
b,同步函数
格式:
在函数上加上synchronized修饰符即可。
那么同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
拿同步代码块的示例:
[java] view plaincopy
1. class Ticket implements Runnable
2. {
3. private int tick=100;
4. Object obj = new Object();
5. public void run()
6. {
7. while(true)
8. {
9. show();
10. }
11. }
12. //直接在函数上用synchronized修饰即可实现同步
13. public synchronized void show()
14.{
15. if(tick>0)
16. {
17. try
18. {
19. //使用线程中的sleep方法,模拟线程出现的安全问题
20. //因为sleep方法有异常声明,所以这里要对其进行处理
21. Thread.sleep(10);
22. }
23. catch (Exception e)
24. {
25. }
26. //显示线程名及余票数
27. System.out.println(Thread.currentThread().getName()+"..tick="+tick--);
28. }
29. }
30.}
3、同步的前提
a,必须要有两个或者两个以上的线程。
b,必须是多个线程使用同一个锁。
4、同步的利弊
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
5、如何寻找多线程中的安全问题
a,明确哪些代码是多线程运行代码。
b,明确共享数据。
c,明确多线程运行代码中哪些语句是操作共享数据的。
五、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
经典示例:
[java] view plaincopy
1. /*
2. 加同步的单例设计模式————懒汉式
3. */
4. class Single
5. {
6. private static Single s = null;
7. private Single(){}
8. public static void getInstance()
9. {
10. if(s==null)
11. {
12. synchronized(Single.class)
13. {
14. if(s==null)
15. s = new Single();
16. }
17. }
18. return s;
19. }
20.}
六、死锁
当同步中嵌套同步时,就有可能出现死锁现象。
示例:
[java] view plaincopy
1. /*
2. 写一个死锁程序
3. */
4.
5. //定义一个类来实现Runnable,并复写run方法
6. class LockTest implements Runnable
7. {
8. private boolean flag;
9. LockTest(boolean flag)
10. {
11. this.flag=flag;
12. }
13. public void run()
14. {
15. if(flag)
16. {
17. while(true)
18. {
19. synchronized(LockClass.locka)//a锁
20. {
21. System.out.println(Thread.currentThread().getName()+"------if_locka");
22.
23. synchronized(LockClass.lockb)//b锁
24. {
25. System.out.println(Thread.currentThread().getName()+"------if_lockb");
26. }
27. }
28. }
29. }
30. else
31. {
32. while(true)
33. {
34. synchronized(LockClass.lockb)//b锁
35. {
36. System.out.println(Thread.currentThread().getName()+"------else_lockb");
37.
38. synchronized(LockClass.locka)//a锁
39. {
40. System.out.println(Thread.currentThread().getName()+"------else_locka");
41. }
42. }
43. }
44. }
45. }
46.}
47.
48.//定义两个锁
49. class LockClass
50.{
51. static Object locka = new Object();
52. static Object lockb = new Object();
53. }
54.
55. class DeadLock
56.{
57. public static void main(String[] args)
58. {
59. //创建2个进程,并启动
60. new Thread(new LockTest(true)).start();
61. new Thread(new LockTest(false)).start();
62. }
63. }
结果:程序卡住,不能继续执行
七、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
1、使用同步操作同一资源的示例:
[java] view plaincopy
1. /*
2. 有一个资源
3. 一个线程往里存东西,如果里边没有的话
4. 一个线程往里取东西,如果里面有得话
5. */
6.
7. //资源
8. class Resource
9. {
10. private String name;
11. private String sex;
12. private boolean flag=false;
13.
14. public synchronized void setInput(String name,String sex)
15. {
16. if(flag)
17. {
18. try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
19. }
20. this.name=name;
21. this.sex=sex;
22.
23. flag=true;//表示有资源
24. notify();//唤醒等待
25. }
26.
27. public synchronized void getOutput()
28. {
29. if(!flag)
30. {
31. try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
32. }
33. System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出
34.
35. flag=false;//资源已取出
36. notify();//唤醒等待
37. }
38.}
39.
40.
41. //存线程
42.class Input implements Runnable
43. {
44. private Resource r;
45. Input(Resource r)
46. {
47. this.r=r;
48. }
49. public void run()//复写run方法
50. {
51. int x=0;
52. while(true)
53. {
54. if(x==0)//交替打印张三和王羲之
55. {
56. r.setInput("张三",".....man");
57. }
58. else
59. {
60. r.setInput("王羲之","..woman");
61. }
62. x=(x+1)%2;//控制交替打印
63. }
64. }
65. }
66.
67. //取线程
68.class Output implements Runnable
69. {
70. private Resource r;
71. Output(Resource r)
72. {
73. this.r=r;
74. }
75. public void run()//复写run方法
76. {
77. while(true)
78. {
79. r.getOutput();
80. }
81. }
82.}
83.
84.
85.
86.class ResourceDemo2
87. {
88. public static void main(String[] args)
89. {
90. Resource r = new Resource();//表示操作的是同一个资源
91.
92. new Thread(new Input(r)).start();//开启存线程
93.
94. new Thread(new Output(r)).start();//开启取线程
95. }
96.}
结果:部分截图
几个小问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
2、JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
升级解决方案的示例:
[java] view plaincopy
1. /*
2. 生产者生产商品,供消费者使用
3. 有两个或者多个生产者,生产一次就等待消费一次
4. 有两个或者多个消费者,等待生产者生产一次就消费掉
5.
6. */
7.
8. import java.util.concurrent.locks.*;
9.
10.class Resource
11. {
12. private String name;
13. private int count=1;
14. private boolean flag = false;
15.
16. //多态
17. private Lock lock=new ReentrantLock();
18.
19. //创建两Condition对象,分别来控制等待或唤醒本方和对方线程
20. Condition condition_pro=lock.newCondition();
21. Condition condition_con=lock.newCondition();
22.
23. //p1、p2共享此方法
24. public void setProducer(String name)throws InterruptedException
25. {
26. lock.lock();//锁
27. try
28. {
29. while(flag)//重复判断标识,确认是否生产
30. condition_pro.await();//本方等待
31.
32. this.name=name+"......"+count++;//生产
33. System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
34. flag=true;//控制生产\消费标识
35. condition_con.signal();//唤醒对方
36. }
37. finally
38. {
39. lock.unlock();//解锁,这个动作一定执行
40. }
41.
42. }
43.
44. //c1、c2共享此方法
45. public void getConsumer()throws InterruptedException
46. {
47. lock.lock();
48. try
49. {
50. while(!flag)//重复判断标识,确认是否可以消费
51. condition_con.await();
52.
53. System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
54. flag=false;//控制生产\消费标识
55. condition_pro.signal();
56. }
57. finally
58. {
59. lock.unlock();
60. }
61.
62. }
63. }
64.
65. //生产者线程
66.class Producer implements Runnable
67. {
68. private Resource res;
69. Producer(Resource res)
70. {
71. this.res=res;
72. }
73. //复写run方法
74. public void run()
75. {
76. while(true)
77. {
78. try
79. {
80. res.setProducer("商品");
81. }
82. catch (InterruptedException e)
83. {
84. }
85. }
86. }
87. }
88.
89. //消费者线程
90.class Consumer implements Runnable
91. {
92. private Resource res;
93. Consumer(Resource res)
94. {
95. this.res=res;
96. }
97. //复写run
98. public void run()
99. {
100. while(true)
101. {
102. try
103. {
104. res.getConsumer();
105. }
106. catch (InterruptedException e)
107. {
108. }
109. }
110. }
111.
112.}
113.
114.class ProducerConsumer
115. {
116. public static void main(String[] args)
117. {
118. Resource res=new Resource();
119.
120. new Thread(new Producer(res)).start();//第一个生产线程 p1
121. new Thread(new Consumer(res)).start();//第一个消费线程 c1
122.
123. new Thread(new Producer(res)).start();//第二个生产线程 p2
124. new Thread(new Consumer(res)).start();//第二个消费线程 c2
125. }
126.}
运行结果:部分截图
八、停止线程
在JDK 1.5版本之前,有stop停止线程的方法,但升级之后,此方法已经过时。
那么现在我们该如果停止线程呢?
只有一种办法,那就是让run方法结束。
1、开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
如:run方法中有如下代码,设置一个flag标记。
[java] view plaincopy
1. public void run()
2. {
3. while(flag)
4. {
5. System.out.println(Thread.currentThread().getName()+"....run");
6. }
7. }
那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。
2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();
如:
[java] view plaincopy
1. class StopThread implements Runnable
2. {
3. private boolean flag =true;
4. public void run()
5. {
6. while(flag)
7. {
8. System.out.println(Thread.currentThread().getName()+"....run");
9. }
10. }
11. public void changeFlag()
12. {
13. flag = false;
14. }
15. }
16.
17. class StopThreadDemo
18.{
19. public static void main(String[] args)
20. {
21. StopThread st = new StopThread();
22. Thread t1 = new Thread(st);
23. Thread t2 = new Thread(st);
24. t1.start();
25. t2.start();
26.
27. int num = 0;
28. while(true)
29. {
30. if(num++ == 60)
31. {
32. t1.interrupt();//清除冻结状态
33. t2.interrupt();
34. st.changeFlag();//改变循环标记
35. break;
36. }
37. System.out.println(Thread.currentThread().getName()+"......."+num);
38. }
39. System.out.println("over");
40. }
41. }
结果:
九、什么时候写多线程?
当某些代码需要同时被执行时,就用单独的线程进行封装。
示例:
[java] view plaincopy
1. class ThreadTest
2. {
3. public static void main(String[] args)
4. {
5. //一条线程
6. new Thread()
7. {
8. public void run()
9. {
10. for (int x=0;x<100 ;x++ )
11. {
12. System.out.println(Thread.currentThread().toString()+"....."+x);
13. }
14. }
15. }.start();
16.
17. //又是一条线程
18. Runnable r= new Runnable()
19. {
20. public void run()
21. {
22. for (int x=0;x<100 ;x++ )
23. {
24. System.out.println(Thread.currentThread().getName()+"....."+x);
25. }
26. }
27. };
28. new Thread(r).start();
29.
30. //可以看作主线程
31. for (int x=0;x<1000 ;x++ )
32. {
33. System.out.println("Hello World!");
34. }
35.
36. }
37. }
扩展小知识:
1、join方法
当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。
2、setPriority()方法用来设置优先级
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级
3、yield()方法可以暂停当前线程,让其他线程执行。
- Java基础/Java多线程
- Java基础-多线程基础篇
- java多线程基础
- Java多线程编程基础
- java多线程开发基础
- Java多线程基础
- Java -- 多线程技术基础
- 【java】多线程基础
- Java基础:多线程
- Java语言基础:多线程
- Java语言基础:多线程
- Java语言基础:多线程
- java多线程基础分析
- Java多线程编程基础
- java 多线程基础
- Java基础_多线程
- Java多线程基础
- java多线程基础
- 致自己
- Python笔记——基本控制流
- 《深入理解计算机系统》学习笔记--001
- Unity基础小案例---射击小球
- websphere下log4j不起作用的问题
- Java基础---多线程
- 启动android模拟器时.有时会报The connection to adb is down, and a severe error has occur
- Python笔记——数字及数学函数
- <Error>: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_C
- UFT延长使用时间
- hive jdbc
- java架构师之路:JAVA程序员必看的15本书的电子版下载地址
- Python(七)python下的内部函数库和第三方函数库
- 遥感应用综述