1.概述
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。一个进程中至少有一个线程。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
多线程存在的意义:充分利用cpu的空闲时间,提高进程的整体运行效率。
2.创建线程的两种方式
第一种:继承Thread类
步骤:
1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2.建立子类对象的同时线程也被创建。
3.通过调用start方法开启线程。
示例代码:
[java] view plaincopy
1.class Demo extends Thread
2.{
3. public void run()
4. {
5. for(int i = 0;i<60;i++)
6. {
7. System.out.println(“demo run---+”i);
8. }
9. }
10.}
11.
12.class ThreadDemo
13.{
14. public static void main(String[] args)
15. {
16. Demo d = new Demo();//创建一个线程
17. d.start();//调用start方法执行该线程的run方法
18. }
19.}
第二种:实现Runnable接口
步骤:
1.子类覆盖接口中的run方法
2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3.Thread类对象调用start方法开启线程。
代码示例:
[java] view plaincopy
1.class Test implement Runnable
2.{
3. public void run()//子类覆盖接口中的run方法
4. {
5. for(int x=0; x<60; x++)
6. {
7. System.out.println(“run..."+x);
8. }
9. }
10.
11.}
12.
13.
14.class ThreadTest
15.{
16. public static void main(String[] args)
17. {
18. Test te = new Test();实现Runnable接口的对象
19. Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数
20. Thread t2 = new Thread(te);
21. t1.start();//调用Thread类的start方法开启线程
22. t2.start();
23. }
24.}
两种线程创建方式的区别:
继承Thread类创建对象:
1.Thread子类无法再从其它类继承(java语言单继承)。
2.编写简单,run()方法的当前对象就是线程对象,可直接操作。
使用Runnable接口创建线程:
1.可以将CPU,代码和数据分开,形成清晰的模型。
2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。
3.有利于保持程序的设计风格一致。
继承Thread类线程代码存放于Thread子类run方法中;
实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。
问题:为什么要将Runnable接口的子类对象传递给Thread的构造函数?
答:因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象。
3.线程的四种状态
sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。
4.线程的对象获取与名称的操作
线程都有自己的默认名称,格式为:Thread-编号 该编号从0开始。
操作方法:
static Thread currentThread():获取当前线程对象。
getName(): 获取线程名称。
setName或者构造函数:设置线程名称。
示例代码:
[java] view plaincopy
1.class Test extends Thread
2.{
3. Test(String name)//构造方法设置线程名称
4. {
5. super(name);//继承父类方法
6. }
7. public void run()//执行代码
8. {
9. for(int x=0; x<60; x++)
10. {
11. System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
12. }
13. }
14.
15.}
16.
17.class ThreadTest
18.{
19. public static void main(String[] args)
20. {
21. Test t1 = new Test("one---");//调用构造函数设置线程名称
22. Test t2 = new Test("two+++");
23. t1.start();
24. t2.start();
25.
26. for(int x=0; x<60; x++)
27. {
28. System.out.println("main....."+x);
29. }
30. }
31.}
5.线程的安全
导致安全问题的出现的原因:
1.多个线程访问出现延迟
2.线程随机性
线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的
解决方法:同步(synchronized)
格式:
synchronized(对象)
{
需要同步的代码;
}
同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。
4个窗口售票示例:
[java] view plaincopy
1.class Ticket implements Runnable
2.{
3. private int tick = 1000;
4. Object obj = new Object();//建立一个obj类对象,供synchronized作参数
5. public void run()
6. {
7. while(true)
8. {
9. synchronized(obj)//同步锁
10. {
11. if(tick>0)
12. System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
13.
14. }
15. }
16. }
17.}
18.
19.class TicketDemo2
20.{
21. public static void main(String[] args)
22. {
23.
24. Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用
25.
26. Thread t1 = new Thread(t);
27. Thread t2 = new Thread(t);
28. Thread t3 = new Thread(t);
29. Thread t4 = new Thread(t);
30. //开始4个线程
31. t1.start();
32. t2.start();
33. t3.start();
34. t4.start();
35.
36. }
37.}
同步的前提:
|---->同步需要两个或者两个以上的线程
|---->多个线程使用的是同一个锁
未满足这两个条件,不能称其同步。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。
同步函数
格式:在函数上加上synchronized修饰符即可。
同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。
所以同步函数使用的锁是this。
关于死锁
同步中嵌套同步,但是锁却不同,就会产生死锁
如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁
[java] view plaincopy
1.class Dead implements Runnable
2.{
3. private booleanb = false;
4. Dead(booleanb)
5. {
6. this.b = b;
7. }
8. public void run()
9. {
10. while(true)
11. {
12. if(b)
13. {
14. synchronized(Locks.locka)
15. {//0线程,持有了A锁
16. System.out.println(Thread.currentThread().getName()+"locka...+..if");
17. //等待B锁
18. synchronized(Locks.lockb)
19. {
20. System.out.println(Thread.currentThread().getName()+"lockb...+..if");
21. }
22. }
23. }
24. else
25. {
26. synchronized(Locks.lockb)
27. {
28. //1线程就进来了,持有了B锁
29. System.out.println(Thread.currentThread().getName()+"lockb...+..else");
30. synchronized(Locks.locka)//等待获得A锁
31. {
32. System.out.println(Thread.currentThread().getName()+"locka...+..else");
33.
34. }
35. }
36. }
37. }
38. }
39.}
40.//创造锁
41.class Locks
42.{
43. public static Object locka = new Object();
44. public static Object lockb = new Object();
45.}
46.class DeadLock
47.{
48. public static void main(String[]args)
49. {
50. Dead d1 = new Dead(true);
51. Dead d2 = new Dead(false);
52. Thread t1 = new Thread(d1);
53. Thread t2 = new Thread(d2);
54. t1.start();
55. t2.start();
56. }
57.}
6.线程间通信
多个线程操作同一个资源,但是操作的动作不同
注意:wait();notify();notifyAll()都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,因为只有同步才具有锁。
问题:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
1.这些方法存在与同步中。
2.使用这些方法时必须要标识所属的同步的锁。
3.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
注意:不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。
问题:wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
新特性:JDK1.5之后新增了java.until.concurrent.locks这个包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。
生产和消费实例中升级:
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
该对象可以Lock锁 进行获取。
该示例中,实现了本方只唤醒对方操作。
代码如下:
[java] view plaincopy
1.import java.util.concurrent.locks.*;
2.
3.class ProducerConsumerDemo
4.{
5. public static void main(String[] args)
6. {
7. Resource r = new Resource();
8.
9. Producer pro = new Producer(r);
10. Consumer con = new Consumer(r);
11.
12. Thread t1 = new Thread(pro);
13. Thread t2 = new Thread(pro);
14. Thread t3 = new Thread(con);
15. Thread t4 = new Thread(con);
16.
17. t1.start();
18. t2.start();
19. t3.start();
20. t4.start();
21.
22. }
23.}
24.
25.class Resource
26.{
27. private String name;
28. private int count = 1;
29. private boolean flag = false;
30. private Lock lock = new ReentrantLock();
31.
32. private Condition condition_pro = lock.newCondition();
33. private Condition condition_con = lock.newCondition();
34.
35.
36. public void set(String name)throws InterruptedException
37. {
38. lock.lock();
39. try
40. {
41. while(flag)
42. condition_pro.await();
43. this.name = name+"--"+count++;
44.
45. System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
46. flag = true;
47. condition_con.signal();
48. }
49. finally
50. {
51. lock.unlock();//释放锁的动作一定要执行。
52. }
53. }
54. public void out()throws InterruptedException
55. {
56. lock.lock();
57. try
58. {
59. while(!flag)
60. condition_con.await();
61. System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
62. flag = false;
63. condition_pro.signal();
64. }
65. finally
66. {
67. lock.unlock();
68. }
69.
70. }
71.}
72.
73.class Producer implements Runnable
74.{
75. private Resource res;
76.
77. Producer(Resource res)
78. {
79. this.res = res;
80. }
81. public void run()
82. {
83. while(true)
84. {
85. try
86. {
87. res.set("+商品+");
88. }
89. catch (InterruptedException e)
90. {
91. }
92.
93. }
94. }
95.}
96.
97.class Consumer implements Runnable
98.{
99. private Resource res;
100.
101. Consumer(Resource res)
102. {
103. this.res = res;
104. }
105. public void run()
106. {
107. while(true)
108. {
109. try
110. {
111. res.out();
112. }
113. catch (InterruptedException e)
114. {
115. }
116. }
117. }
118.}
7.停止线程
1.定义循环结束标记(run方法结束)
因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
特殊情况:
当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
测试代码:
[java] view plaincopy
1.class StopThread implements Runnable
2.{
3. private boolean flag =true;
4. public synchornized void run()
5. {
6. while(flag)
7. {
8. try
9. {
10. wait();//调用线程等待
11. }
12. catch(InterruptedException e)
13. {
14. System.out.println(Thread.currentThread().getName()+"...Exception");
15. flag = false;//在异常中控制线程的运行
16. }
17. System.out.println(Thread.currentThread().getName()+"....run");
18. }
19. }
20. public void changeFlag()
21. {
22. flag = false;
23. }
24.}
25.
26.class StopThreadDemo
27.{
28. public static void main(String[] args)
29. {
30. StopThread st = new StopThread();
31.
32. Thread t1 = new Thread(st);
33. Thread t2 = new Thread(st);
34.
35.
36. t1.setDaemon(true);
37. t2.setDaemon(true);
38. t1.start();
39. t2.start();
40.
41. int num = 0;
42.
43. while(true)
44. {
45. if(num++ == 60)
46. {
47. //st.changeFlag();
48. t1.interrupt();//调用interrupt方法来中断线程,抛出异常
49. t2.interrupt();
50. break;
51. }
52. System.out.println(Thread.currentThread().getName()+"......."+num);
53. }
54. System.out.println("over");
55. }
56.}
8.线程类的其他方法
守护线程
setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。
注意:该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
用法示例代码如下:
[java] view plaincopy
1.class StopThread implements Runnable
2.{
3. public void run()//原本run方法是一个死循环,但是将线程定义为守护线程后,主线程结束,访问run方法的线程立即也结束了
4. {
5. while(true)
6. {
7. System.out.println(Thread.currentThread().getName()+"....run");
8. }
9. }
10.}
11.class StopThreadDemo
12.{
13. public static void main(String[] args)
14. {
15. StopThread st = new StopThread();
16. Thread t1 = new Thread(st);
17. Thread t2 = new Thread(st);
18. //将t1,t2定义为守护线程
19. t1.setDaemon(true);
20. t2.setDaemon(true);
21. t1.start();//开始线程
22. t2.start();
23. int num = 0;
24. while(true)
25. {
26. if(num++ == 60)
27. {
28. break;
29. }
30. System.out.println(Thread.currentThread().getName()+"......."+num);
31. }
32. System.out.println("over");//主线程结束,t1,t2也结束
33. }
34.}
join()
抢夺cpu执行权,适用于临时加入线程用,先执行完,其他线程才执行。
测试代码如下:
[java] view plaincopy
1.class Demo implements Runnable
2.{
3. public void run()
4. {
5. for(int x=0; x<70; x++)
6. {
7. System.out.println(Thread.currentThread().getName()+"....."+x);
8. }
9. }
10.}
11.class JoinDemo
12.{
13. public static void main(String[] args) throws Exception
14. {
15. Demo d = new Demo();
16. Thread t1 = new Thread(d);
17. Thread t2 = new Thread(d);
18. t1.start();
19. t1.join();//t1获取cpu的执行权,主线程处于冻结状态,只有t1结束主线程才能恢复运行状态
20. t2.start();
21. //t1.join();主线程冻结,t1,t2交替运行,t1结束,主线程才继续
22. for(int x=0; x<80; x++)
23. { }
24. System.out.println("over");
25. }
26.}
setPriority()
更改线程的优先级。三个优先级分别为:
MAX_PRIORITY(最高优先级,10)
MIN_PRIORITY(最低优先级,1)
NORM_PRIORITY(默认优先级,5)
首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。
yeild()
暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行)
个人总结:
线程的运行状态与线程之间的通信个人很难理解,可能还得多多写代码操练才行,线程的操作给我的感觉就是你必须要考虑得很全面,每个线程的执行状态,每个线程读取的数据什么的都必须要考虑清楚,这要才能避免线程的安全问题。还是觉得线程这一块比较难,得多下点工夫。
黑马程序员 :反射
最新推荐文章于 2023-09-06 11:03:12 发布