zoukankan      html  css  js  c++  java
  • Java多线程:多线程的Synchronized详解

    输出为什么要引入同步机制

    在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
    解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

    怎样实现同步

    对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized
    例如:
    synchronized void f() { /* ... */ } synchronized void g() { /* ... */ }
    如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。 调用synchronized方法时,对象就会被锁定。

     1 public class MyStack {
     2     int idx = 0;
     3     char [] data = new char[ 6];
     4     public synchronized void push( char c) {
     5       data[ idx] = c;
     6       idx++;
     7     }
     8     public synchronized char pop() {
     9       idx--;
    10       return data[ idx];
    11     }
    12 }

    说明:
    •当synchronized方法执行完或发生异常时,会自动释放锁。
    •被synchronized保护的数据应该是私有(private)的。

    看几个例子

     1 public class ThreadTest3
     2 {
     3     public static void main(String[] args)
     4     {
     5         Runnable r = new HelloThread();
     6         
     7         Thread t1 = new Thread(r);
     8         
     9         //r = new HelloThread();
    10         
    11         Thread t2 = new Thread(r);
    12         
    13         t1.start();
    14         t2.start();
    15     }
    16 }
    17 
    18 class HelloThread implements Runnable
    19 {
    20     int i;
    21     
    22     @Override
    23     public void run()
    24     {
    25     //    int i = 0;
    26         
    27         while(true)
    28         {
    29             System.out.println("number: " + i++);
    30             
    31             try
    32             {
    33                 Thread.sleep((long)(Math.random() * 1000));
    34             }
    35             catch (InterruptedException e)
    36             {
    37                 e.printStackTrace();
    38             }
    39             
    40             if(5 == i)
    41             {
    42                 break;
    43             }
    44         }
    45     }
    46 }

    输出顺序执行:

    程序执行时出现了两种输出,现在分别来分析一下:

    为什么打印两个0:如文章开头所讲,由于两个线程同时访问一个资源,而且此资源未加锁就会导致访问冲突。

    线程1打印语句还未执行++操作,线程2就进来了,所以导致打印两个0

    为什么"只有一个线程输出":

    只打印了0-4,给人感觉只打印了一条线程,其实这是两条线程共同作用的结果。

    因为 i 这个变量是一个成员变量,而在同一个对象 r 中成员变量是共享的,当线程对 i 进行++操作,线程2中的 i 也会更改,所以打印出这样的结果

    如果将 i 放在方法内即变成局部变量,则会打印两条线程的执行结果

    总结:

    关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
    如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
    停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。

    synchronized关键字

    synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
    Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。

     1 public class ThreadTest4
     2 {
     3     public static void main(String[] args)
     4     {
     5         Example example = new Example();
     6         
     7         Thread t1 = new TheThread(example);
     8         
     9         example = new Example();
    10         
    11         Thread t2 = new TheThread2(example);
    12         
    13         t1.start();
    14         t2.start();
    15     }
    16 }
    17 
    18 class Example
    19 {
    20     public synchronized void execute()
    21     {
    22         for(int i = 0; i < 5; i++)
    23         {
    24             try
    25             {
    26                 Thread.sleep((long)(Math.random() * 1000));
    27             }
    28             catch (InterruptedException e)
    29             {
    30                 e.printStackTrace();
    31             }
    32             
    33             System.out.println("hello: " + i);
    34         }
    35     }
    36     
    37     public synchronized void execute2()
    38     {
    39         for(int i = 0; i < 5; i++)
    40         {
    41             try
    42             {
    43                 Thread.sleep((long)(Math.random() * 1000));
    44             }
    45             catch (InterruptedException e)
    46             {
    47                 e.printStackTrace();
    48             }
    49             
    50             System.out.println("world: " + i);
    51         }
    52     }
    53 }
    54 
    55 class TheThread extends Thread
    56 {
    57     private Example example;
    58     
    59     public TheThread(Example example)
    60     {    
    61         this.example = example;
    62     }
    63     
    64     @Override
    65     public void run()
    66     {
    67         this.example.execute();
    68     }
    69 }
    70 
    71 class TheThread2 extends Thread
    72 {
    73     private Example example;
    74     
    75     public TheThread2(Example example)
    76     {    
    77         this.example = example;
    78     }
    79     
    80     @Override
    81     public void run()
    82     {
    83         this.example.execute2();
    84     }
    85 }

    结果为

    结果可以看出两条线程都同时执行了且互不影响。

    当我们修改main方法

     1 public class ThreadTest4
     2 {
     3     public static void main(String[] args)
     4     {
     5         Example example = new Example();
     6         
     7         Thread t1 = new TheThread(example);
     8         
     9         Thread t2 = new TheThread2(example);
    10         
    11         t1.start();
    12         t2.start();
    13     }
    14 }

    使用同一个 example,结果为

    这个结果是必然的,不管你执行多少次。

    很显然,线程在执行时是一个线程一个线程的执行的。

    原因就如上面红字所讲的,当访问某个对象的synchronized方法时表示对该对象上锁,我们这里使用的是同一个example对象,所以只能等上一个线程执行完后才能执行下一个线程,而不能同时执行。

    如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。

    再来更改Example对象,在其方法上都加上 static 修饰

     1 public synchronized static void execute()
     2     {
     3         for(int i = 0; i < 5; i++)
     4         {
     5             try
     6             {
     7                 Thread.sleep((long)(Math.random() * 1000));
     8             }
     9             catch (InterruptedException e)
    10             {
    11                 e.printStackTrace();
    12             }
    13             
    14             System.out.println("hello: " + i);
    15         }
    16     }
    17     
    18     public synchronized static void execute2()
    19     {
    20         for(int i = 0; i < 5; i++)
    21         {
    22             try
    23             {
    24                 Thread.sleep((long)(Math.random() * 1000));
    25             }
    26             catch (InterruptedException e)
    27             {
    28                 e.printStackTrace();
    29             }
    30             
    31             System.out.println("world: " + i);
    32         }
    33     }

    这时,不管你是否使用同一个example,执行结果都是先执行线程1在执行线程2.

    原因就在于:

    如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。

    再对Example方法进行修改,改成一个static一个非static

     1     public synchronized void execute()
     2     {
     3         for(int i = 0; i < 5; i++)
     4         {
     5             try
     6             {
     7                 Thread.sleep((long)(Math.random() * 1000));
     8             }
     9             catch (InterruptedException e)
    10             {
    11                 e.printStackTrace();
    12             }
    13             
    14             System.out.println("hello: " + i);
    15         }
    16     }
    17     
    18     public synchronized static void execute2()
    19     {
    20         for(int i = 0; i < 5; i++)
    21         {
    22             try
    23             {
    24                 Thread.sleep((long)(Math.random() * 1000));
    25             }
    26             catch (InterruptedException e)
    27             {
    28                 e.printStackTrace();
    29             }
    30             
    31             System.out.println("world: " + i);
    32         }
    33     }

    这时不管你是否使用同一个example,结果都是乱序,即两个线程同时执行,原因还是在于锁对象问题。

    static锁的是class对象,非static锁的就是对象,两个锁的对象不同,结果自然不同。

    synchronized代码块

    synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;

    synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。

    所以更多情况下,我们选择把需要同步的逻辑写在synchronized块中。

    1 synchronized(object)
    2 {
    3       。。。          
    4 }

    表示线程在执行的时候会对object对象上锁

    所以上面的程序就可以改为

     1 public void execute()
     2     {
     3         synchronized (this)
     4         {
     5             for (int i = 0; i < 20; i++)
     6             {
     7                 try
     8                 {
     9                     Thread.sleep((long) (Math.random() * 1000));
    10                 }
    11                 catch (InterruptedException e)
    12                 {
    13                     e.printStackTrace();
    14                 }
    15 
    16                 System.out.println("hello: " + i);
    17             }
    18         }
    19 
    20     }
    21 
    22     public void execute2()
    23     {
    24         synchronized(Example.class)
    25         {
    26             for (int i = 0; i < 20; i++)
    27             {
    28                 try
    29                 {
    30                     Thread.sleep((long) (Math.random() * 1000));
    31                 }
    32                 catch (InterruptedException e)
    33                 {
    34                     e.printStackTrace();
    35                 }
    36 
    37                 System.out.println("world: " + i);
    38             }
    39         }    
    40     }
  • 相关阅读:
    Hadoop命令大全
    Cube中时间维度
    无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?
    IE6、IE7、IE8、FF对空标签块状元素解释的不同点
    SSIS导出平面文件数据带_x003C_none_x003E的问题
    用DB2 Runtime Client实现Apache Derby 数据库ODBC编程
    区块链技术探索
    JS原型对象
    this关键字
    消息认证码
  • 原文地址:https://www.cnblogs.com/2015110615L/p/6732695.html
Copyright © 2011-2022 走看看