您的位置:首页 > 职场人生

黑马程序员——java笔记(多线程+Runnable接口)-第22天

2015-09-19 19:26 489 查看
----------- android培训java培训java学习型技术博客、期待与您交流! ------------

创建线程方式二:实现Runnable接口

1. 定义类实现Runnable接口。

2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。

3. 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。为什么?
因为线程的任务都封装在Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确要运行的任务。

4. 调用线程对象的start方法开启线程。

实现Runnable接口的好处:

1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,
按照面向对象的思想将任务封装成对象。

2. 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

线程都有自己默认的名称。即:thread-编号。编号从0开始。

示例:
1.
//准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。
2.
//通过接口的形式完成。
3.
class Demo implements Runnable{//任务类,进行接口实现
4.
public void run(){//覆盖run方法
5.
show();
6.
}
7.
public void show(){
8.
for(int x = 0; x < 20; x++){
9.
System.out.println(Thread.currentThread().getName() + "..." +x);
10.// currentThread()返回对当前正在执行的线程对象的引用。
11.//返回该线程的名称。getName()
12. }
13. }
14.}
15.
16.class ThreadDemo{
17. public static voidmain(String[] args){
18. Demod = new Demo();
19. Thread t1 = new Thread(d);
20.//此时Runnable r=new Demo();
21. Thread t2 = new Thread(d);
22. t1.start();
23. t2.start();
24. }
25.}
26.
复制代码
运行结果:

Thread类、Runnable接口内部源码关系模拟代码:
1.
class Thread{
2.
private Runnable r ;
3.
Thread(){
4.
}
5.
Thread(Runnable r){
6.
this.r = r;
7.
}
8.

9.
public void run(){
10. if(r!=null)
11. r.run();
12. }
13. public void
start(){
14. run();
15. }
16.}
17.
18.class ThreadImpl implements Runnable{
19. public void run(){
20. System.out.println("runnable run" );
21. }
22.}
23.
24.class ThreadDemo4{
25. public static voidmain(String[] args){
26. ThreadImpl i = new ThreadImpl();
27. Thread t = new Thread(i);
28. t.start();
29. }
30.}
31.
32.class SubThread extends Thread{
33. public void run(){
34. System.out.println("hahah" );
35. }
36.}
37.
38.class ThreadDemo5{
39. public static voidmain(String[] args){
40. SubThread s = new SubThread();
41. s.start();
42. }
43.}
44.
复制代码

自定义线程名称。
5.2
线程安全问题


5.2.1 线程安全问题产生的原因

需求:模拟4个线程同时卖100张票。

代码:
1.
class Ticket implements Runnable{
2.
private int num = 100;
3.

4.
public void run(){
5.
while(true ){
6.
if(num > 0){
7.
try{
8.
Thread. sleep(10);//线程运行time=10后,冻结线程。线程睡觉。
9.
} catch(InterruptedExceptione){
10. e.printStackTrace();
11. }
12. System.out.println(Thread.currentThread().getName() +"...sale..." + num--);
13. }
14. }
15. }
16.}
17.
18.class TicketDemo{
19. public static voidmain(String[] args){
20. Ticket t = new Ticket();
21. Thread t1 = new Thread(t);
22. Thread t2 = new Thread(t);
23. Thread t3 = new Thread(t);
24. Thread t4 = new Thread(t);
25.
26. t1.start();
27. t2.start();
28. t3.start();
29. t4.start();
30. }
31.}
32.
复制代码
运行结果:

……

原因分析:

出现上图安全问题的原因在于Thread-0通过了if判断后,在执行到“num--”语句之前,
num此时仍等于1。 CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“num--”的操作,因而出现了0、-1、-2的情况。

线程安全问题产生的原因:

1. 多个线程在操作共享的数据。

2. 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
5.2.2 线程安全问题的解决方案

思路:

就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。将共享数据的线程代码封装。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;

}

同步的好处:解决了线程的安全问题。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

同步的前提:必须有多个线程并使用同一个锁。

修改后代码:

1.
class Ticket implements Runnable{
2.
private intnum = 100;
3.
Object obj = new Object();//新创建一个对象
4.

5.
public voidrun(){
6.
while(true ){
7.
synchronized(obj ){//同步对象锁
8.
if(num> 0){
9.
System.out.println(Thread.currentThread().getName() +"...sale..." + num--);
10. }
11. }
12. }
13. }
14.}
15.
16.class TicketDemo{
17. public staticvoid main(String[] args){
18. Ticket t = new Ticket();
19. Thread t1 = new Thread(t);
20. Thread t2 = new Thread(t);
21. Thread t3 = new Thread(t);
22. Thread t4 = new Thread(t);
23.
24. t1.start();
25. t2.start();
26. t3.start();
27. t4.start();
28. }
29.}
30.
复制代码
运行结果:

