目录
常见有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
---------------------------------------------------------------------------------------------------------------------------------------------------
如果我的文章对您有点帮助,麻烦点个赞,您的鼓励将是我继续写作的动力
如果哪里有不正确的地方,欢迎指正
如果哪里表述不清,欢迎留言讨论