摘要:尤其关键的是,当一个线程访问的一个同步代码块或同步方法时,其他线程对中所有其它同步代码块或同步方法的访问将被阻塞。同步代码块是对一个对象作为参数进行锁定。
为什么需要同步多线程?
线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源。我们采用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关键字,有以下几点来说明:
当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
当两个并发线程访问同一个对象object中的这个synchronized同步代码块或同步方法时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块或同步方法以后才能执行该代码块或同步方法。
然而,当一个线程访问object的一个synchronized同步代码块或同步方法时,另一个线程仍然可以访问该object中的非synchronized同步代码块或非synchronized同步方法。
尤其关键的是,当一个线程访问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块之外的代码是可以被多个线程同时访问到。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64615.html
摘要:本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,的使用,定时器,单例模式,以及线程状态与线程组。源码采用构建,多线程这部分源码位于模块中。通知可能等待该对象的对象锁的其他线程。 本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock的使用,定时器,单例模式,以及线程状态与线程组。 写在前面 花了一周时...
摘要:关键字加到非静态方法上持有的是对象锁。线程和线程持有的锁不一样,所以和运行同步,但是和运行不同步。所以尽量不要使用而使用参考多线程编程核心技术并发编程的艺术如果你觉得博主的文章不错,欢迎转发点赞。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) J...
摘要:转载请备注地址多线程学习二将分为两篇文章介绍同步方法另一篇介绍同步语句块。如果两个线程同时操作对象中的实例变量,则会出现非线程安全,解决办法就是在方法前加上关键字即可。 转载请备注地址: https://blog.csdn.net/qq_3433... Java多线程学习(二)将分为两篇文章介绍synchronized同步方法另一篇介绍synchronized同步语句块。系列文章传送门...
摘要:在两个线程访问同一个对象中的同步方法时一定是线程安全的。当一个线程访问的一个同步代码块时,其他线程对同一个钟所有其他同步代码块的访问被阻塞,这说明使用的对象监视器是一个。 非线程安全其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是脏读,也就是取到的数据其实是被更改过的。而线程安全就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。 非线程安全问题存...
摘要:无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。另外在中引入了自适应的自旋锁。和关键字的总结推荐一 该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb... 本文是对 synchronized 关键字使用、底层原理、JD...
阅读 1181·2023-04-26 02:42
阅读 1633·2021-11-12 10:36
阅读 1780·2021-10-25 09:47
阅读 1262·2021-08-18 10:22
阅读 1801·2019-08-30 15:52
阅读 1213·2019-08-30 10:54
阅读 2635·2019-08-29 18:46
阅读 3495·2019-08-26 18:27