……

原因分析:

上图显示安全问题已被解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。
因此,当num=1时,CPU切换到某个线程后,如上图的Thread-3线程,其他线程将无法通过同步代码块继而进行if判断语句,只有等到Thread-3线程执行完“num--”操作(此后num的值为0),并且跳出同步代码块后,才能抢到锁。其他线程即使抢到锁,然而,此时num值已为0,也就无法通过if语句判断,从而无法再执行“num--”的操作了,也就不会出现0、-1、-2等情况了。

利用同步代码块解决安全问题案例:

需求:储户,两个,每个都到银行存钱,每次存100,共存三次。

代码:

1.
class Bank{
2.
private intsum ;
3.
public voidadd(int num){
4.
synchronized(this ){//同步函数,默认this
5.
sum = sum + num;
6.
System. out.println("sum =" + sum);
7.
}
8.
}
9.
}
10.
11.class Cus
implements Runnable{//1,实现Runnable
12. private
Bank b = new Bank();//创建bank类
13. public voidrun(){//2,覆盖run
14. for(int x = 0; x < 3; x++){
15. b.add(100);
16. }
17. }
18.}
19.
20.class BankDemo{
21. publicstatic void main(String[] args){
22. Cus c = new Cus();
23. Thread t1 = new Thread(c);//Cus作为Runnable的参数传递给Thread
24. Thread t2 = new Thread(c);
25. t1.start();
26. t2.start();
27. }
28.}
29.
复制代码
运行结果:

原因分析:

由如下代码中可以看到,同步代码块中的语句,存在可能有多个线程同时操作共享数据(sum)的情况,通过同步代码块即可解决存在的安全问题。

如果不设置同步代码块,出现的结果如下:

安全问题的另一种解决方案:同步代码块

格式:在函数上加上synchronized修饰符即可。
示例:
1.
class Bank{
2.
private intsum ;
3.
public synchronized void add(int num){ //同步函数
4.
sum = sum + num;
5.
System.out.println("sum = " + sum);
6.
}
7.
}
8.

复制代码
运行结果:

P.S.
同步函数和同步代码块的区别:
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。

建议使用同步代码块。

由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

示例:
1.
class Ticket implements Runnable{
2.
private int num = 100;
3.
boolean flag = true;
4.

5.
public void run(){
6.
if(flag ){
7.
while(true ){
8.
synchronized(this ){
9.
if(num > 0){
10. try{
11. Thread. sleep(10);
12. } catch(InterruptedException e){
13. e.printStackTrace();
14. }
15. System.out.println(Thread.currentThread().getName()+ "...obj..." + num--);
16. }
17. }
18. }
19. }else
20. while(true )
21. show();
22. }
23.
24. public
synchronized void show(){
25. if(num > 0){
26. try{
27. Thread. sleep(10);
28. } catch(InterruptedException e){
29. e.printStackTrace();
30. }
31. System.out.println(Thread.currentThread().getName() +"...function..." + num--);
32. }
33. }
34.}
35.
36.class SynFunctionLockDemo{
37. public static voidmain(String[] args){
38. Ticket t = new Ticket();
39. Thread t1 = new Thread(t);
40. Thread t2 = new Thread(t);
41.
42. t1.start();
43. try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false,那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行,实验就达不到目的了。
44. Thread. sleep(10);
45. }catch(InterruptedException e){
46. e.printStackTrace();
47. }
48. t. flag = false ;
49. t2.start();
50. }
51.}
52.
复制代码
运行结果:

……

静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

示例:
1.
class Ticket implements Runnable{
2.
private static int num = 100;
3.
Object obj = new Object();
4.
boolean flag = true;
5.

6.
public void run(){
7.
if(flag ){
8.
while(true ){
9.
synchronized(Ticket.class){//this.getClass()
10. if(num > 0){
11. try{
12. Thread. sleep(10);
13. } catch(InterruptedException e){
14. e.printStackTrace();
15. }
16. System.out.println(Thread.currentThread().getName()+ "...obj..." + num--);
17. }
18. }
19. }
20. }else
21. while(true )
22. show();
23. }
24.
25. public static
synchronized void show(){
26. if(num > 0){
27. try{
28. Thread. sleep(10);
29. } catch(InterruptedException e){
30. e.printStackTrace();
31. }
32. System.out.println(Thread.currentThread().getName() +"...function..." + num--);
33. }
34. }
35.}
36.
37.class SynFunctionLockDemo{
38. public static voidmain(String[] args){
39. Ticket t = new Ticket();
40. Thread t1 = new Thread(t);
41. Thread t2 = new Thread(t);
42.
43. t1.start();
44. try{
45. Thread. sleep(10);
46. }catch(InterruptedException e){
47. e.printStackTrace();
48. }
49. t.flag = false ;
50. t2.start();
51. }
52.}
53.
复制代码
运行结果:

