3

为什么需要同步多线程?
线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源。我们采用Java中的同步代码块和同步方法达到这样的目的。比如这样的解决多线程无固定序执行的问题:

public class TwoThreadTest {
        public static void main(String[] args) {
              Thread th1= new MyThread1();
              Thread th2= new MyThread2();
              th1.start();
              th2.start();
       }
}

class MyThread2 extends Thread{
        @Override
        public void run() {
               for( int i=0;i<10;i++)
                     System. out.println( "thread 1 counter:"+i);
       }
}

class MyThread1 extends Thread{
        @Override
        public void run() {
               for( int i=0;i<10;i++)
                     System. out.println( "thread 2 counter:"+i);
       }      
}

这种状态下多线程执行的结果是随机地去任意插入执行,这完全取决于JVM对于线程的调度,在很多要求定序执行的情况下,这种随机执行的状态显然是不合要求的。

public class ThreadTest {
        public static void main(String[] args) {
              MyThread thread = new MyThread();
              Thread th1= new Thread(thread);
              Thread th2= new Thread(thread);
              th1.start();
              th2.start();
       }

}

class MyThread implements Runnable{
        @Override
        public synchronized void run() {
               for( int i=0;i<10;i++)
                     System. out.println(Thread. currentThread().getName()+" counter:"+i);
       }
}

使用了同步方法后我们就可以控制线程独占执行体对象,这样在执行的过程中就可以使得线程将执行体上的任务一次性执行完后退出锁定状态,JVM再调度另一个线程进来一次性运行执行体内的任务。

线程创建运行的范式
在以前我们也有自己的线程创建和运行的编程范式,一般是定义一个执行类重写run()方法,但是这种方式将执行体和执行的任务放在了一起,从软件工程的角度来看不利于解耦。一个线程的执行的意思是说线程通过执行对象执行了某个对象的某个任务,从这个角度来说,将任务的规定者从执行类中分离出来可以使得多线程编程的各个角色明晰出来,进而获得良好地解耦,以下就是线程创建和执行的编程范式:

public class FormalThreadClass {
        public static void main(String[] args) {
              Thread thread = new Thread( new MyRunnable());
              thread.start();
       }
}

class MyRunnable implements Runnable{
       MyTask myTask = new MyTask();
        @Override
        public void run() {
               myTask.doTask();
       }
}

class MyTask{
        public void doTask() {
              System. out.println( "This is real Tasking");
       }
}

synchronized关键字
synchronized可以用来修饰方法以构成同步方法,还可以修饰对象构成同步代码块,最终的目的都是一样的:
给要访问数据的线程添加一个规定:一次只允许一个线程访问数据。只有?当前正在访问数据”的线程结束访问之后,其他线程才允许访问这个数据。

关于synchronized关键字,有以下几点来说明:

  1. 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

  2. 当两个并发线程访问同一个对象object中的这个synchronized同步代码块或同步方法时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块或同步方法以后才能执行该代码块或同步方法。

  3. 然而,当一个线程访问object的一个synchronized同步代码块或同步方法时,另一个线程仍然可以访问该object中的非synchronized同步代码块或非synchronized同步方法。

  4. 尤其关键的是,当一个线程访问object的一个synchronized同步代码块或同步方法时,其他线程对object中所有其它synchronized同步代码块或同步方法的访问将被阻塞。

1.以下这个例子可以说明synchronized方法的这些特性,同步代码块也是一样:

① synchronized方法表面上它只是锁定了当前的方法本身,实际上当synchronized方法起作用的时候,整个对象的带有synchronized的方法都将被锁定,这也就是为什么当一个线程执行一个synchronized方法时,其他的线程除了不能访问当前的同步方法外还并不能访问其他的同步方法,而只能访问非synchronized方法,因为这种锁定是对象级别的。

public class ThreadTest {
        public static void main(String[] args) {
               final MyTask myTask = new MyTask();
              Thread thread1 = new Thread( new Runnable() {
                      public void run() {
                           myTask.doTask1();
                     }
              });
              Thread thread2 = new Thread( new Runnable() {
                      public void run() {
                           myTask.doTask2();
                     }
              });
              thread1.start();
              thread2.start();
       }
}

