课程咨询 :186 8716 1620      qq:2066486918

昆明Java培训 > 达内新闻 > java线程安全synchronized
  • java线程安全synchronized

    发布:昆明Java培训      来源:达内新闻      时间:2016-09-28

  • 昆明Java培训机构的老师今天给大家讲java线程安全synchronized

    一、线程安全问题:

    并发编程的原则:设计并发编程的目的是为了使程序获得更高的执行效率,但绝不能出现数据一致性(数据准确)问题,如果并发程序连最基本的执行结果准确性都无法保证,那并发编程就没有任何意义。

    为什么会出现数据不正确:

    如果一个资源(变量,对象,文件,数据库)可以同时被很多线程使用就会出现数据不一致问题,也就是我们说的线程安全问题。这样的资源被称为共享资源或临界区。

    昆明Java培训机构的老师举个例子:

    一个共享变量m,现在有两个线程同时对它进行累加操作,各执行10000次,那么我么期待的结果是20000,但实际上并不是这样的。看代码:

    package com.jalja.base.threadTest;

    public class SynchronizedTest implements Runnable{

    private static volatile int m=0;

    public static void main(String[] args) {

    Runnable run=new SynchronizedTest();

    Thread thread1=new Thread(run);

    Thread thread2=new Thread(run);

    thread1.start();

    thread2.start();

    try {

    //join()使main线程等待这连个线程执行结束后继续执行下面的代码

    thread1.join();

    thread2.join();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println("m的最终结果:"+m);

    }

    public void run() {

    for(int i=0;i<10000;i++){

    m++;

    }

    }

    }

    无论运行多少次m总是小于20000。为什么会出现这样的结果呢?当线程thread1在将m++的结果写入内存之前,线程thread2已经从内存中读取了m的值,并在这个值(过时值)上进行++操作,最后将m=1写入内存中(可能就覆盖了thread1计算的m=1的值,也可能是出现thread1覆盖了thread2的值)。出现这样的结果是必然的。

    如何控制多线程操作共享数据引起的数据准确性问题呢?使用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问,也就是保证我们的共享资源每次只能被一个线程使用,一旦该资源被线程使用,其他线程将不得拥有使用权。在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。

    二、互斥访问之synchronized(同步方法或者同步块)

    互斥锁:顾名思义,就是互斥访问目的的锁。

    举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。

    在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,只有拥有该对象锁的线程才能访问。

    在Java中,可以使用synchronized关键字来标记一个需要同步的方法或者同步代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。通过这种方式达到我们上面提到的在同一时刻,只能有一个线程访问临界资源。

    synchronized用法:

    1、同步代码块

    synchronized(synObject) {

    }

    用法:将synchronized作用于一个给定的对象或类的一个属性,所以每当有线程执行这段代码块,该线程会先请求获取对象synObject的锁,如果该锁已被其他线程占有,那么新的线程只能等待,从而使得其他线程无法同时访问该代码块。

    package com.jalja.base.threadTest;

    public class SynchronizedTest implements Runnable{

    private static volatile int m=0;

    public static void main(String[] args) {

    Runnable run=new SynchronizedTest();

    Thread thread1=new Thread(run);

    Thread thread2=new Thread(run);

    thread1.start();

    thread2.start();

    try {

    //join()使main线程等待这连个线程执行结束后继续执行下面的代码

    thread1.join();

    thread2.join();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println("m的最终结果:"+m);

    }

    public void run() {

    synchronized (this) {

    for(int i=0;i<10000;i++){

    m++;

    }

    }

    }

    }

    该代码是使用当前对象作为互斥锁,下面我们使用类的一个属性作为互斥锁。

    package com.jalja.base.threadTest;

    public class SynchronizedTest implements Runnable{

    private static volatile int m=0;

    private Object object=new Object();

    public static void main(String[] args) {

    Runnable run=new SynchronizedTest();

    Thread thread1=new Thread(run);

    Thread thread2=new Thread(run);

    thread1.start();

    thread2.start();

    try {

    //join()使main线程等待这连个线程执行结束后继续执行下面的代码

    thread1.join();

    thread2.join();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println("m的最终结果:"+m);

    }

    public void run() {

    synchronized (object) {

    for(int i=0;i<10000;i++){

    m++;

    }

    }

    }

    }

    1、同步方法

    package com.jalja.base.threadTest;

    public class SynchronizedTest implements Runnable{

    private static int m=0;

    public static void main(String[] args) {

    Runnable run=new SynchronizedTest();

    Thread thread1=new Thread(run);

    Thread thread2=new Thread(run);

    thread1.start();

    thread2.start();

    try {

    //join()使main线程等待这连个线程执行结束后继续执行下面的代码

    thread1.join();

    thread2.join();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println("m的最终结果:"+m);

    }

    public synchronized void run() {

    for(int i=0;i<10000;i++){

    m++;

    }

    }

    }

    这段代码中,synchronzied作用于一个实例方法,就是说当线程在进入run()方法前,必须获取当前对象实例锁,本例中对象实例锁就是run。在这里昆明Java培训机构的老师提醒大家认真看这三段代码中main函数的实现,在这里我们使用Runnable创建两个线程,并且这两个线程都指向同一个Runnable接口实例,这样才能保证两个线程在工作中,使用同一个对象锁,从而保证线程安全。

    一种错误的理解:

    package com.jalja.base.threadTest;

    public class SynchronizedTest implements Runnable{

    private static int m=0;

    public static void main(String[] args) {

    Thread thread1=new Thread(new SynchronizedTest());

    Thread thread2=new Thread(new SynchronizedTest());

    thread1.start();

    thread2.start();

    try {

    //join()使main线程等待这连个线程执行结束后继续执行下面的代码

    thread1.join();

    thread2.join();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println("m的最终结果:"+m);

    }

    public synchronized void run() {

    for(int i=0;i<10000;i++){

    m++;

    }

    }

    }

    这段代码的运行结果是错误的,请看main函数的实现方式,使用Runnable创建两个线程,但是两个线程拥有各自的Runnable实例,所以当thread1线程进入同步方法时加的是自己的对象实例锁,而thread2在进入同步方法时关注的是自己的实例锁,两个线程拥有不同的对象实例锁,因此无法达到互斥的要求。

    略作改动:

    package com.jalja.base.threadTest;

    public class SynchronizedTest implements Runnable{

    private static int m=0;

    public static void main(String[] args) {

    Thread thread1=new Thread(new SynchronizedTest());

    Thread thread2=new Thread(new SynchronizedTest());

    thread1.start();

    thread2.start();

    try {

    //join()使main线程等待这连个线程执行结束后继续执行下面的代码

    thread1.join();

    thread2.join();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println("m的最终结果:"+m);

    }

    public void run() {

    for(int i=0;i<10000;i++){

    count();

    }

    }

    public static synchronized void count(){

    m++;

    }

    }

    这样处理结果就是我么想要的了,在这里我们将处理业务的代码封装成一个静态的同步方法,那现在访问该同步方法需要的是当前类的锁,而类在内存中只有一份,所以无论如何,他们使用的都是同一个锁。

    了解详情请登陆昆明达内Java培训官网(km.Java.tedu.cn)!

    推荐文章

上一篇:activiti源码分析:设计模式

下一篇:java中抽象、分装、继承和多态的理解

最新开班日期  |  更多

Java--零基础全日制班

Java--零基础全日制班

开班日期:12/29

Java--零基础业余班

Java--零基础业余班

开班日期:12/29

Java--周末提升班

Java--周末提升班

开班日期:12/29

Java--零基础周末班

Java--零基础周末班

开班日期:12/29

  • 网址:http://km .java.tedu.cn      地址:昆明市官渡区春城路62号证券大厦附楼6楼
  • 课程培训电话:186 8716 1620      qq:2066486918    全国服务监督电话:400-827-0010
  • 服务邮箱 ts@tedu.cn
  • 2001-2016 达内国际公司(TARENA INTERNATIONAL,INC.) 版权所有 京ICP证08000853号-56