zoukankan      html  css  js  c++  java
  • Java多线程技术学习笔记(一)

    目录:

    1. 概述
    2. 多线程的好处与弊端
    3. JVM中的多线程解析
    4. 多线程的创建方式之一:继承Thread类
    5. 线程的状态
    6. 多线程创建的方式之二:实现Runnable接口
    7. 使用方式二创建多线程的好处
    8. 多线程示例
    9. 线程安全问题现象
    10. 线程安全问题产生的原因
    11. 同步代码块
    12. 同步的好处与弊端
    13. 同步的前提
    14. 同步函数
    15. 验证同步函数的锁
    16. 单例模式的线程安全问题的解决方案
    17. 死锁示例

    一、概述 目录

    首先得了解进程,打开我们电脑的windows资源管理器,可以直观看到进程的样子:

    进程直观上理解就是正在进行的程序。而每个进程包含一个或者多个线程。也就是说一个进程是由若干线程组成的,在程序执行期间,真正执行的是线程,而进程只是负责给该进程中的线程分配执行路径,

    所以,线程就是进程中负责程序执行的控制单元(执行路径),一个进程可以有多个执行路径,称为多线程。就像我们再使用QQ给多个好友聊天一样,每一个聊天过程都是一个线程,这些线程都属于QQ这个进程。

    而开启多线程就是为了同时运行多部分代码。每一线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

    二、多线程的好处与弊端 目录

    上一部分说到多线程是为了同时运行多部分代码,但是对于一个cpu而言,在每一个时刻只能执行一个线程,它会在不同线程之间快速切换,由于切换速度很快,所以感觉上去像是多个线程在"同时"执行,现在虽然出现多核技术核数是几乎不可能多过线程数的,所以仍然需要cpu不断在多个线程之间切换,以提高cpu的利用效率。 然而,但是每一个线程都需要一定的内存空间去执行,线程一多,内存空间不足,就会使得电脑显得特别卡,这就是多线程的弊端。注意到cpu在线程之间的切换是随机的。

    三、JVM中的多线程解析 目录

    JVM启动时就启动了多个线程,至少有两个线程可以分析出来:一个是执行main函数的线程(也称为主线程),另一个是负责垃圾回收的线程。

    在JVM垃圾回收方法是finalize方法,该方法由垃圾回收器来调用,而gc() 方法是用来运行垃圾回收器的:

    下面展示主线程和垃圾回收线程的运行:

    复制代码
     1 package thread.demo;
     2 
     3 class Demo extends Object{
     4     public void finalize(){
     5         System.out.println("demo ok");
     6     }
     7 }
     8 public class ThreadDemo_1 {
     9 
    10     /**
    11      * @param args
    12      */
    13     public static void main(String[] args) {
    14         // TODO Auto-generated method stub
    15         new Demo();
    16         new Demo();
    17         System.gc();
    18         new Demo();
    19         System.out.println("Hello World!"); 
    20     }
    21 
    22 }
    复制代码

    运行结果:

    Hello World!
    demo ok
    demo ok

    可以发现,虽然第19行在第17行代码(垃圾回收)之前,但是第19行代码却先执行,怎么回事呢?因为垃圾回收(第17行)是由垃圾回收线程执行,而第19行代码主线程的部分,cpu从主线程开始执行,然后在主线程和垃圾回收线程之间切换,创建完两个Demo()对象之后,虽然我们调用垃圾回收器,但是垃圾回收程序还没来得及执行,cpu切换到了主线程,于是先打印出了“Hello World!”

    于是虽然第19行代码,执行完了,看似整个程序都执行完了,但是JVM(Java 虚拟机)还没有结束,即虽然主线程结束了,但是JVM还要执行垃圾回收线程。 

    四、多线程创建方式之一:继承Thread类 目录

    首先看一看一个简单的打印程序:

     View Code

    运行结果:

     View Code

    这时显示的只是主线程运行的结果,很容易理解!下面通过一种方式,让“旺财”和“xiaoqiang”的打印能够分别运行在不同的线程中,首先查看java的API文档:

    翻译:线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地执行多个执行线程。 

    接着文档中给出了创建线程的方法:

    总结出来就是继承Thread类然后重写Thread类中的run()方法,run方法中的代码就是线程要执行的代码,然后调用start方法开启一个线程。

     View Code

    运行结果:

     View Code

    可以看出,运行结果正如上面分析的一样,cpu在多个线程之间随机切换,于是打印出的结果与上面只有主线程时结果差别很大。

    下面我们让主线程参与进来,即同时看看线程d1,d2和主线程的运行结果:

    复制代码
     1 package thread.demo;
     2 class Demo_2 extends Thread {
     3     private String name;
     4     Demo_2(String name){
     5         this.name = name;
     6     }
     7     public void run() {
     8         show();
     9     }
    10     public void show(){
    11         for (int x = -99999999; x < 99999999; x++){};
    12         for (int i = 0; i < 10; i++){
    13             System.out.println(name + "...i" + i);
    14         }
    15     }
    16 }
    17 public class ThreadDemo_2 extends Thread {
    18 
    19     /**
    20      * @param args
    21      */
    22     public static void main(String[] args) {
    23         /* 创建线程的目的是为了开启一条执行路径,去运行指定代码和
    24         其他代码实现同时运行,而运行的指定代码就是这个执行路径的
    25         任务。 JVM创建的主线程的任务都定义在主函数中。
    26         而自定义的线程它的任务:
    27         Thread类用于描述线程,线程需要任务,所以Thread类也有对
    28         任务的描述,这个任务就通过Thread类中的run方法来体现的。
    29         也就是说,run方法就是封装自定义线程任务的函数。
    30         run方法中定义的就是线程要运行的任务代码!!!
    31         
    32         开启线程是为了运行指定代码,所以只有继承Thread类,并重写run方法。
    33         并将运行代码定义在run方法中即可
    34         */
    35         Demo_2 d1 = new Demo_2("旺财");
    36         Demo_2 d2 = new Demo_2("xiaoqiang");
    37         d1.start();
    38         d2.start();
    39         System.out.println("over");
    40     }
    41 
    42 }
    复制代码

    运行结果:

    复制代码
    over
    xiaoqiang...i0
    旺财...i0
    xiaoqiang...i1
    旺财...i1
    xiaoqiang...i2
    旺财...i2
    xiaoqiang...i3
    xiaoqiang...i4
    旺财...i3
    xiaoqiang...i5
    旺财...i4
    xiaoqiang...i6
    旺财...i5
    xiaoqiang...i7
    旺财...i6
    xiaoqiang...i8
    旺财...i7
    xiaoqiang...i9
    旺财...i8
    旺财...i9
    复制代码

    多次执行,会发现显示结果一直变化,这就是多个线程随机占用cpu的结果。 当然,如果想看到到底是哪个线程正在执行,可以调用Thread中的currentThread().getName()方法,其中currentThread()是用来返回当前的线程对象,然后用线程对象继续调用getName()就是返回当前线程的名字。程序如下:

    复制代码
     1 package thread.demo;
     2 class Demo_2 extends Thread {
     3     private String name;
     4     Demo_2(String name){
     5         this.name = name;
     6     }
     7     public void run() {
     8         show();
     9     }
    10     public void show(){
    11         for (int x = -99999999; x < 99999999; x++){};
    12         for (int i = 0; i < 10; i++){
    13             System.out.println(name + "...i" + "...name: " + getName());
    14         }
    15     }
    16 }
    17 public class ThreadDemo_2 extends Thread {
    18 
    19     /**
    20      * @param args
    21      */
    22     public static void main(String[] args) {
    23         /* 创建线程的目的是为了开启一条执行路径,去运行指定代码和
    24         其他代码实现同时运行,而运行的指定代码就是这个执行路径的
    25         任务。 JVM创建的主线程的任务都定义在主函数中。
    26         而自定义的线程它的任务:
    27         Thread类用于描述线程,线程需要任务,所以Thread类也有对
    28         任务的描述,这个任务就通过Thread类中的run方法来体现的。
    29         也就是说,run方法就是封装自定义线程任务的函数。
    30         run方法中定义的就是线程要运行的任务代码!!!
    31         
    32         开启线程是为了运行指定代码,所以只有继承Thread类,并重写run方法。
    33         并将运行代码定义在run方法中即可
    34         */
    35         Demo_2 d1 = new Demo_2("旺财");
    36         Demo_2 d2 = new Demo_2("xiaoqiang");
    37         d1.start();
    38         d2.start();
    39         System.out.println("over" + "..." + Thread.currentThread().getName());
    40     }
    41 
    42 }
    复制代码

    运行结果:

    复制代码
    over...main
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    xiaoqiang...i...name: Thread-1
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    旺财...i...name: Thread-0
    复制代码

    注意主线程的名字是固定的就是main,然后自定义的线程从0开始编号。

    五、线程的状态 目录

     

    六、多线程创建的方式之二:实现Runnable接口 目录

    首先来读一下API文档关于Runnable的描述:

    划红线部分:Runnable接口由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法。于是采用实现接口的方式来实现多线程:

    • 定义Runnable接口
    • 覆盖Runnable接口中的run方法,将线程任务代码封装到run方法中。
    • 通过Thread类创建对线,并将Runnable接口中的子类对象作为参数传递给Thread类的构造函数。因为线程任务都封装在Runnable接口子类对象的run方法中,所以在创建时必须要明确要运行的任务,如果不传递这个对象,Thread类会运行它自己的run方法,而不是我们定义在接口中的方法。
    • 调用线程的start方法开启线程。

    代码如下:

    复制代码
     1 package thread.demo;
     2 //通过实现接口的方式实现多线程创建
     3 class Demo_3 implements Runnable {
     4     public void run() {
     5         show();
     6     }
     7     public void show(){
     8         for (int x = -99999999; x < 99999999; x++){};
     9         for (int i = 0; i < 10; i++){
    10             System.out.println(Thread.currentThread().getName());
    11         }
    12     }
    13 }
    14 
    15     public class ThreadDemo_3 extends Thread {
    16 
    17         /**
    18          * @param args
    19          */
    20         public static void main(String[] args) {
    21             //创建一个Runnable接口的子类对象
    22             Demo_3 d = new Demo_3(); 
    23             //将上述Runnable接口的子类对象传入Thread构造函数,
    24             //创建线程
    25             Thread t1 = new Thread(d);
    26             Thread t2 = new Thread(d);
    27             t1.start();
    28             t2.start();
    29     }
    30 
    31 }
    复制代码

    运行结果:

    复制代码
    Thread-1
    Thread-0
    Thread-1
    Thread-0
    Thread-1
    Thread-0
    Thread-0
    Thread-1
    Thread-0
    Thread-1
    Thread-0
    Thread-1
    Thread-0
    Thread-1
    Thread-0
    Thread-1
    Thread-1
    Thread-1
    Thread-0
    Thread-0
    复制代码

    同样可以看见两个线程在随机切换执行。

    细节:通过阅读API文档,可以发现,Thread类里面定义了自己的run方法,而Runnable也有run方法,而Thread的构造方法包含着下面两种(当然不止两种):

    第一种构造方法不包含任何参数,那么在使用Thread创建的线程对象在运行时,就调用Thread类自己的run方法,如果传入一个Runnable子类对象,那么在使用Thread类创建对象时,运行的任务就是Runnable接口中定义run方法。原理用代码简单解释如下:

    复制代码
     1 package thread.demo;
     2 
     3 class Thread {
     4     private Runnable r;
     5     Thread() {}
     6     Thread(Runnable) {
     7         this.r = r;
     8     }
     9     
    10     public void run() {
    11         r.run();
    12     }
    13     
    14     public void start() {
    15         run();
    16     }
    17 }
    18 
    19 public class SubThread extends Thread {
    20 
    21     public void run() {
    22         System.out.println("dsa");
    23     }
    24 
    25 }
    复制代码

    在Thread内部有一个私有的Runable子类对象,可以看出,当我们把Runable子类 r 对象传递给Thread类构造函数的时候,启动start()就会调用run(),而run()接着调用 r 的run方法;

    但是当我们直接通过上面介绍的方式一,即直接继承Thread类创建线程的时候,如19-23行所示,我们需要覆盖Thread类中的run方法,那么SubThread类的对象就在通过start方法启动线程的时候调用的run方法时就会调用我们在Thread子类中自己定义的run方法(21-22行)!

    七、使用方式二创建多线程的好处 目录

    创建线程的两种方式:

    • 方式一:继承Thread
    • 方式二:实现Runnable接口

    好处:

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

    2)避免了java单继承的局限性

    所以,创建线程第二种方式较为常用!!! 

    八、多线程示例 目录

    下面实现四个售票员(四个线程)一起卖100张票的示例。

    复制代码
     1 package thread.demo;
     2 //买票:四个售票员一起卖100张票
     3 class Ticket implements Runnable {
     4     private int num = 100;
     5     public void run() {
     6         while(true) {
     7             if (num > 0) {
     8                 System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
     9             }
    10         }
    11     }
    12 }
    13 
    14 public class TicketDemo {
    15 
    16 
    17     public static void main(String[] args) {
    18 
    19         Ticket t = new Ticket();
    20         /*
    21         Ticket t1 = new Ticket();
    22         Ticket t2 = new Ticket();
    23         Ticket t3 = new Ticket();
    24         Ticket t4 = new Ticket();
    25         */
    26         Thread seller1 = new Thread(t);
    27         Thread seller2 = new Thread(t);
    28         Thread seller3 = new Thread(t);
    29         Thread seller4 = new Thread(t);
    30         
    31         seller1.start();
    32         seller2.start();
    33         seller3.start();
    34         seller4.start();
    35     }
    36 
    37 }
    复制代码

    运行结果:

    复制代码
    Thread-0...sale...100
    Thread-3...sale...97
    Thread-2...sale...98
    Thread-1...sale...99
    Thread-2...sale...94
    Thread-3...sale...95
    Thread-0...sale...96
    Thread-3...sale...91
    Thread-2...sale...92
    Thread-2...sale...88
    Thread-2...sale...87
    Thread-2...sale...86
    Thread-1...sale...93
    Thread-1...sale...84
    Thread-1...sale...83
    Thread-2...sale...85
    Thread-3...sale...89
    Thread-0...sale...90
    Thread-3...sale...80
    Thread-2...sale...81
    Thread-1...sale...82
    Thread-1...sale...76
    Thread-1...sale...75
    Thread-2...sale...77
    Thread-3...sale...78
    Thread-0...sale...79
    Thread-3...sale...72
    Thread-2...sale...73
    Thread-1...sale...74
    Thread-2...sale...69
    Thread-3...sale...70
    Thread-0...sale...71
    Thread-3...sale...66
    Thread-2...sale...67
    Thread-1...sale...68
    Thread-2...sale...63
    Thread-2...sale...61
    Thread-3...sale...64
    Thread-0...sale...65
    Thread-3...sale...59
    Thread-2...sale...60
    Thread-1...sale...62
    Thread-2...sale...56
    Thread-3...sale...57
    Thread-0...sale...58
    Thread-3...sale...53
    Thread-2...sale...54
    Thread-1...sale...55
    Thread-1...sale...49
    Thread-2...sale...50
    Thread-3...sale...51
    Thread-0...sale...52
    Thread-0...sale...45
    Thread-3...sale...46
    Thread-2...sale...47
    Thread-1...sale...48
    Thread-2...sale...42
    Thread-3...sale...43
    Thread-0...sale...44
    Thread-3...sale...39
    Thread-2...sale...40
    Thread-1...sale...41
    Thread-2...sale...36
    Thread-3...sale...37
    Thread-0...sale...38
    Thread-3...sale...33
    Thread-2...sale...34
    Thread-1...sale...35
    Thread-2...sale...30
    Thread-3...sale...31
    Thread-0...sale...32
    Thread-3...sale...27
    Thread-2...sale...28
    Thread-1...sale...29
    Thread-2...sale...24
    Thread-3...sale...25
    Thread-0...sale...26
    Thread-3...sale...21
    Thread-2...sale...22
    Thread-1...sale...23
    Thread-2...sale...18
    Thread-3...sale...19
    Thread-0...sale...20
    Thread-3...sale...15
    Thread-2...sale...16
    Thread-1...sale...17
    Thread-2...sale...12
    Thread-3...sale...13
    Thread-0...sale...14
    Thread-3...sale...9
    Thread-2...sale...10
    Thread-1...sale...11
    Thread-2...sale...6
    Thread-3...sale...7
    Thread-0...sale...8
    Thread-3...sale...3
    Thread-2...sale...4
    Thread-1...sale...5
    Thread-3...sale...1
    Thread-0...sale...2
    复制代码

    看到四个线程一起把100张票卖完了.

    九、线程安全问题现象 目录

    分析上面的示例,由于线程之间随机切换执行,假如售票员0(线程0),卖到了1号票,此时 num = 1 ;

                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
                }

    经过判断,满足if 条件,进入后面的代码块,但是存在一种情况就是,线程0还没有来得及执行打印语句,就切换到了线程1,此时线程0处于等待状态(等待继续执行...), 此时num仍然为1, 然后线程1判断

    满足条件,顺利执行完,之后num--, 于是num = 0; 恰好随机切换到线程0, 然后线程0执行打印语句(Thread-0...sale...0),就是说售票员0 把0号票卖出去了,显然不可以!这种情况就导致了线程不安全!

    为了使得程序出现我们分析的这种不安全的情况,需要在示例代码在第7行之后稍微停顿一下,然后执行后面的打印语句,如果不停顿,现在cpu的计算速度很快,判断 if (num > 0)为真之后往往很快就会执行到打印语句,上面分析的情况很难观测到,于是为了说明问题,做如下添加:

    复制代码
     1 package thread.demo;
     2 //买票:四个售票员一起卖100张票
     3 class Ticket implements Runnable {
     4     private int num = 100;
     5     public void run() {
     6         while(true) {
     7             if (num > 0) {
     8                 // 让线程sleep一会,好让打印语句还没来得及执行,其他线程
     9                 // 就切换进来,这样方便我们观测线程安全隐患
    10                 try {
    11                     Thread.sleep(20);
    12                 } catch (InterruptedException e) {
    13                     // TODO Auto-generated catch block
    14                     e.printStackTrace();
    15                 }
    16                 
    17                 System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
    18             }
    19         }
    20     }
    21 }
    22 
    23 public class TicketDemo {
    24 
    25 
    26     public static void main(String[] args) {
    27 
    28         Ticket t = new Ticket();
    29         /*
    30         Ticket t1 = new Ticket();
    31         Ticket t2 = new Ticket();
    32         Ticket t3 = new Ticket();
    33         Ticket t4 = new Ticket();
    34         */
    35         Thread seller1 = new Thread(t);
    36         Thread seller2 = new Thread(t);
    37         Thread seller3 = new Thread(t);
    38         Thread seller4 = new Thread(t);
    39         
    40         seller1.start();
    41         seller2.start();
    42         seller3.start();
    43         seller4.start();
    44     }
    45 
    46 }
    复制代码

    运行结果:

    复制代码
    Thread-3...sale...100
    Thread-2...sale...98
    Thread-1...sale...100
    Thread-0...sale...99
    Thread-0...sale...95
    Thread-3...sale...97
    Thread-2...sale...97
    Thread-1...sale...96
    Thread-2...sale...94
    Thread-3...sale...94
    Thread-1...sale...94
    Thread-0...sale...94
    Thread-0...sale...93
    Thread-3...sale...91
    Thread-1...sale...92
    Thread-2...sale...93
    Thread-3...sale...88
    Thread-0...sale...89
    Thread-2...sale...89
    Thread-1...sale...90
    Thread-0...sale...87
    Thread-2...sale...84
    Thread-3...sale...85
    Thread-1...sale...86
    Thread-2...sale...83
    Thread-3...sale...82
    Thread-1...sale...82
    Thread-0...sale...83
    Thread-0...sale...81
    Thread-3...sale...80
    Thread-1...sale...80
    Thread-2...sale...81
    Thread-3...sale...78
    Thread-0...sale...79
    Thread-1...sale...79
    Thread-2...sale...77
    Thread-2...sale...76
    Thread-1...sale...75
    Thread-3...sale...75
    Thread-0...sale...76
    Thread-2...sale...74
    Thread-1...sale...73
    Thread-3...sale...73
    Thread-0...sale...74
    Thread-0...sale...72
    Thread-1...sale...70
    Thread-3...sale...71
    Thread-2...sale...69
    Thread-0...sale...68
    Thread-2...sale...65
    Thread-1...sale...67
    Thread-3...sale...66
    Thread-1...sale...64
    Thread-2...sale...61
    Thread-0...sale...62
    Thread-3...sale...63
    Thread-2...sale...59
    Thread-1...sale...57
    Thread-3...sale...60
    Thread-0...sale...58
    Thread-2...sale...56
    Thread-1...sale...55
    Thread-3...sale...54
    Thread-0...sale...53
    Thread-1...sale...52
    Thread-2...sale...51
    Thread-0...sale...50
    Thread-3...sale...49
    Thread-2...sale...48
    Thread-1...sale...47
    Thread-3...sale...46
    Thread-0...sale...45
    Thread-1...sale...44
    Thread-3...sale...42
    Thread-0...sale...43
    Thread-2...sale...41
    Thread-1...sale...39
    Thread-0...sale...38
    Thread-3...sale...40
    Thread-2...sale...37
    Thread-2...sale...36
    Thread-0...sale...34
    Thread-3...sale...35
    Thread-1...sale...33
    Thread-1...sale...32
    Thread-2...sale...31
    Thread-3...sale...30
    Thread-0...sale...29
    Thread-0...sale...28
    Thread-3...sale...27
    Thread-1...sale...26
    Thread-2...sale...25
    Thread-1...sale...24
    Thread-2...sale...23
    Thread-0...sale...22
    Thread-3...sale...21
    Thread-3...sale...20
    Thread-1...sale...18
    Thread-2...sale...19
    Thread-0...sale...20
    Thread-2...sale...17
    Thread-3...sale...15
    Thread-0...sale...16
    Thread-1...sale...14
    Thread-3...sale...13
    Thread-0...sale...12
    Thread-2...sale...11
    Thread-1...sale...10
    Thread-0...sale...9
    Thread-3...sale...8
    Thread-1...sale...7
    Thread-2...sale...6
    Thread-3...sale...5
    Thread-0...sale...4
    Thread-2...sale...3
    Thread-1...sale...2
    Thread-3...sale...1
    Thread-0...sale...0
    Thread-2...sale...-1
    Thread-1...sale...-2
    复制代码

    可以看到售票员竟然卖出了0,-1,-2号票!!这就是线程安全问题的简单演示,每次运行出现的结果不一样,也有可能恰好正常,没有出现安全问题,以为线程切换的不确定性,但是这种问题一旦出现,通常很致命,所以必须注意!

    十、线程安全问题产生的原因 目录

    由上面可以知道,线程安全问题必须要解决,但是解决问题关键是分析原因!根据上面示例总结出以下原因:

    • 多个线程操作共享的数据
    • 操作共享数据的线程代码有多条

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

    十一、同步代码块 目录

    既然知道产生线程安全问题的原因,就开始着手解决。

    解决思路:

    将多条操作操作共享数据的代码封装起来, 当有线程在执行这些代码的时候,其他线程是不可以参与运算。

    必须要当前线程把这些代码都执行完毕以后,其他线程才可以参与运算.

    Java中,用同步代码块就可以解决这个问题。同步代码块的格式:

    synchronized(对象){

      需要被同步的代码;

    }

    其中的“对象”可以看做一个标记,可以使用任何类型的对象,包括自定义的对象。当然,为了方便,使用Object类的对象即可。

    复制代码
     1 package thread.demo;
     2 //买票:四个售票员一起卖100张票
     3 class Ticket implements Runnable {
     4     private int num = 100;
     5     Object obj = new Object();
     6     public void run() {
     7         while(true) {
     8             synchronized(obj) {
     9                 if (num > 0) {
    10                     // 让线程sleep一会,好让打印语句还没来得及执行后面的打印语句,其他线程
    11                     // 就切换进来,这样方便我们观测线程安全隐患
    12                     try {
    13                         Thread.sleep(20);
    14                     } catch (InterruptedException e) {
    15                         // TODO Auto-generated catch block
    16                         e.printStackTrace();
    17                     }
    18                     
    19                     System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
    20                 }
    21             }
    22         }
    23     }
    24 }
    25 
    26 public class TicketDemo {
    27 
    28 
    29     public static void main(String[] args) {
    30 
    31         Ticket t = new Ticket();
    32         /*
    33         Ticket t1 = new Ticket();
    34         Ticket t2 = new Ticket();
    35         Ticket t3 = new Ticket();
    36         Ticket t4 = new Ticket();
    37         */
    38         Thread seller1 = new Thread(t);
    39         Thread seller2 = new Thread(t);
    40         Thread seller3 = new Thread(t);
    41         Thread seller4 = new Thread(t);
    42         
    43         seller1.start();
    44         seller2.start();
    45         seller3.start();
    46         seller4.start();
    47     }
    48 
    49 }
    复制代码

    运行结果:

    复制代码
    Thread-0...sale...100
    Thread-0...sale...99
    Thread-0...sale...98
    Thread-0...sale...97
    Thread-0...sale...96
    Thread-0...sale...95
    Thread-0...sale...94
    Thread-0...sale...93
    Thread-0...sale...92
    Thread-0...sale...91
    Thread-0...sale...90
    Thread-0...sale...89
    Thread-0...sale...88
    Thread-2...sale...87
    Thread-2...sale...86
    Thread-2...sale...85
    Thread-3...sale...84
    Thread-3...sale...83
    Thread-3...sale...82
    Thread-3...sale...81
    Thread-1...sale...80
    Thread-1...sale...79
    Thread-1...sale...78
    Thread-1...sale...77
    Thread-1...sale...76
    Thread-1...sale...75
    Thread-3...sale...74
    Thread-2...sale...73
    Thread-2...sale...72
    Thread-2...sale...71
    Thread-2...sale...70
    Thread-2...sale...69
    Thread-2...sale...68
    Thread-0...sale...67
    Thread-0...sale...66
    Thread-0...sale...65
    Thread-0...sale...64
    Thread-0...sale...63
    Thread-0...sale...62
    Thread-2...sale...61
    Thread-2...sale...60
    Thread-3...sale...59
    Thread-3...sale...58
    Thread-3...sale...57
    Thread-3...sale...56
    Thread-3...sale...55
    Thread-3...sale...54
    Thread-3...sale...53
    Thread-3...sale...52
    Thread-3...sale...51
    Thread-3...sale...50
    Thread-3...sale...49
    Thread-3...sale...48
    Thread-3...sale...47
    Thread-3...sale...46
    Thread-3...sale...45
    Thread-3...sale...44
    Thread-3...sale...43
    Thread-3...sale...42
    Thread-3...sale...41
    Thread-1...sale...40
    Thread-1...sale...39
    Thread-1...sale...38
    Thread-1...sale...37
    Thread-1...sale...36
    Thread-1...sale...35
    Thread-1...sale...34
    Thread-1...sale...33
    Thread-1...sale...32
    Thread-1...sale...31
    Thread-1...sale...30
    Thread-1...sale...29
    Thread-1...sale...28
    Thread-1...sale...27
    Thread-1...sale...26
    Thread-1...sale...25
    Thread-3...sale...24
    Thread-2...sale...23
    Thread-2...sale...22
    Thread-2...sale...21
    Thread-0...sale...20
    Thread-0...sale...19
    Thread-0...sale...18
    Thread-0...sale...17
    Thread-2...sale...16
    Thread-2...sale...15
    Thread-2...sale...14
    Thread-2...sale...13
    Thread-3...sale...12
    Thread-3...sale...11
    Thread-3...sale...10
    Thread-3...sale...9
    Thread-3...sale...8
    Thread-3...sale...7
    Thread-1...sale...6
    Thread-1...sale...5
    Thread-1...sale...4
    Thread-1...sale...3
    Thread-3...sale...2
    Thread-3...sale...1
    复制代码

    可以看出,问题得到了很好地解决!

    十二、同步的好处与弊端 目录

    首先讨论下,同步到底是如何实现的。

    假如0线程执行到run方法的同步代码块,那么 0 线程就持有了 obj, 即obj 被加载到了0线程里面,当其他线程过来时,它们也需要加载obj才能进入同步代码块,但是在线程没有执行完同步代码块之前,obj一直被0线程占有,所以其他线程无法进入同步代码块,知道0线程执行完同步代码块释放 obj,其他线程才有机会加载obj, 然后进入同步代码块。所以obj就像一把锁一样,里面的不出来,外面的就进不去。所以常常称为同步锁!谁拿到了锁,谁就进得去,出来之后释放锁,以便其他线程有机会使用。

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

    同步的弊端:相对降低了效率,因为同步外的线程都会去判断同步锁。

    十三、同步的前提 目录

    同步中,必须有多个线程并使用同一个锁,因为一个线程没必要同步,数据全被这一个线程使用,就不存在所谓的线程安全问题。而如果不使用同一个锁,即使一个线程在执行加了锁的代码块,其他线程同样可以通过拿到其他锁进来参与运算。

    十四、同步函数 目录

    一个稍微简单点的多线程程序示例:

    按 Ctrl+C 复制代码
    按 Ctrl+C 复制代码

    运行结果:

    sum = 100
    sum = 200
    sum = 300
    sum = 500
    sum = 400
    sum = 600

    当然,由于线程的随机切换,显示结果有点乱,最终两人共向银行存入了600.

    分析一下这个程序:run方法里面的代码调用了add方法,sum 是两个线程的共享数据,对于共享数据的操作不止一条:

            sum += num;
            System.out.println("sum = "  + sum);

    假如第一个用户(线程0)存入100,执行第一条语句,sum 就变为100. 正常接下来就应该输出 sum = 100, 但是这时存在一种情况:线程0 还没来得及输出,第二个用户(线程1)存入100,于是sum = 100 + 100 = 200, 线程1 然后正常输出 sum = 200,刚输出完成,线程0又切入了,接着执行打印语句,本来他存入的只是100,但是由于sum = 200, 线程0 也打印出sum = 200, 对应这实际情况就是,用户1存入了100,系统却显示存入了200,显然存在着线程安全问题。同样,为了展示这个安全问题,在上面两条语句之间假如一定的停顿:

    复制代码
            sum += num;
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("sum = "  + sum);
    复制代码

    运行结果:

    sum = 200
    sum = 200
    sum = 400
    sum = 400
    sum = 600
    sum = 600

    可见,200显示了两次,肯定是有一个用户存入100时,却打印出了200. 显然不合理!

    首先想到的就是同步:

    复制代码
     1 class Bank {
     2     private int sum;
     3     Object obj = new Object();
     4     public void add(int num) {
     5         synchronized(obj) {
     6             sum += num;
     7             try {
     8                 Thread.sleep(20);
     9             } catch (InterruptedException e) {
    10                 // TODO Auto-generated catch block
    11                 e.printStackTrace();
    12             }
    13             System.out.println("sum = "  + sum);
    14         }
    15     }
    16 }
    复制代码

    运行结果:

    sum = 100
    sum = 200
    sum = 300
    sum = 400
    sum = 500
    sum = 600

    问题解决!

    但是,发现,要创建对象,写synchronized代码块是不是有点麻烦呢???我们发现add函数里面的内容都需要同步,就是说add函数里面的代码就是我们要同步的代码,于是直接同步这个函数就可以了,即同步函数:

    复制代码
     1 class Bank {
     2     private int sum;
     3     //Object obj = new Object();
     4     public synchronized void add(int num) {//同步函数
     5         //synchronized(obj) {
     6             sum += num;
     7             try {
     8                 Thread.sleep(20);
     9             } catch (InterruptedException e) {
    10                 // TODO Auto-generated catch block
    11                 e.printStackTrace();
    12             }
    13             System.out.println("sum = "  + sum);
    14         //}
    15     }
    16 }
    复制代码

    运行结果同上!

    十五、同步函数锁的验证 目录

    由前面的分析知道,同步是需要一把“锁”,而锁可以是任意类型的对象,那么同步函数到底使用的是什么锁呢??下面来验证。

    回到前面售票的程序,改成两个售票员,一个售票员卖票线程放在同步代码块里面,另一个售票员的的线程放在同步函数里面,如果这两个线程用的是同一个锁,那么就不会出现安全隐患。

    于是在程序改为下面:

    复制代码
     1 package thread.demo;
     2 //买票:四个售票员一起卖100张票
     3 class Ticket_2 implements Runnable {
     4     private int num = 100;
     5     Object obj = new Object();
     6     boolean flag = true;
     7     public void run() { 
     8         if (flag) 
     9         {
    10             while (true) 
    11             {
    12                 synchronized(obj)
    13                 {
    14                     if (num > 0) 
    15                     {
    16                         // 让线程sleep一会,好让打印语句还没来得及执行后面的打印语句,其他线程
    17                         // 就切换进来,这样方便我们观测线程安全隐患
    18                         try { 
    19                             Thread.sleep(20);
    20                         } catch (InterruptedException e) {
    21                             // TODO Auto-generated catch block
    22                             e.printStackTrace();
    23                         }
    24                         System.out.println(Thread.currentThread().getName() + "...bloc k..." + num--);
    25                     }
    26                 }
    27             }
    28                 
    29         }//end if
    30         else
    31             while (true) show();
    32     }
    33     
    34     public synchronized void show() 
    35     {
    36         if (num > 0) 
    37         {
    38             // 让线程sleep一会,好让打印语句还没来得及执行,其他线程
    39             // 就切换进来,这样方便我们观测线程安全隐患
    40             try { 
    41                 Thread.sleep(20);
    42             } catch (InterruptedException e) {
    43                 // TODO Auto-generated catch block
    44                 e.printStackTrace();
    45             }
    46             System.out.println(Thread.currentThread().getName() + "...function..." + num--);
    47         }
    48     } 
    49 }
    50 
    51 public class SynFunctionLockDemo
    52 {
    53 
    54 
    55     public static void main(String[] args) 
    56     {
    57 
    58         Ticket_2 t = new Ticket_2();
    59 
    60         Thread seller1 = new Thread(t);
    61         Thread seller2 = new Thread(t);
    62 
    63         
    64         seller1.start(); //在同步代码块执行
    65         t.flag = false; // 标志变为false,使得下一个线程在同步函数执行
    66         seller2.start();
    67 
    68     }
    69 }
    复制代码

    如果是在同步函数里面执行的代码块会打印出带有“function”的字符串。如果是在同步代码块里面执行的会打印出带有"bloc  k"的字符串,执行如下:

    复制代码
    Thread-0...function...100
    Thread-0...function...99
    Thread-0...function...98
    Thread-0...function...97
    Thread-0...function...96
    Thread-0...function...95
    Thread-0...function...94
    Thread-0...function...93
    Thread-0...function...92
    Thread-0...function...91
    Thread-0...function...90
    Thread-0...function...89
    Thread-0...function...88
    Thread-0...function...87
    Thread-0...function...86
    Thread-0...function...85
    Thread-1...function...84
    Thread-1...function...83
    Thread-1...function...82
    Thread-1...function...81
    Thread-0...function...80
    Thread-0...function...79
    Thread-0...function...78
    Thread-0...function...77
    Thread-0...function...76
    Thread-0...function...75
    Thread-0...function...74
    Thread-1...function...73
    Thread-1...function...72
    Thread-0...function...71
    Thread-0...function...70
    Thread-0...function...69
    Thread-1...function...68
    Thread-1...function...67
    Thread-1...function...66
    Thread-0...function...65
    Thread-0...function...64
    Thread-0...function...63
    Thread-0...function...62
    Thread-1...function...61
    Thread-1...function...60
    Thread-1...function...59
    Thread-1...function...58
    Thread-0...function...57
    Thread-0...function...56
    Thread-0...function...55
    Thread-1...function...54
    Thread-1...function...53
    Thread-1...function...52
    Thread-1...function...51
    Thread-1...function...50
    Thread-1...function...49
    Thread-1...function...48
    Thread-1...function...47
    Thread-0...function...46
    Thread-0...function...45
    Thread-0...function...44
    Thread-0...function...43
    Thread-1...function...42
    Thread-1...function...41
    Thread-1...function...40
    Thread-1...function...39
    Thread-1...function...38
    Thread-1...function...37
    Thread-1...function...36
    Thread-1...function...35
    Thread-1...function...34
    Thread-1...function...33
    Thread-1...function...32
    Thread-1...function...31
    Thread-1...function...30
    Thread-1...function...29
    Thread-1...function...28
    Thread-1...function...27
    Thread-0...function...26
    Thread-0...function...25
    Thread-0...function...24
    Thread-1...function...23
    Thread-1...function...22
    Thread-1...function...21
    Thread-1...function...20
    Thread-1...function...19
    Thread-1...function...18
    Thread-1...function...17
    Thread-1...function...16
    Thread-0...function...15
    Thread-0...function...14
    Thread-0...function...13
    Thread-1...function...12
    Thread-1...function...11
    Thread-1...function...10
    Thread-1...function...9
    Thread-1...function...8
    Thread-1...function...7
    Thread-0...function...6
    Thread-0...function...5
    Thread-1...function...4
    Thread-1...function...3
    Thread-1...function...2
    Thread-1...function...1
    复制代码

    发现竟然都在同步函数里面执行,什么原因呢?原因在于:主线程开启以后一口气运行到了66行代码,注意这里虽然线程0和线程1被开启,即具有了执行资格,但是还不具备执行权,执行权仍然被主线程占有着,等待主线程完事之后,线程0和线程1在真正抢到执行权的时候,发现flag = false,于是全部走进了同步函数!!! 要想使得线程0和线程1分别走进不同的同步方法,需要在开启线程0之后,让主线程停顿一会,让线程0开始执行(此时线程1还没有被开启,是不可能执行的),进入同步代码块,然后等到主线程再执行的时候就会把flag置为false,开启线程1,线程1就可以进入同步函数。

    具体改动如下:

    复制代码
     1 public static void main(String[] args) 
     2     {
     3 
     4         Ticket_2 t = new Ticket_2();
     5 
     6         Thread seller1 = new Thread(t);
     7         Thread seller2 = new Thread(t);
     8 
     9         
    10         seller1.start(); //在同步代码块执行
    11         
    12         // 主线程停顿一会,给线程0执行的机会!!
    13         try {
    14             Thread.sleep(20);
    15         } catch (InterruptedException e) {
    16             // TODO Auto-generated catch block
    17             e.printStackTrace();
    18         }
    19         
    20         t.flag = false; // 标志变为false,使得下一个线程在同步函数执行
    21         seller2.start();
    22 
    23     }
    复制代码

    运行结果:

    复制代码
    Thread-0...bloc k...100
    Thread-0...bloc k...99
    Thread-1...function...98
    Thread-0...bloc k...96
    Thread-1...function...97
    Thread-0...bloc k...94
    Thread-1...function...95
    Thread-0...bloc k...93
    Thread-1...function...92
    Thread-0...bloc k...91
    Thread-1...function...90
    Thread-1...function...89
    Thread-0...bloc k...89
    Thread-0...bloc k...88
    Thread-1...function...88
    Thread-0...bloc k...87
    Thread-1...function...87
    Thread-1...function...86
    Thread-0...bloc k...86
    Thread-1...function...84
    Thread-0...bloc k...85
    Thread-1...function...82
    Thread-0...bloc k...83
    Thread-0...bloc k...80
    Thread-1...function...81
    Thread-0...bloc k...79
    Thread-1...function...78
    Thread-0...bloc k...77
    Thread-1...function...76
    Thread-1...function...75
    Thread-0...bloc k...74
    Thread-1...function...73
    Thread-0...bloc k...72
    Thread-1...function...71
    Thread-0...bloc k...70
    Thread-1...function...69
    Thread-0...bloc k...68
    Thread-1...function...67
    Thread-0...bloc k...66
    Thread-1...function...65
    Thread-0...bloc k...64
    Thread-1...function...63
    Thread-0...bloc k...62
    Thread-1...function...61
    Thread-0...bloc k...60
    Thread-1...function...59
    Thread-0...bloc k...58
    Thread-1...function...57
    Thread-0...bloc k...56
    Thread-1...function...55
    Thread-0...bloc k...54
    Thread-1...function...53
    Thread-0...bloc k...52
    Thread-1...function...51
    Thread-0...bloc k...50
    Thread-1...function...49
    Thread-0...bloc k...48
    Thread-1...function...47
    Thread-0...bloc k...46
    Thread-1...function...45
    Thread-0...bloc k...44
    Thread-1...function...43
    Thread-0...bloc k...42
    Thread-1...function...41
    Thread-0...bloc k...40
    Thread-1...function...39
    Thread-0...bloc k...38
    Thread-1...function...37
    Thread-0...bloc k...36
    Thread-1...function...35
    Thread-0...bloc k...34
    Thread-1...function...33
    Thread-0...bloc k...32
    Thread-1...function...31
    Thread-0...bloc k...30
    Thread-1...function...29
    Thread-0...bloc k...28
    Thread-1...function...27
    Thread-0...bloc k...26
    Thread-1...function...25
    Thread-0...bloc k...24
    Thread-1...function...23
    Thread-0...bloc k...22
    Thread-1...function...21
    Thread-0...bloc k...20
    Thread-1...function...19
    Thread-0...bloc k...18
    Thread-0...bloc k...17
    Thread-1...function...16
    Thread-1...function...15
    Thread-0...bloc k...14
    Thread-1...function...13
    Thread-0...bloc k...12
    Thread-1...function...11
    Thread-0...bloc k...10
    Thread-1...function...9
    Thread-0...bloc k...8
    Thread-1...function...7
    Thread-0...bloc k...6
    Thread-1...function...5
    Thread-0...bloc k...4
    Thread-1...function...3
    Thread-0...bloc k...2
    Thread-1...function...1
    Thread-0...bloc k...0
    复制代码

    可以看到,线程用到了不同的同步方式:同步函数(1线程)和同步代码块(0线程)!但是,也看到89,88,87,86号票被出售了两次,而且还售出了0号票(每次执行情况会不同,但是都会出现类似的线程问题),这说明线程0和线程1没有同步,即二者目前使用的锁不一致,即同步函数使用的不是Object 类型的锁!

    我们知道类里面的函数都默认持有this代表着调用该类的对象,同步函数当让持有this啦,试着把同步代码块的锁改为this:

    synchronized(this)

    然后多次执行,结果如下:

    复制代码
    Thread-0...bloc k...100
    Thread-0...bloc k...99
    Thread-0...bloc k...98
    Thread-0...bloc k...97
    Thread-0...bloc k...96
    Thread-0...bloc k...95
    Thread-0...bloc k...94
    Thread-0...bloc k...93
    Thread-0...bloc k...92
    Thread-0...bloc k...91
    Thread-0...bloc k...90
    Thread-0...bloc k...89
    Thread-0...bloc k...88
    Thread-0...bloc k...87
    Thread-0...bloc k...86
    Thread-1...function...85
    Thread-1...function...84
    Thread-1...function...83
    Thread-0...bloc k...82
    Thread-0...bloc k...81
    Thread-0...bloc k...80
    Thread-0...bloc k...79
    Thread-1...function...78
    Thread-1...function...77
    Thread-1...function...76
    Thread-0...bloc k...75
    Thread-0...bloc k...74
    Thread-0...bloc k...73
    Thread-0...bloc k...72
    Thread-0...bloc k...71
    Thread-1...function...70
    Thread-1...function...69
    Thread-0...bloc k...68
    Thread-0...bloc k...67
    Thread-0...bloc k...66
    Thread-1...function...65
    Thread-1...function...64
    Thread-1...function...63
    Thread-1...function...62
    Thread-1...function...61
    Thread-0...bloc k...60
    Thread-0...bloc k...59
    Thread-1...function...58
    Thread-1...function...57
    Thread-1...function...56
    Thread-1...function...55
    Thread-0...bloc k...54
    Thread-0...bloc k...53
    Thread-0...bloc k...52
    Thread-0...bloc k...51
    Thread-1...function...50
    Thread-1...function...49
    Thread-1...function...48
    Thread-0...bloc k...47
    Thread-0...bloc k...46
    Thread-0...bloc k...45
    Thread-1...function...44
    Thread-1...function...43
    Thread-1...function...42
    Thread-0...bloc k...41
    Thread-0...bloc k...40
    Thread-0...bloc k...39
    Thread-0...bloc k...38
    Thread-0...bloc k...37
    Thread-0...bloc k...36
    Thread-0...bloc k...35
    Thread-0...bloc k...34
    Thread-1...function...33
    Thread-1...function...32
    Thread-1...function...31
    Thread-1...function...30
    Thread-1...function...29
    Thread-1...function...28
    Thread-1...function...27
    Thread-0...bloc k...26
    Thread-0...bloc k...25
    Thread-0...bloc k...24
    Thread-0...bloc k...23
    Thread-0...bloc k...22
    Thread-1...function...21
    Thread-0...bloc k...20
    Thread-0...bloc k...19
    Thread-0...bloc k...18
    Thread-0...bloc k...17
    Thread-1...function...16
    Thread-1...function...15
    Thread-1...function...14
    Thread-0...bloc k...13
    Thread-0...bloc k...12
    Thread-0...bloc k...11
    Thread-0...bloc k...10
    Thread-1...function...9
    Thread-1...function...8
    Thread-1...function...7
    Thread-0...bloc k...6
    Thread-0...bloc k...5
    Thread-0...bloc k...4
    Thread-0...bloc k...3
    Thread-0...bloc k...2
    Thread-0...bloc k...1
    复制代码

     发现线程问题被解决了!所以验证了同步函数使用的同步锁就是 this!

    虽然同步函数书写较为简单(作为同步代码块的简写形式,二者功能一致),但是建议使用同步代码块,因为同步函数使用的锁唯一,而同步代码块可以使用任意对象作为锁,只在需要的若干条语句自由加锁,直观。

    但是,注意到,如果需要同步的函数是静态的呢?因为静态函数属于类而不是具体对象,所以静态函数中是不存在this的,所以如果同步函数是静态的,锁显然就不是this!!!

    继续按照上面思路验证,在上面程序的基础上,把同步函数改为静态,同时由于静态函数使用了num, 所以把num也改为静态,改动如下:

    private static int num = 100;
    
    ....
    
    
    public static synchronized void show() 

    运行如下:

    复制代码
    Thread-0...block...100
    Thread-0...block...99
    Thread-0...block...98
    Thread-1...function...97
    Thread-1...function...96
    Thread-0...block...95
    Thread-0...block...94
    Thread-1...function...93
    Thread-1...function...92
    Thread-0...block...91
    Thread-0...block...89
    Thread-1...function...90
    Thread-1...function...88
    Thread-0...block...87
    Thread-1...function...86
    Thread-0...block...86
    Thread-1...function...85
    Thread-0...block...85
    Thread-1...function...84
    Thread-0...block...83
    Thread-1...function...82
    Thread-0...block...81
    Thread-1...function...80
    Thread-0...block...80
    Thread-0...block...79
    Thread-1...function...78
    Thread-1...function...77
    Thread-0...block...77
    Thread-0...block...76
    Thread-1...function...75
    Thread-0...block...74
    Thread-1...function...73
    Thread-1...function...72
    Thread-0...block...72
    Thread-0...block...71
    Thread-1...function...71
    Thread-1...function...70
    Thread-0...block...69
    Thread-1...function...68
    Thread-0...block...67
    Thread-1...function...66
    Thread-0...block...65
    Thread-0...block...64
    Thread-1...function...63
    Thread-0...block...62
    Thread-1...function...61
    Thread-1...function...60
    Thread-0...block...59
    Thread-1...function...58
    Thread-0...block...57
    Thread-1...function...56
    Thread-0...block...55
    Thread-1...function...53
    Thread-0...block...54
    Thread-0...block...52
    Thread-1...function...52
    Thread-0...block...51
    Thread-1...function...50
    Thread-1...function...49
    Thread-0...block...48
    Thread-1...function...47
    Thread-0...block...46
    Thread-1...function...45
    Thread-0...block...44
    Thread-0...block...43
    Thread-1...function...42
    Thread-0...block...41
    Thread-1...function...40
    Thread-1...function...39
    Thread-0...block...38
    Thread-0...block...37
    Thread-1...function...36
    Thread-0...block...35
    Thread-1...function...34
    Thread-1...function...33
    Thread-0...block...32
    Thread-0...block...31
    Thread-1...function...30
    Thread-0...block...29
    Thread-1...function...28
    Thread-0...block...27
    Thread-1...function...26
    Thread-1...function...25
    Thread-0...block...24
    Thread-0...block...23
    Thread-1...function...22
    Thread-0...block...21
    Thread-1...function...20
    Thread-1...function...19
    Thread-0...block...18
    Thread-0...block...17
    Thread-1...function...16
    Thread-0...block...15
    Thread-1...function...14
    Thread-1...function...13
    Thread-0...block...12
    Thread-0...block...11
    Thread-1...function...10
    Thread-0...block...9
    Thread-1...function...8
    Thread-1...function...7
    Thread-0...block...6
    Thread-1...function...5
    Thread-0...block...4
    Thread-0...block...3
    Thread-1...function...2
    Thread-1...function...1
    Thread-0...block...0
    复制代码

    看到86,85 ,0等处又出现了线程安全问题。这说明,显然静态函数使用的不是锁不是this, 当然静态函数不可能持有this的,与我们预料的一样!

    然而每一个类都属于它所在的字节码文件对象,虽然静态函数不属于具体的对象,而是属于一个类,所以静态函数必然持有字节码文件的对象,显然这个对象是静态的!

    于是在上面改动的基础上再作如下改动:

    synchronized(Ticket_2.class)
    
    ... ...

    运行结果:

    复制代码
    Thread-0...block...100
    Thread-0...block...99
    Thread-0...block...98
    Thread-0...block...97
    Thread-1...function...96
    Thread-1...function...95
    Thread-1...function...94
    Thread-1...function...93
    Thread-1...function...92
    Thread-1...function...91
    Thread-1...function...90
    Thread-1...function...89
    Thread-1...function...88
    Thread-1...function...87
    Thread-1...function...86
    Thread-1...function...85
    Thread-1...function...84
    Thread-1...function...83
    Thread-1...function...82
    Thread-1...function...81
    Thread-1...function...80
    Thread-1...function...79
    Thread-1...function...78
    Thread-1...function...77
    Thread-1...function...76
    Thread-1...function...75
    Thread-1...function...74
    Thread-1...function...73
    Thread-1...function...72
    Thread-1...function...71
    Thread-1...function...70
    Thread-1...function...69
    Thread-0...block...68
    Thread-0...block...67
    Thread-0...block...66
    Thread-0...block...65
    Thread-0...block...64
    Thread-1...function...63
    Thread-1...function...62
    Thread-1...function...61
    Thread-1...function...60
    Thread-1...function...59
    Thread-1...function...58
    Thread-1...function...57
    Thread-1...function...56
    Thread-1...function...55
    Thread-1...function...54
    Thread-1...function...53
    Thread-1...function...52
    Thread-1...function...51
    Thread-1...function...50
    Thread-1...function...49
    Thread-1...function...48
    Thread-1...function...47
    Thread-1...function...46
    Thread-1...function...45
    Thread-1...function...44
    Thread-1...function...43
    Thread-1...function...42
    Thread-1...function...41
    Thread-1...function...40
    Thread-1...function...39
    Thread-1...function...38
    Thread-1...function...37
    Thread-1...function...36
    Thread-1...function...35
    Thread-1...function...34
    Thread-1...function...33
    Thread-1...function...32
    Thread-1...function...31
    Thread-1...function...30
    Thread-1...function...29
    Thread-1...function...28
    Thread-1...function...27
    Thread-1...function...26
    Thread-1...function...25
    Thread-1...function...24
    Thread-1...function...23
    Thread-1...function...22
    Thread-1...function...21
    Thread-1...function...20
    Thread-1...function...19
    Thread-1...function...18
    Thread-1...function...17
    Thread-1...function...16
    Thread-1...function...15
    Thread-1...function...14
    Thread-1...function...13
    Thread-1...function...12
    Thread-1...function...11
    Thread-1...function...10
    Thread-1...function...9
    Thread-1...function...8
    Thread-1...function...7
    Thread-1...function...6
    Thread-1...function...5
    Thread-1...function...4
    Thread-1...function...3
    Thread-1...function...2
    Thread-1...function...1
    复制代码

    所以静态的同步函数使用的锁是 该函数所述的字节码文件对象,该对象可以用 getClass方法获取,也可以用当前 类名.class 表示,但是此时由于getClass()是非静态方法,所以只能用类名.class。

    十六、单例模式的线程问题的解决方案 目录

    复制代码
     1 package thread.demo;
     2 //单例模式
     3 //饿汉式
     4 /*
     5 class Single
     6 {
     7     private static final Single s = new Single();
     8     private Single(){}
     9     public static Single getInstance()
    10     {
    11         return s;
    12     }
    13 }
    14 */
    15 
    16 //懒汉式
    17 
    18 class Single_l
    19 {
    20     private static Single_l s = null;
    21     private Single_l(){}
    22     public static Single_l getInstance()
    23     {
    24         if (s == null)
    25         {
    26             synchronized(Single_l.class)
    27             {
    28                 if (s == null)
    29                     s = new Single_l();
    30             }
    31         }
    32         return s;
    33     }    
    34 }
    35 
    36 
    37 public class SingleDemo {
    38 
    39     /**
    40      * @param args
    41      */
    42     public static void main(String[] args) {
    43         // TODO Auto-generated method stub
    44 
    45     }
    46 
    47 }
    复制代码

    由于懒汉式单例模式的同步代码块的代码有多条,所以可能会出现线程安全问题,所以需要进行同步。

    十七、多线程死锁示例 目录

    复制代码
     1 package thread.demo;
     2 //买票:四个售票员一起卖100张票
     3 class Ticket_3 implements Runnable 
     4 {
     5     private int num = 100;
     6     Object obj = new Object();
     7     boolean flag = true;
     8     public void run() 
     9     { 
    10         if (flag) 
    11         {
    12             while (true) 
    13             {
    14                 synchronized(obj)
    15                 {
    16                      show();//同步代码块中使用同步函数
    17                 }
    18             }    
    19         }//end if
    20         else
    21             while (true) show();
    22     }
    23     
    24     public synchronized void show() 
    25     {
    26         synchronized(obj)//同步函数中使用同步代码块
    27         {
    28             if (num > 0) 
    29             {
    30                 // 让线程sleep一会,好让打印语句还没来得及执行,其他线程
    31                 // 就切换进来,这样方便我们观测线程安全隐患
    32                 try { 
    33                     Thread.sleep(10);
    34                 } catch (InterruptedException e) {
    35                     // TODO Auto-generated catch block
    36                     e.printStackTrace();
    37                 }
    38                 System.out.println(Thread.currentThread().getName() + "...function..." + num--);
    39             } //end if 
    40         }//end synchronized
    41     } 
    42 }
    43 
    44 public class DeadLock
    45 {
    46     public static void main(String[] args) 
    47     {
    48 
    49         Ticket_3 t = new Ticket_3();
    50 
    51         Thread seller1 = new Thread(t);
    52         Thread seller2 = new Thread(t);
    53 
    54         
    55         seller1.start(); //在同步代码块执行
    56         
    57         // 主线程停顿一会,给线程0执行的机会!!
    58         try {
    59             Thread.sleep(20);
    60         } catch (InterruptedException e) {
    61             // TODO Auto-generated catch block
    62             e.printStackTrace();
    63         }
    64         
    65         t.flag = false; // 标志变为false,使得下一个线程在同步函数执行
    66         seller2.start();
    67 
    68     }
    69 }
    复制代码

    程序解释:这是一个同步锁嵌套的情况,可以看到在第14行中的同步代码块中使用了同步函数,而在第24行的同步函数中使用了同步代码块,即两个同步锁互相嵌套。下面分析程序的运行:

    • 程序从主函数开始,运行到58行之后,主线程停顿,线程0得到cpu执行权,flag = true, 执行到14 行,线程 0 拿到同步锁 obj, 执行同步代码块,而同步代码块的语句就是调用24行的同步函数,而同步函数里面是26行开始的同步代码块,执行同步代码块,就需要判断锁 obj, 线程0确实持有锁 obj,于是顺利进入26行开始的同步代码块执行,
    • 线程0执行一会,由于代码块中有sleep的存在,主线程有可能切入进来占有cpu执行权,线程0就停顿一会,主线程继续由65行开始执行,flag = false, 线程1开启,然后主线程,0线程,1线程随机切换。
    • 假如刚才停顿的线程0又抢到了cpu执行权,线程0继续执行,再次拿到同步代码块锁obj, 进入到第15行,存在一种情况,就在此时线程1抢到cpu执行权,线程0则持有着代码块的锁obj,并瞅准机会去拿到同步函数的锁this从16行出进入同步函数. 
    • 线程1获得cpu执行权之后,由于flag == false, 执行到第21行,拿到同步函数锁this进入同步函数show(), 然后在第26行准备拿锁obj的时候,却发现被obj被线程0占有,只能等待,而线程0即使抢到了cpu执行权,但是同步函数锁this却被线程1占有,也没法继续,于是程序就停顿不前,这就是死锁产生的一个过程!

    多次执行上面的代码,会出现类似下面的情况:

     

    即程序陷入了死锁,无法继续运行,所有线程0和线程1都处于等待状态!!

    上面的程序只展示了死锁的样子,下面给出一个较为简单的死锁示例备用:

    复制代码
     1 package thread.demo;
     2 class MyLock
     3 {
     4     //“两把锁”
     5     public static final Object locka = new Object();
     6     public static final Object lockb = new Object();
     7 }
     8 class Test implements Runnable
     9 {
    10     private boolean flag;
    11     Test(boolean flag)
    12     {
    13         this.flag = flag;
    14     }
    15     @Override
    16     public void run() 
    17     {
    18         if (flag)
    19         {
    20             synchronized(MyLock.locka)
    21             {
    22                 System.out.println("if...loacka");
    23                 synchronized(MyLock.lockb)
    24                 {
    25                     System.out.println("if...loackb");
    26                 }
    27             }
    28         }
    29         else
    30         {
    31             synchronized(MyLock.lockb)
    32             {
    33                 System.out.println("else...loackb");
    34                 synchronized(MyLock.locka)
    35                 {
    36                     System.out.println("else...loacka");
    37                 }
    38             }
    39         }
    40     }
    41     
    42 }
    43 public class DeadLockTest {
    44 
    45     /**
    46      * @param args
    47      */
    48     public static void main(String[] args) {
    49         Test a = new Test(true);
    50         Test b = new Test(false);
    51         Thread t1 = new Thread(a);
    52         Thread t2 = new Thread(b);
    53         
    54         t1.start();
    55         t2.start();
    56     }
    57 
    58 }
    复制代码

    运行结果:


    后续:下一篇博文将会记载多线程间通信的学习笔记。

    参考文献:传智播客JAVA SE视频教程,李刚《疯狂JAVA讲义》

    请尊重原创知识,本人非常愿意与大家分享 转载请注明出处:http://www.cnblogs.com/90zeng/ 作者:博客园-90Zeng
  • 相关阅读:
    奥一新源科技 面试总结
    腾讯实习 面试总结
    图片预加载 解决图片加载闪动问题
    “-webkit-font-smoothing”
    onselectstart
    jquery 高级 学习笔记
    css 样式重置
    《响应式Web设计—HTML5和CSS3实战》 学习记录
    shift、unshift、 push、pop用法--JavaScript参考手册
    《JavaScript DOM 编程艺术 》 笔记
  • 原文地址:https://www.cnblogs.com/xuan52rock/p/4740126.html
Copyright © 2011-2022 走看看