class MyTask{
        public synchronized void doTask1() {
               for ( int i = 0; i < 5; i++) {
                     System. out.println( "1 This is real Tasking "+i);
              }
       }
        public void doTask2() {
               for ( int i = 0; i < 5; i++) {
                     System. out.println( "2 This is real Tasking "+i);
              }
       }
}

② 如使在静态方法中用synchronized时,因为这个方法就不是仅属于某个对象而是属于整个类的了,所以一旦一个线程进入了这个代码块就会将这个类的所有对象的所有synchronized方法或synchronized同步代码块锁定,其他的线程就没有办法访问所有这些对象的synchronized方法和synchronized代码块(注意其他线程还是仍然能访问这些对象的非synchronized方法和synchronized代码块的),因此这种锁定是class级别的。

public class FormalThreadClass {
        public static void main(String[] args) {
              MyTask myTask1 = new MyTask();
              MyTask myTask2 = new MyTask();
              Thread thread1 = new Thread( new MyRunnable(myTask1));
              Thread thread2 = new Thread( new MyRunnable(myTask2));
              thread1.start();
              thread2.start();
       }
}

class MyRunnable implements Runnable {
       MyTask myTask;
        public MyRunnable(MyTask myTask) {
               this. myTask = myTask;
       }
        @Override
        public void run() {
              MyTask. doTask();
       }
}

class MyTask {
        public static synchronized void doTask() {
               for ( int i = 0; i < 5; i++) {
                     System. out.println(Thread. currentThread().getName()+" running "+i);
              }
       }
}

2.synchronized同步代码块是对一个对象作为参数进行锁定。

① 如在使用synchronized(this)时,一旦一个线程进入了这个代码块就会将整个对象的所有synchronized方法或synchronized同步代码块锁定,其他的线程就没有办法访问这个对象的synchronized方法和synchronized代码块(注意其他线程还是仍然能访问这个对象的非synchronized方法和synchronized代码块的)。

public class ThreadTest {
        public static void main(String[] args) {
               final MyTask myTask = new MyTask();
              Thread thread1 = new Thread( new Runnable() {
                      public void run() {
                           myTask.doTask1();
                     }
              });
              Thread thread2 = new Thread( new Runnable() {
                      public void run() {
                           myTask.doTask2();
                     }
              });
              thread1.start();
              thread2.start();
       }
}

class MyTask {
        public void doTask1() {

               synchronized (this) {
                      for ( int i = 0; i < 5; i++) {
                           System. out.println( "1 is running");
                     }
              }

       }

        public void doTask2() {
               for ( int i = 0; i < 5; i++) {
                     System. out.println( "2 is running");
              }
       }
}

所以:synchronized方法实际上等同于用一个synchronized块包住方法中的所有语句,然后在synchronized块的括号中传入this关键字。当然,如果是静态方法,需要锁定的则是class对象。

① 如在使用synchronized(.class)时,一旦一个线程进入了这个代码块就会将整个类的所有这个synchronized(.class) 同步代码块锁定,其他的线程就没有办法访问这个对象的synchronized(**.class) 代码块,这种锁也是class级别的,但要注意在这种情况下,其他线程仍然是可以访问仅做了synchronized的代码块或非静态方法的,因为它们仅仅是对当前对象的锁定。

public class FormalThreadClass {
        public static void main(String[] args) {
              MyTask myTask1 = new MyTask();
              MyTask myTask2 = new MyTask();
              Thread thread1 = new Thread( new MyRunnable(myTask1));
              Thread thread2 = new Thread( new MyRunnable(myTask2));
              thread1.start();
              thread2.start();
       }
}

class MyRunnable implements Runnable {
       MyTask myTask;
        public MyRunnable(MyTask myTask) {
               this. myTask = myTask;
       }
        @Override
        public void run() {
               myTask.doTask();
       }
}

class MyTask {
        public  void doTask() {
               synchronized (MyTask.class ) {
                      for ( int i = 0; i < 5; i++) {
                           System. out.println(Thread. currentThread().getName()+" running "+i);
                     }
              }
       }
}

总结起来这一部分:
synchronized方法是一种粗粒度的并发控制手段,某一时刻只能有一个线程执行该方法。synchroized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内synchroized块之外的代码是可以被多个线程同时访问到。


JinhaoPlus
1.5k 声望92 粉丝

扎瓦程序员