JAVA并发编程——线程协作通信(一)

线程间的共享

一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。

一、可见性

如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性。如果一个变量同时在多个线程的工作内存中存在副本,那么这个变量就叫共享变量

二、 JMM(java内存模型)

多个线程同时对主内存的一个共享变量进行读取和修改时,首先会读取这个变量到自己的工作内存中成为一个副本,对这个副本进行改动之后,再更新回主内存中变量所在的地方。

由于CPU时间片是以线程为最小单位,所以这里的工作内存实际上就是指的物理缓存,CPU运算时获取数据的地方;而主内存也就是指的是内存,也就是原始的共享变量存放的位置。

在这里插入图片描述
有关共享变量的两条规定:
1、线程对共享变量的所有操作必须在工作内存中进行,不能直接操作主内存。
2、不同线程间不能访问彼此的工作内存中的变量,线程间变量值的传递都必须经过主内存。

如果一个线程1对共享变量x的修改对线程2可见的话,需要经过下列步骤:
1、线程1将更改x后的值更新到主内存。
2、主内存将更新后的x的值更新到线程2的工作内存中x的副本。

所以,要实现共享变量的可见性必须保证下列两点:
1、线程对工作内存中副本的更改能够及时的更新到主内存上。
2、其他线程能够及时的将主内存上共享变量的更新刷新到自己工作内存的该变量的副本上。

Java中可以通过synchronized、volatile、java concurrent类来实现共享变量的可见性。

三、synchronized实现可见性

synchronized 实际上是对访问修改共享变量的代码块进行加互斥锁,多个线程对synchronized代码块的访问时,某一时刻仅仅有一个线程在访问和修改代码块中的内容(加锁),其他所有的线程等待该线程离开代码块时(释放锁)才有机会进入synchronized代码块。
所以某一个线程进入synchronized代码块前后,执行过程入如下:

a.线程获得互斥锁
b.清空工作内存
c.从主内存拷贝共享变量最新的值到工作内存成为副本
d.执行代码
e.将修改后的副本的值刷新回主内存中
f.线程释放锁

随后,其他代码在进入synchronized代码块的时候,所读取到的工作内存上共享变量的值都是上一个线程修改后的最新值。

同步代码块

synchronized(同步对象){ 
 需要同步的代码 
}

同步方法
除了可以将需要的代码设置成同步代码块外,也可以使用 synchronized 关键字将一个方法声明为同步方法。


synchronized 方法返回值 方法名称(参数列表){ 
 
}

示例代码:演示synchronized关键字的作用,不加和加上的区别,最后打印的线程应该打印100000。

public class SynTest {
	
	private int age = 100000;//初始100000
	
    private static class TestThread implements Runnable{

    	private SynTest synTest;

    	public TestThread(SynTest synTest) {
    		this.synTest = synTest;
		}
    	
    	@Override
	    public void run() {
    		for(int i=0;i<100000;i++) {//递增100000
    			synTest.test();
    		}
			System.out.println(Thread.currentThread().getName()
					+" age =  "+synTest.getAge());
	    }
    }
    
	public synchronized void test() {
		age++;
	}
	
	public void test2() {
		synchronized(this){
			age--;
		}
	}
	
	public int getAge() {
		return age;
	}
	

	public static void main(String[] args) throws InterruptedException {
		SynTest synTest = new SynTest();
		TestThread testThread = new TestThread(synTest);
		Thread endThread = new Thread(testThread);
		endThread.start();
		for(int i=0;i<100000;i++) {//递减100000
			synTest.test2();
		}
		System.out.println(Thread.currentThread().getName()
				+" age =  "+synTest.getAge());		

	}
}

注:实例锁和类锁是不同的,两者可以并行。
锁的实例不一样,也是可以并行的。

四、volatile实现可见性

Volatile 只保证内存可见性,不保证操作的原子性。
某个变量只有一个线程进行修改,其他的线程都是读取,这个时候要求可见性,低原子性,可以考虑使用:volatile。

volatile如何实现可见性?

volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中。这样一来,不同的线程都能及时的看到该变量的最新值。

示例代码:

public class VolatileUnsafe {
	
	private static class VolatileVar implements Runnable {

	    private static Object o = new Object();
	    private volatile int a = 0;

	    @Override
	    public void run() {
	        synchronized (o){
                a = a + 1;
                System.out.println(Thread.currentThread().getName() + ":----" + a);
                SleepTools.ms(100);
                a = a + 1;
                System.out.println(Thread.currentThread().getName() + ":----" + a);
            }
	    }
	}
	
    public static void main(String[] args) {

    	VolatileVar v = new VolatileVar();

        Thread t1 = new Thread(v);
        Thread t2 = new Thread(v);
        Thread t3 = new Thread(v);
        Thread t4 = new Thread(v);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

}

volatile适用情况:
1、对变量的写入操作不依赖当前值
比如自增自减、number = number + 5等(不满足)

2、当前volatile变量不依赖于别的volatile变量
比如 volatile_var > volatile_var2这个不等式(不满足)

synchronized和volatile比较
1、volatile不需要同步操作,所以效率更高,不会阻塞线程,但是适用情况比较窄。

2、volatile读变量相当于加锁(即进入synchronized代码块),而写变量相当于解锁(退出synchronized代码块)。

3、synchronized既能保证共享变量可见性,也可以保证锁内操作的原子性;volatile只能保证可见性。

五、ThreadLocal的使用

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

示例代码:

public class UseThreadLocal {
	/*声明一个ThreadLocal类型的static变量,所有线程共享,并初始化为1*/
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    /**
     * 运行3个线程
     */
    public void StartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestThread(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }
    
    /**
     *类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
     */
    public static class TestThread implements Runnable{
        int id;
        public TestThread(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            Integer s = threadLocal.get();//获得变量的值
            s = s+id;
            threadLocal.set(s);//将值写回变量
            System.out.println(Thread.currentThread().getName()+':'+threadLocal.get());
        }
    }

    public static void main(String[] args){
    	UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}

相关文章
Java并发:ThreadLocal详解
Java并发编程:深入剖析ThreadLocal
ThreadLocal用法详解和原理

参考文章

Java多线程共享变量控制
Java多线程看这一篇就足够了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值