java线程安全问题

线程安全的本质体现在两个方面,

A变量安全:多线程同时运行一段代码

B线程同步:一个线程还没执行完,另一个线程又进来接着执行。

看个简单的例子。
Java代码

    public class ThreadSafe implements java.lang.Runnable {  

        int num = 1;
        public void run() {
            for (int i = 0; i < 3; i++) {
                num = num + 1;
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("num is value +==="+Thread.currentThread().getName()+"---------" + num);
            }
        }  

    }

TestMan.java 写道

package com.java.thread.test;

public class TestMan {
public static void main(String[] args) {
Runnable safe=new ThreadSafe();
Thread thread1=new Thread(safe,"thread1");
Thread thread2=new Thread(safe,"thread2");
thread1.start();
thread2.start();
}

}

运行结果


num is value +===thread2---------3
num is value +===thread1---------4
num is value +===thread2---------5
num is value +===thread1---------6
num is value +===thread1---------7
num is value +===thread2---------7

很明显是错误的,应为两个线程共享同一个变量。这里就是变量的安全问题。

解决办法:

1抛弃单实例,多线程的方式,用多实例,多线程的方式,这样就和单线程是一个样了,不会出错,但是是最接近传统的编程模式

2不要用类的实例变量,经可能把变量封装到方法内部。

1类的解决办法的代码。
Java代码

   public class TestMan {
        public static void main(String[] args) {
            Runnable safe=new ThreadSafe();
            Runnable safe2=new ThreadSafe();
            Thread thread1=new Thread(safe,"thread1");
            Thread thread2=new Thread(safe2,"thread2");
            thread1.start();
            thread2.start();
        }  

    }  

运行结果

num is value +===thread1---------2
num is value +===thread2---------2
num is value +===thread1---------3
num is value +===thread2---------3
num is value +===thread1---------4
num is value +===thread2---------4

2类解决办法的代码
Java代码

  public class ThreadSafe implements java.lang.Runnable {  

        public void run() {
            int num = 1;
            for (int i = 0; i < 3; i++) {
                num = num + 1;
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("num is value +==="+Thread.currentThread().getName()+"---------" + num);
            }
        }  

    }

Java代码

 public class TestMan {
        public static void main(String[] args) {
            Runnable safe=new ThreadSafe();  

            Thread thread1=new Thread(safe,"thread1");
            Thread thread2=new Thread(safe,"thread2");
            thread1.start();
            thread2.start();
        }  

    }

运行结果

num is value +===thread2---------2
num is value +===thread1---------2
num is value +===thread1---------3
num is value +===thread2---------3
num is value +===thread1---------4
num is value +===thread2---------4

这两种办法,比较推荐适用第二个办法,就是把变量经可能的封装到风发内部,这样他们就是线程的私有变量了。另外,从jdk1.2后,推出了 threadlocal 对象,它作为线程的一个局部变量,可以为每个线程创建一个副本,用来保存每个线程的属性,各是各的,互不干扰。单每个 threadlocal变量只能保存一个变量,假如有多个变量要保存,那么就要写多个threadlocal对象。

我们把代码改写一下。
Java代码

  public class ThreadSafe implements java.lang.Runnable {
        ThreadLocal local=new ThreadLocal();
        public void run() {
            for (int i = 0; i < 3; i++) {
                if(local.get()==null){
                    local.set(new Integer(1));
                }
                int num=local.get().intValue();
                num=num+1;
                local.set(new Integer(num));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("num is value +==="+Thread.currentThread().getName()+"---------" + local.get().intValue());
            }
        }  

    }

Java代码

public class TestMan {
        public static void main(String[] args) {
            Runnable safe=new ThreadSafe();
            Thread thread1=new Thread(safe,"thread1");
            Thread thread2=new Thread(safe,"thread2");
            thread1.start();
            thread2.start();
        }  

    }

运行结果

num is value +===thread2---------2
num is value +===thread1---------2
num is value +===thread1---------3
num is value +===thread2---------3
num is value +===thread1---------4
num is value +===thread2---------4

结果是一样的,所以这里变量安全有3个办法可以解决。

然后在说说线程的同步的问题。我们看上面的运行结果。

num is value +===thread2---------2
num is value +===thread1---------2
num is value +===thread1---------3
num is value +===thread2---------3
num is value +===thread1---------4
num is value +===thread2---------4

就 可以看出他们不是线程同步的,是thread1和thread2在交替执行的。在有些情况下,要求一段代码在运行的过程中是一个不可分割的实体,就是原子的。就是说当已经有线程在执行这段代码的时候,其他的线程必须等待他执行完毕后才能竟来执行,这就是所谓的线程同步。

java通过同步锁来执行线程的同步和等待,也就是说,要不间断执行的代码需要放在synchronized关键字标识的代码块中。可以用来修饰代码块,也可以修饰方法。

Java代码

public class ThreadSafe implements java.lang.Runnable{
    int num = 1;
    public void run() {
        for (int i = 0; i < 3; i++) {
        	synchronized (this) {
                num = num + 1;
	            System.out.println("num is value +==="+Thread.currentThread().getName()+"---------" + num);
        	}
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    }

Java代码

 public class TestMan {
        public static void main(String[] args) {
            Runnable safe=new ThreadSafe();
            Thread thread1=new Thread(safe,"thread1");
            Thread thread2=new Thread(safe,"thread2");
            thread1.start();
            thread2.start();
        }  

    }

运行结果

num is value +===thread1---------2
num is value +===thread2---------3
num is value +===thread2---------4
num is value +===thread1---------5
num is value +===thread2---------6
num is value +===thread1---------7

Java代码

public class ThreadSafe implements java.lang.Runnable {
        public void run() {
            int num = 1;
            synchronized (this) {
                for (int i = 0; i < 3; i++) {
                    num = num + 1;
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("num is value +==="
                            + Thread.currentThread().getName() + "---------" + num);
                }
            }
        }  

    }

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂

分享:

发表评论

邮箱地址不会被公开。 必填项已用*标注

😉😐😡😈🙂😯🙁🙄😛😳😮:mrgreen:😆💡😀👿😥😎😕

验证码 *