Java并发基础(3)——线程的共享

目录

一、synchronized关键字

1.1 对象锁demo

1.1.1、测试两个线程锁同一个对象

1.1.2、测试两个线程锁两个对象

1.2 测试类锁demo

1.3 小结

二、volatile关键字

2.1 demo

三、ThreadLocal

3.1 demo


常见有synchronized关键字(也称内置锁)、volatile关键字、ThreadLocal

在学习前,先了解一下两个概念

原子性:类似数据库事务概念,保证一个线程安全

可见性:每次获取时都从主内存中取值

一、synchronized关键字

Synchronized 我们需要知道:

1、Synchronized 满足原子性和可见性

2、Synchronized 关键字有两种:对象锁和类锁

对象锁:假设一个普通的类BuySomething,有一个普通的方法buy

private synchronized void buy(){ .... }

此时,synchronized 锁的是一个对象(new 出来的BuySomething对象)

类锁:如果还有一个静态方法buy2

Private static synchronized void buy2(){....}

此时,synchronized 锁的是类的class对象(BuySomething的class对象)

new 出来的BuySomething对象 可能有多个

BuySomething的class对象 只有一个

下面我们用代码理解一下对象锁和类锁

1.1 对象锁demo

1.1.1、测试两个线程锁同一个对象

public class TestSynchronized {
	
	//使用对象锁
    private synchronized void ObjectSyn(){
        SleepTools.second(3);
        System.out.println(Thread.currentThread().getName() +"使用对象锁");
        SleepTools.second(3);
    }
    
    //使用对象锁的线程
    private static class ObjectSynThread implements Runnable{
        private TestSynchronized testSynchronized;
        public ObjectSynThread(TestSynchronized testSynchronized) {
            this.testSynchronized = testSynchronized;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() +"使用对象锁的线程--start");
            testSynchronized.ObjectSyn();
            System.out.println(Thread.currentThread().getName() +"使用对象锁的线程--end");
        }
    }
    public static void main(String[] args) {
    	//先new出一个对象
		TestSynchronized testSynchronized1 = new TestSynchronized();
		
		Thread thread1 = new Thread(new ObjectSynThread(testSynchronized1));
		Thread thread2 = new Thread(new ObjectSynThread(testSynchronized1));
		thread1.start();
		thread2.start();
		
		SleepTools.second(30);
	}
}

运行结果

从结果中可以看出,线程0和线程1都启动了,线程0先拿到对象锁先执行完,两个线程之间存在竞争

1.1.2、测试两个线程锁两个对象

修改上面代码中的main方法,其他不变

 public static void main(String[] args) {
    	//先new出两个对象
		TestSynchronized testSynchronized1 = new TestSynchronized();
        TestSynchronized testSynchronized2 = new TestSynchronized();
		
		Thread thread1 = new Thread(new ObjectSynThread(testSynchronized1));
		Thread thread2 = new Thread(new ObjectSynThread(testSynchronized2));
		thread1.start();
		thread2.start();		
		SleepTools.second(30);
	}

运行结果如下

可以看出,线程0和线程1互不影响,可以同时运行

1.2 测试类锁demo

public class TestSynchronized2 {	
	 //使用类锁
    private static synchronized  void ClassSyn(){
        SleepTools.second(3);
        System.out.println(Thread.currentThread().getName() +"使用类锁");
        SleepTools.second(3);
    }
	
	//使用类锁的线程
    private static class ClassSynThread extends Thread{
        @Override
        public void run() {
        	System.out.println(Thread.currentThread().getName() +"使用类锁的线程--start");
            ClassSyn();
            System.out.println(Thread.currentThread().getName() +"使用类锁的线程--end");
        }
    }
    
    public static void main(String[] args) {
    	ClassSynThread classSynThread1 = new ClassSynThread();
    	ClassSynThread classSynThread2 = new ClassSynThread();
		
    	classSynThread1.start();
    	classSynThread2.start();
		SleepTools.second(30);
	}
}

运行结果如下

我们启用了两个使用类锁的线程,可以看出,Thread-1先拿到类锁,先执行完

类锁,其实也就是锁一个对象,只不过是这个类是class对象,在虚拟机中只有一个

和对象锁只锁一个new出来的对象类似

另外分别起一个对象锁,一个类锁的两个线程,之间也是互相不干扰的。因为它们锁的对象不同

1.3 小结

使用类锁,多线程之间需要竞争

使用对象锁,锁同一个对象,多线程之间需要竞争

                     锁不同的对象,互不干扰

类锁和对象锁之间,也互不干扰

二、volatile关键字

最轻量的同步机制,适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性

可见性好理解,不论哪个线程,在取值的时候都是从主内存中取,确保获取的值都是最新的

但是它不能保证原子性

2.1 demo

public class TestVolatile {
	private static class MyThread implements Runnable {
		private volatile int a = 0;
	    @Override
	    public void run() {
	    	a = a + 1;
	    	System.out.println(Thread.currentThread().getName()+":======"+a);
	    	SleepTools.ms(10);
	    	a = a + 1;
	    	System.out.println(Thread.currentThread().getName()+":======"+a);
	    }
	}
	
    public static void main(String[] args) {
    	MyThread myThread = new MyThread();

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

运行结果如下

在保证原子性的情况下,结果应该为6才对

而a = a + 1不是原子操作,volatile又不能保证原子性,所以每次执行的结果都不一样

三、ThreadLocal

简单理解为一个Map,类型 Map<Thread,Integer>

对于一个共享变量,每一个线程都保存该变量的副本,从而保证多线程之间的安全性

3.1 demo

public class TestThreadLocal {
	
	//可以理解为 一个map,类型 Map<Thread,Integer>
	static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){
		@Override
		protected Integer initialValue() {
			return 1;
		}
	};
    
    public static class TestThread implements Runnable{
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            //获得变量的值
            Integer s = threadLaocl.get();
            s = s + 1;
            threadLaocl.set(s);
            System.out.println(Thread.currentThread().getName()+":"+threadLaocl.get());
        }
    }

    public static void main(String[] args){
    	Thread thread1 = new Thread(new TestThread());
    	Thread thread2 = new Thread(new TestThread());
    	Thread thread3 = new Thread(new TestThread());
    	thread1.start();
    	thread2.start();
    	thread3.start();
    }
}

运行结果如下

threadLaocl通过initialValue()把共享变量的初始值设置为1

每一个线程通过threadLaocl.get();获取共享变量的值

然后+1后通过 threadLaocl.set(s);设置回去

再通过threadLaocl.get(),获取线程自己的副本,可以看到每个线程结果都为2,而不是加三次1得4

---------------------------------------------------------------------------------------------------------------------------------------------------

如果我的文章对您有点帮助,麻烦点个赞,您的鼓励将是我继续写作的动力

如果哪里有不正确的地方,欢迎指正

如果哪里表述不清,欢迎留言讨论

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值