一、介绍
当我们写并发编程时,多个线程可同时访问一个共享资源,比如变量或对象,如果多个线程同时读写该资源,会导致该资源状态混乱,数据不准确,相互之间产生冲突。
因此加入同步锁,使资源同一时间只能有一个线程访问,从而保证资源不发生冲突。
二、线程同步具体实现
1、Synchronized
使用Synchronized关键字,分为同步方法和同步代码块,具体关于Synchronized的介绍和使用请移步《Synchronized简单介绍(原理、使用、对比分析)》
2、volatile
volatile的中文意思是不稳定的、反复无常的、异变的。
a、volatile是轻量级同步机制,在访问volatile修饰的变量时,不会执行加锁操作,所以也就不能使线程阻塞,是一种比synchronized关键字更轻量级的同步机制。
b、volatile只能保证可见性,不能保证原子性;加锁机制即可以保证可见性又可以保证原子性,所以votatile并不能替代synchronized。
volatile的中文意思是不稳定的,反复无常的,异变的。当使用volatile声明变量后,系统总是重新从它所在的内存中读取数据,而非从缓存中读取,从而保证了数据在内存中的可见性,这就保证了同步。(通过这里也可以看出,volatile修饰的变量都是可变的,不能修饰final类型的变量)
c、volatile会禁止指令重排,屏蔽代码优化,进而造成效率降低,所以只有在必要时才可用该关键字。
3、使用原子变量
原子变量保证了原子操作(所谓原子操作,就是数据的读取、修改、保存作为一个整体行为,这个整体行为要么都执行,要么都不执行,是不可被分割、不可中断的,不能仅仅执行其中的一部分动作)
需要线程同步的根本原因就是因为普通变量的操作不是原子的,而原子变量保证了原子性,所以能保证同步。
在Java的util.concurrent.atomic包中提供了创建原子类型变量的工具类,使用这些原子类可以简化线程同步。
常用的原子变量类型有:
基本类型对应:AtomicBoolean、AtomicInteger、AtomicLong
数组类型对应:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
引用类型对应:AtomicReference
4、使用ThreadLocal类
ThreadLocal通过字面直接翻译,就是线程本地 ,也就是线程本地变量、线程局部变量。
ThreadLocal的作用域仅仅是本线程内,即ThreadLocal为每个线程提供了一个特定的变量,以保存该线程所独享的数据,所以其提供了一种隔离线程,防止线程间数据共享的方法。
详细介绍请移步《ThreadLocal解析》。
5、使用阻塞队列
阻塞队列(BlockingQueue)和普通队列的区别就是阻塞,所谓阻塞,即当队列是空时,从队列中取数据(take())的操作会被阻塞,或当队列已经满时,向队列存数据(put(E, e))的操作将会被阻塞。当存取数据队列被阻塞时,即表示当前线程阻塞。
阻塞队列的常见实现类如ArrayBlockingQueue,LinkedBlockingQueeu,PriorityBlockingQueue等。
阻塞队列的使用场景典型的是生产者消费者模式,生产者线程向阻塞队列中存放数据,消费者线程从该阻塞队列中取出数据。
public class MainActivity extends AppCompatActivity {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
Button mBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn = (Button) findViewById(R.id.mBtn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Producer(queue).start();
new Consumer(queue).start();
}
});
}
//生产者
class Producer extends Thread {
ArrayBlockingQueue<String> mQueue;
public Producer(ArrayBlockingQueue<String> queue) {
mQueue = queue;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(50);
mQueue.put("Production" + i);
System.out.println("Producer:Production" + i );
} catch (Exception e) {
e.getMessage();
}
}
}
}
//消费者
class Consumer extends Thread {
ArrayBlockingQueue<String> mQueue;
public Consumer(ArrayBlockingQueue<String> queue) {
mQueue = queue;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(50);
String str = mQueue.take();
System.out.println("Consumer: " + str );
} catch (Exception e) {
e.getMessage();
}
}
}
}
}
执行结果如下:
6、使用Lock
主要就是使用重入锁ReentrantLock类 ,其实现了Lock接口。
常用的方法:
创建ReentrantLock()实例:ReentrantLock lock = new ReentrantLock();
获得锁:lock.lock();
释放锁:lock.unlock();
代码 :
//创建实例
ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
//doSomething
}finally{
lock.unlock();
}
通过代码可以看到,使用ReentrantLock,必须自己实现获得/释放锁,从而带来了风险,显然不如直接使用synchronized.