深切怀念传智播客张孝祥老师,特将其代表作——Java并发库视频研读两遍,受益颇丰,记以后阅
05. 线程范围内共享变量的概念与作用
线程范围内共享数据图解:
代码演示:
class ThreadScopeShareData
{
三个模块共享数据,主线程模块和AB模块
privatestatic int data = 0; 准备共享的数据
存放各个线程对应的数据
private Map<Thread, Integer>threadData = new HashMap<Thread, Integer>();
publicstatic void main(String[] args)
{ 创建两个线程
for (int i=0;i<2; i++)
{
new Thread(
new Runnable()
{
public void run()
{现在当前线程中修改一下数据,给出修改信息
data = newRandom().nextInt();
SOP(Thread.currentThread().getName()+将数据改为+data);
将线程信息和对应数据存储起来
threadData.put(Thread.currentThread(),data);
使用两个不同的模块操作这个数据,看结果
new A().get();
new B().get();
}
}
).start();
}
}
staticclass A
{
public void get()
{
data =threadData.get(Thread.currentThread());
SOP(A+Thread.currentThread().getName()+拿到的数据+data);
}
}
staticclass B
{
public void get()
{
data =threadData.get(Thread.currentThread());
SOP(B+Thread.currentThread().getName()+拿到的数据+data);
}
}
}
结果并没与实现线程间的数据同步,两个线程使用的是同一个线程的数据。要解决这个问题,可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据。代码实现:上面红色部分
程序中存在的问题:获取的数据与设置的数据不同步
Thread-1共享数据设置为:-997057737
Thread-1--A模块数据:-997057737
Thread-0共享数据设置为:11858818
Thread-0--A模块数据:11858818
Thread-0--B模块数据:-997057737
Thread-1--B模块数据:-997057737
最好将Runnable中设置数据的方法也写在对应的模块中,与获取数据模块互斥,以保证数据同步
public class ThreadScopeShareData {
privatestatic int data = 0;
privatestatic Map<Thread, Integer> threadData = new HashMap<Thread,Integer>();
publicstatic void main(String[] args) {
for(inti=0;i<2;i++){
newThread(new Runnable(){
@Override
publicvoid run() {
intdata = new Random().nextInt();
System.out.println(Thread.currentThread().getName()
+" has put data :" + data);
threadData.put(Thread.currentThread(),data);
newA().get();
newB().get();
}
}).start();
}
}
staticclass A{
publicvoid get(){
intdata = threadData.get(Thread.currentThread());
System.out.println("Afrom " + Thread.currentThread().getName()
+" get data :" + data);
}
}
staticclass B{
publicvoid get(){
intdata = threadData.get(Thread.currentThread());
System.out.println("Bfrom " + Thread.currentThread().getName()
+" get data :" + data);
}
}
}
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 void get(){ int data = threadData.get(Thread.currentThread()); System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); } } static class B{ public void get(){ int data = threadData.get(Thread.currentThread()); System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); } } }一个最典型的例子就是JavaEE中的数据库操作的Connection。一个线程内的所有操作都用同一个Connection,如此才能统一进行Tx等操作。
06.ThreadLocal类及应用技巧
多个模块在同一个线程中运行时要共享同一份数据,实现线程范围内的数据共享可以用上一节中所用的方法。
JDK1.5提供了ThreadLocal类来方便实现线程范围内的数据共享,它的作用就相当于上一节中的Map。
ThreadLocal的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map集合中增加一条记录,key就是各自的线程,value就是各自的set方法传进去的值。
在线程结束时可以调用ThreadLocal.clear()方法用来更快释放内存,也可以不调用,因为线程结束后也可以自动释放相关的ThreadLocal变量。
一个ThreadLocal对象只能记录一个线程内部的一个共享变量,需要记录多个共享数据,可以创建多个ThreadLocal对象,或者将这些数据进行封装,将封装后的数据对象存入ThreadLocal对象中。
将数据对象封装成单例,同时提供线程范围内的共享数据的设置和获取方法,提供已经封装好了的线程范围内的对象实例,使用时只需获取实例对象即可实现数据的线程范围内的共享,因为该对象已经是当前线程范围内的对象了。下边给出张老师的优雅代码:
package cn.itheima;
import java.util.Random;
publicclassThreadLocalShareDataDemo
{ /**06.ThreadLocal类及应用技巧
* 将线程范围内共享数据进行封装,封装到一个单独的数据类中,提供设置获取方法
* 将该类单例化,提供获取实例对象的方法,获取到的实例对象是已经封装好的当前线程范围内的对象
*/
publicstaticvoidmain(String[] args)
{
for (inti=0; i<2; i++)
{
newThread(
newRunnable()
{
publicvoidrun()
{
intdata =new Random().nextInt(889);
System.out.println(Thread.currentThread().getName()+"产生数据:"+data);
MyDatamyData = MyData.getInstance();
myData.setAge(data);
myData.setName("Name:"+data);
newA().get();
newB().get();
}
}).start();
}
}
staticclass A
{ //可以直接使用获取到的线程范围内的对象实例调用相应方法
Stringname = MyData.getInstance().getName();
intage =MyData.getInstance().getAge();
publicvoidget()
{
System.out.println(Thread.currentThread().getName()+"--AA name:"+name+"...age:"+age);
}
}
staticclass B
{
//可以直接使用获取到的线程范围内的对象实例调用相应方法
Stringname = MyData.getInstance().getName();
intage =MyData.getInstance().getAge();
publicvoidget()
{
System.out.println(Thread.currentThread().getName()+"--BB name:"+name+"...age:"+age);
}
}
staticclassMyData
{
privateStringname;
privateintage;
publicString getName()
{
returnname;
}
publicvoidsetName(String name)
{
this.name =name;
}
publicintgetAge()
{
returnage;
}
publicvoidsetAge(int age)
{
this.age =age;
}
//单例
privateMyData() {};
//提供获取实例方法
publicstaticMyData getInstance()
{
//从当前线程范围内数据集中获取实例对象
MyDatainstance = threadLocal.get();
if(instance==null)
{
instance= new MyData();
threadLocal.set(instance);
}
returninstance;
}
//将实例对象存入当前线程范围内数据集中
staticThreadLocal<MyData>threadLocal =newThreadLocal<MyData>();
}
}
张孝祥老师的代码
public class ThreadLocalTest {
privatestatic ThreadLocal<Integer> x = new ThreadLocal<Integer>();
privatestatic ThreadLocal<MyThreadScopeData> myThreadScopeData = newThreadLocal<MyThreadScopeData>();
publicstatic void main(String[] args) {
for(inti=0;i<2;i++){
newThread(new Runnable(){
@Override
publicvoid run() {
intdata = new Random().nextInt();
System.out.println(Thread.currentThread().getName()
+" has put data :" + data);
x.set(data);
/* MyThreadScopeDatamyData = new MyThreadScopeData();
myData.setName("name"+ data);
myData.setAge(data);
myThreadScopeData.set(myData);*/
MyThreadScopeData.getThreadInstance().setName("name"+ data);
MyThreadScopeData.getThreadInstance().setAge(data);
newA().get();
newB().get();
}
}).start();
}
}
staticclass A{
publicvoid get(){
intdata = x.get();
System.out.println("A from " +Thread.currentThread().getName()
+" get data :" + data);
/* MyThreadScopeDatamyData = myThreadScopeData.get();;
System.out.println("Afrom " + Thread.currentThread().getName()
+" getMyData: " + myData.getName() + "," +
myData.getAge());*/
MyThreadScopeDatamyData = MyThreadScopeData.getThreadInstance();
System.out.println("Afrom " + Thread.currentThread().getName()
+" getMyData: " + myData.getName() + "," +
myData.getAge());
}
}
staticclass B{
publicvoid get(){
intdata = x.get();
System.out.println("Bfrom " + Thread.currentThread().getName()
+" get data :" + data);
MyThreadScopeDatamyData = MyThreadScopeData.getThreadInstance();
System.out.println("Bfrom " + Thread.currentThread().getName()
+" getMyData: " + myData.getName() + "," +
myData.getAge());
}
}
}
class MyThreadScopeData{
privateMyThreadScopeData(){}
publicstatic /*synchronized*/ MyThreadScopeData getThreadInstance(){
MyThreadScopeDatainstance = map.get();
if(instance== null){
instance= new MyThreadScopeData();
map.set(instance);
}
returninstance;
}
//privatestatic MyThreadScopeData instance = null;//new MyThreadScopeData();
privatestatic ThreadLocal<MyThreadScopeData> map = newThreadLocal<MyThreadScopeData>();
privateString name;
privateint age;
publicString getName() {
returnname;
}
publicvoid setName(String name) {
this.name= name;
}
publicint getAge() {
returnage;
}
publicvoid setAge(int age) {
this.age= age;
}
}
public class ThreadLocalTest { private static ThreadLocal<Integer> x = new ThreadLocal<Integer>(); private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>(); 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); x.set(data); /* MyThreadScopeData myData = new MyThreadScopeData(); myData.setName("name" + data); myData.setAge(data); myThreadScopeData.set(myData);*/ MyThreadScopeData.getThreadInstance().setName("name" + data); MyThreadScopeData.getThreadInstance().setAge(data); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ int data = x.get(); System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); /* MyThreadScopeData myData = myThreadScopeData.get();; System.out.println("A from " + Thread.currentThread().getName() + " getMyData: " + myData.getName() + "," + myData.getAge());*/ MyThreadScopeData myData = MyThreadScopeData.getThreadInstance(); System.out.println("A from " + Thread.currentThread().getName() + " getMyData: " + myData.getName() + "," + myData.getAge()); } } static class B{ public void get(){ int data = x.get(); System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); MyThreadScopeData myData = MyThreadScopeData.getThreadInstance(); System.out.println("B from " + Thread.currentThread().getName() + " getMyData: " + myData.getName() + "," + myData.getAge()); } } } class MyThreadScopeData{ private MyThreadScopeData(){} public static /*synchronized*/ MyThreadScopeData getThreadInstance(){ MyThreadScopeData instance = map.get(); if(instance == null){ instance = new MyThreadScopeData(); map.set(instance); } return instance; } //private static MyThreadScopeData instance = null;//new MyThreadScopeData(); private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>(); 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; } }
07. 多个线程之间共享数据的方式探讨
例子:卖票:多个窗口同时卖这100张票,票就需要多个线程共享
a、如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个对象中有共享数据。
卖票就可以这样做,每个窗口都在做卖票任务,卖的票都是同一个数据。
public classMultiThreadShareData {
public static void main(String[] args) {
ShareData1 data1 = new ShareData1();
newThread(data1).start();
new Thread(data1).start();
}
}
class ShareData1implements Runnable{
private int count = 100;
@Override
public void run() {
// TODO Auto-generatedmethod stub
while(true){
count--;
}
}
}
b、如果每个线程执行的代码不同,就需要使用不同的Runnable对象,有两种方式实现
Runnable对象之间的数据共享:
a) 将共享数据单独封装到一个对象中,同时在对象中提供操作这些共享数据的方法,可以方便实现对共享数据各项操作的互斥和通信。
public classMultiThreadShareData {
public static void main(String[] args) {
final ShareData1 data1 = new ShareData1();
new Thread(new Runnable(){
@Override
public void run() {
data1.decrement();
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
data1.increment();
}
}).start();
}
}
class ShareData1{
private int j = 0;
public synchronized void increment(){
j++;
}
public synchronized void decrement(){
j--;
}
}
b) 将各个Runnable对象作为某个类的内部类,共享数据作为外部类的成员变量,对共享数据的操作方法也在外部类中提供,以便实现互斥和通信,内部类的Runnable对象调用外部类中操作共享数据的方法即可。
public classMultiThreadShareData {
public static void main(String[] args) {
ShareData1 data2 = newShareData1();
newThread(new MyRunnable1(data2)).start();
newThread(new MyRunnable2(data2)).start();
}
}
class MyRunnable1 implementsRunnable{
private ShareData1 data1;
public MyRunnable1(ShareData1 data1){
this.data1= data1;
}
public void run() {
data1.decrement();
}
}
class MyRunnable2 implements Runnable{
privateShareData1 data1;
publicMyRunnable2(ShareData1 data1){
this.data1= data1;
}
public void run() {
data1.increment();
}
}
class ShareData1{
private int j = 0;
public synchronized void increment(){
j++;
}
public synchronized void decrement(){
j--;
}
}
注意:要同步互斥的几段代码最好分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。