……

5.2.3
多线程下的单例模式


饿汉式:
1.
class Single{
2.
private static
final Single s = newSingle();
3.
private Single(){}
4.
public static SinglegetInstance(){
5.
return s ;
6.
}
7.
}
8.

复制代码
P.S.

饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

懒汉式:
1.
class Single{
2.
private static Single s = null;
3.
private Single(){}
4.
public static SinglegetInstance(){
5.
if(s==null){
6.
synchronized(Single.class){
7.
if(s == null)
8.
s = new Single();
9.
}
10. }
11. return s ;
12. }
13.}
14.
复制代码

P.S.
懒汉式存在安全问题,可以使用同步函数解决。
但若直接使用同步函数,则效率较低,因为每次都需要判断。

但若采取如下方式,即可提升效率。

原因在于任何一个线程在执行到第一个if判断语句时,如果Single对象已经创建,则直接获取即可,而不用判断是否能够获取锁,相对于上面使用同步函数的方法就提升了效率。如果当前线程发现Single对象尚未创建,则再判断是否能够获取锁。
1. 如果能够获取锁,那么就通过第二个if判断语句判断是否需要创建Single对象。因为可能当此线程获取到锁之前,已经有一个线程创建完Single对象,并且放弃了锁。此时它便没有必要再去创建,可以直接跳出同步代码块,放弃锁,获取Single对象即可。如果有必要,则再创建。
2. 如果不能获取到锁,则等待,直至能够获取到锁为止,再按步骤一执行。

5.2.4 死锁示例

死锁常见情景之一:同步的嵌套。
示例1:
1.
class Ticket implements Runnable{
2.
private static int num = 100;
3.
Object obj = new Object();
4.
boolean flag = true;
5.

6.
public void run(){
7.
if(flag ){
8.
while(true ){
9.
synchronized(obj ){
10. show();
11. }
12. }
13. }else
14. while(true )
15. show();
16. }
17.
18. public
synchronized void show(){
19.
synchronized(obj ){
20. if(num > 0){
21. try{
22. Thread. sleep(10);
23. } catch(InterruptedExceptione){
24. e.printStackTrace();
25. }
26. System.out.println(Thread.currentThread().getName() +"...function..." + num--);
27. }
28. }
29. }
30.}
31.
32.class DeadLockDemo{
33. public static voidmain(String[] args){
34. Ticket t = new Ticket();
35. Thread t1 = new Thread(t);
36. Thread t2 = new Thread(t);
37.
38. t1.start();
39. try{
40. Thread. sleep(10);
41. }catch(InterruptedException e){
42. e.printStackTrace();
43. }
44. t.flag = false ;
45. t2.start();
46. }
47.}
48.
复制代码
运行结果:

原因分析:

由上图可以看到程序已经被锁死,无法向下执行。

由下图代码可以看到,run方法中的同步代码块需要获取obj对象锁,才能执行代码块中的show方法。

而执行show方法则必须获取this对象锁,然后才能执行其中的同步代码块。

当线程t1获取到obj对象锁执行同步代码块,线程t2获取到this对象锁执行show方法。同步代码块中的show方法因无法获取到this对象锁无法执行,show方法中的同步代码块因无法获取到obj对象锁无法执行,就会产生死锁。

示例2:

1.
class Test implements Runnable{
2.
privateboolean flag ;
3.
Test(boolean flag){
4.
this.flag= flag;
5.
}
6.

7.
public voidrun(){
8.
if(flag ){
9.
while(true )
10.
synchronized(MyLock.locka){
11. System.out.println(Thread.currentThread().getName() +"...if locka...");
12.
synchronized(MyLock.lockb){
13. System.out.println(Thread.currentThread().getName() + "...iflockb...");
14. }
15. }
16. } else{
17. while(true )
18.
synchronized(MyLock.lockb){
19. System.out.println(Thread.currentThread().getName() +"...else lockb...");
20.
synchronized(MyLock.locka){
21. System.out.println(Thread.currentThread().getName() + "...elselocka...");
22. }
23. }
24. }
25. }
26.}
27.
28.class MyLock{
29. publicstatic
final Object locka = new Object();
30. publicstatic
final Object lockb = new Object();
31.}
32.
33.class DeadLockDemo{
34. publicstatic void main(String[] args){
35. Test a = new Test(true );
36. Test b = new Test(false );
37.
38. Thread t1 = new Thread(a);
39. Thread t2 = new Thread(b);
40.
41. t1.start();
42. t2.start();
43. }
44.}
45.
复制代码
运行结果:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: