您的位置:首页 > 编程语言 > Java开发

关于Java多线程和并发运行的学习(二)

2014-09-09 16:51 197 查看
这几天生病了,所以学的比较少,只学习了关于线程中的线程范围数据共享,ThreadLocal以及多线程数据共享。

在这里记下我的理解和代码,其实理解都写在代码注释里了,个人觉得这样更加适合以后复习。

(上一篇我讲的是关于线程的创建,计时器,同步锁,线程间的通信。)

第一个,关于线程范围内的数据共享。

数据共享,一般是多个事件在串在同一个线程里调用同一个数据。下面一个小例子就可以说明清楚:
package Multithreading;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class ThreadScopeShareData {
//private static int data = 0;
private static Map<Thread,Integer> threadData = new HashMap<Thread,Integer>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName()
+ " has put data : " + data);
threadData.put(Thread.currentThread(), data);
new A().get();
new B().get();
}
}).start();
}
}

static class A {
public int get() {
int data = threadData.get(Thread.currentThread());
System.out.println("A from " + Thread.currentThread().getName()
+ " has put data : " + data);
return data;
}
}

static class B {
public int get() {
int data = threadData.get(Thread.currentThread());
System.out.println("B from " + Thread.currentThread().getName()
+ " has put data : " + data);
return data;
}
}
}


运行结果是:



不过上面的例子只是说明线程范围的数据共享。一般在开发时,我们使用ThreadLocal这个东西。

第二个,关于ThreadLocal:

ThreadLocal实现线程范围的共享变量:用于实现线程内的数据共享, 即对于相同的程序代码,多个模块在同一个线程中运行时要共享的一份数据,而在另外线程中运行时又共享另外一份数据。

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

ThreadLocal的应用场景:

* 1.订单处理包含一系列操作:减少库存量,增加一条流水台帐,修改总账,这几个操作要在同一个事务完成,也就是在同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。

* 2.银行转账包含一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象, 转入和转出操作的代码分别是两个不同的账户对象的方法

* 3.例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中调用getContext方法,拿到的都是同一个。

这面的代码可以清晰表现出ThreadLocal的应用:

MyThreadScopeData.java

package Multithreading;

class MyThreadScopeData {
/**
*  单例模式的演变
*/
private MyThreadScopeData() {}// 防止外部使用new
private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();

public static/* synchronized */MyThreadScopeData getThreadInstance() {
/**
* 此处可以不加synchronized,因为A线程进来,instance如果为null,就会执行new,如果在其未返回值前线程切换,
* B线程进来, 获取instance,发现其值为空,紧接着会执行new,但是这个两个不同的线程,操作的是不同的数据,
* 其创建的实例属于各线程自己的实例,互不影响,所以这里不需要同步锁。
*/
MyThreadScopeData instance = map.get();
if (instance == null) {
instance = new MyThreadScopeData();
map.set(instance);
}
return instance;
}

private String name;
private int age;
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}


ThreadLocalTest.java

package Multithreading;

import java.util.Random;

public class ThreadLocalTest {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
public void run() {
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName()
+ " has put data : " + data);

MyThreadScopeData.getThreadInstance().setName("name:" + data);
MyThreadScopeData.getThreadInstance().setAge(data);
new A().get();
new B().get();
}
}).start();
}
}

static class A {
public void get() {
MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
System.out.println("A from "+Thread.currentThread().getName() + " getMyData: "
+ myData.getName());
System.out.println("A from "+Thread.currentThread().getName() + " getMyData: "
+ myData.getAge());
}
}

static class B {
public void get() {
MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
System.out.println("B from "+Thread.currentThread().getName() + " getMyData: "
+ myData.getName());
System.out.println("B from "+Thread.currentThread().getName() + " getMyData: "
+ myData.getAge());
}
}
}


其中利用的单例模式的演变形式,可以通过获取实例来得到对象,而不是new一个对象。

* 总结:一个ThreadLocal代表一个变量,故其中只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。如果有一个自变量要线程共享呢?那请先定义一个对象来装这个自变量, 然后在ThreadLocal中存储这个对象。

第三个,多线程间的数据共享

如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有共享数据,例如:卖票系统就可以这么做。

如果每个线程执行的代码不同,这时候需要用不同的Runnable对象, 有如下两种方式来实现这些Runnable对象之间的数据共享:

* 1.将共享数据封装在A对象中,然后将A对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到各个Runnable对象中去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

* 2.将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量, 每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

* 3.上面两种方式的组合:将共享数据封装在A对象中,每个线程对共享数据的操作方法也分配到A对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量, 每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

总之,要同步互斥的几段代码分别放在几个独立的方法中,然后把这些方法再放在同一个类中, 这样比较容易实现他们之间的同步互斥和通信。

********* 另外的例子在《面试宝典》的第28题。

下面有一个小例子:

package Multithreading;

public class MultiThreadShareData {
public static void main(String[] args) {
final ShareData1 data1 = new ShareData1();
new Thread(new Runnable() {
public void run() {
data1.increment();
}
}).start();
new Thread(new Runnable() {
public void run() {
data1.decrement();
}
}).start();
}
}

class ShareData1 {

private int j = 0;;

public synchronized void increment() {
j++;
}

public synchronized void decrement() {
j--;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: