JAVA并发编程之线程局部变量

共享数据是并发程序最核心的问题之一,对于继承Thread类或者实现Runnable接口的对象来说尤其重要。

如果创建的对象实现了Runnable接口的类的实例,用它作为传入参数,并创建多个线程对象并启动这些线程,那么所有的线程将共享相同的属性。如果在一个线程中改变一个属性,所有线程都会被这个改变影响。

在某种情况下,这个对象的属性不需要被所有线程共享。JAVA提供了一个比较好的机制,即线程局部变量(Thread-Local Variable).

我们写一个简单的DEMO,一是具有刚才提到的问题,另一个使用线程局部变量机制解决这个问题。

1.创建一个类为UnsafeTask的类,它实现了Runnable接口。并且声明一个java.util.Date属性。

public class UnsafeTask implements Runnable{
       private Date startDate;  }

2.实现run()方法。这个方法初始化startDate属性,并且将值打印到控制台。让线程休眠一个随机时间,然后再次将值打印到控制台。

public void run(){
          startDate=new Date();
          System.out.printf("Starting Thread:%s:%s \n",Thread.currentThread().getId(),startDate);
          try{
              TimeUnit.SECONDS.sleep((int)Math.rint(Math.random()*10));
          }catch(InterruptedException e){
              e.printStackTrace();
          }
          System.out.printf("Thread Finished:%s :%s\n",Thread.currentThread().getId(),startDate);
       }

3.创建一个Main主类,并包含了一个main()方法。这个方法将创建一个UnsafeTask类对象,用它作为传入参数创建10个线程对象并启动10个线程,每个线程启动时间间隔为2秒。

public class Main {
     public static void main(String[] args){
         UnsafeTask task=new UnsafeTask();
         for(int i=0;i<10;i++){
             Thread thread=new Thread(task);
             thread.start();
             try{
                TimeUnit.SECONDS.sleep(2);
             }catch(InterruptedException e){
                 e.printStackTrace();
                
             }
         }
     }
}

4.运行结果如图

每个线程有一个不同的开始时间。他们结束时,三个线程就有相同的startDate属性值。

5.接下来我们使用线程局部变量机制来解决上述问题。

6.创建一个SafeTask类,用以实现Runnable接口。

public class SafeTask implements Runnable{}

7.声明一个ThreadLocal<Date>对象。这个对象是在initialValue()方法中隐式实现,返回当前日期。

private static ThreadLocal<Date> startDate=new ThreadLocal<Date>(){
           protected Date initalValue(){
               return new Date();
           }
      };

8.实现run()方法。跟上面方法实现一样功能,但是startDate属性方式改变了。

public void run(){
        System.out.printf("Starting Thread: %s : %s\n",Thread.currentThread().getId(),startDate.get());
          try {
              TimeUnit.SECONDS.sleep((int)Math.rint(Math.random()*10));
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          
          System.out.printf("Thread Finished: %s : %s\n",Thread.currentThread().getId(),startDate.get());
      }

9.Main主类同上,只是创建并作为参数传入的Runnable类对象不同而已。

public class Main {
     public static void main(String[] args){
         SafeTask task=new SafeTask();
         for(int i=0;i<3;i++){
             Thread thread=new Thread(task);
        
             try{
                TimeUnit.SECONDS.sleep(2);
             }catch(InterruptedException e){
                 e.printStackTrace();
                
             }
             thread.start();
         }
     }
}

10.运行结果。

时间: 2024-08-01 08:46:56

JAVA并发编程之线程局部变量的相关文章

Java并发编程:线程的同步

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

Java并发编程:线程的创建

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

【转】Java并发编程:线程池的使用

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool

19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去.因此,一般情况下,当队列满时,会让生产者交出对

Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) 一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线程创建完成时为新

Java并发编程:线程池的使用(转)

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool

JAVA并发编程3_线程同步之synchronized关键字

在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public class Test implements Runnable { private int i = 0; private int getNext() { return i++; } @Override public void run() { // synchronized while (true) { synchro

JAVA并发编程4_线程同步之volatile关键字

上一篇博客JAVA并发编程3_线程同步之synchronized关键字中讲解了JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile.volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程.从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块. 旧的内存模型:保证读写volatile都直接发生在main memory中. 在新的内存模型下(1.5)

JAVA并发编程2_线程安全&amp;内存模型

"你永远都不知道一个线程何时在运行!" 在上一篇博客JAVA并发编程1_多线程的实现方式中后面看到多线程中程序运行结果往往不确定,和我们预期结果不一致.这就是线程的不安全.线程的安全性是非常复杂的,没有任何同步的情况下,多线程的执行顺序是不可预测的.当多个线程访问同一个资源时就会出现线程安全问题.例如有一个银行账户,一个线程往里面打钱,一个线程取钱,要是得到不确定的结果那是多么可怕的事情. 引入: 例如下面的程序,在单线程下,执行两次i++理论上i的最终值是12,但是在多线程环境下则不