线程的同步(解决多线程安全问题)
Posted 巧克力爱王子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程的同步(解决多线程安全问题)相关的知识,希望对你有一定的参考价值。
多线程出现安全问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。
解决方法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
参与执行。
Java对多线程的安全问题提供了专门的解决方法:同步机制。
方法一:同步代码块
synchronized{
//需要被同步的代码
}
说明:
1、操作共享数据的代码,即为需要被同步的代码(注意不要包含多了也不要包含少了);
2、共享数据:多个线程共同操作的变量;
3、同步监视器:俗称锁,任何一个类的对象都可以充当锁(要求各线程必须共用一把锁);
4、在实现Runnab接口创建多线程的方式中,可以考虑使用this作为同步监视器,在继承Thread类
创建多线程的方式中,可以考虑 类.class作为同步监视器。
下面是一个简单的多窗口卖票问题
class Window implements Runnable{//实现Runnable方法创建多线程
private static int ticket = 100;//票数
@Override
public void run() {//重写run方法
while (true){
synchronized (this) {//将操作共享数据的代码用synchronized包起来
if (ticket > 0) {
//当前线程的名字
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
此时,对于操作共享数据的语句,只有当一个线程执行完了,释放了锁,其他线程才能进入。
方法二:使用同步方法
当操作共享数据的代码完全完整的声明在一个方法中,可以将此方法设置为同步的。
注意:
1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明;
2、非静态的同步方法,同步监视器为 this
静态的同步方法,同步监视器为当前类本身(类.class)
此为非静态的同步方法
//实现Runnable接口创建多线程
class Window1 implements Runnable{
private static int ticket = 100;//票数
@Override
public void run() {//重写run方法
while (true){
show();//该方法为操作共享数据的方法
}
}
private synchronized void show(){//同步监视器为this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}
}
}
此为静态的同步方法
//继承Thread类来创建多线程
class Window2 extends Thread {
private static int ticket = 100;//票数
@Override
public void run() {
while (true){
show();//该方法为操作共享数据的方法
}
}
private static synchronized void show(){//同步监视器为Window2.class
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}
}
}
方式三:lock(锁)
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//
保证线程安全的代码
;
}
finally{
lock.unlock();
}
}
}
说明:
1、从
JDK 5.0
开始,
Java
提供了更强大的线程同步机制
——
通过显式定义同步锁对象来实现同
步。同步锁使用Lock
对象充当;
2、
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的
工具。锁提供了对共
享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得
Lock对象;
3、ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线
程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class Windowlock implements Runnable{
private int trick = 100;
// 实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//调用锁定方法
lock.lock();
if(trick > 0){
System.out.println(Thread.currentThread().getName() + ":" + trick);
trick--;
}else{
break;
}
} finally {
//解锁
lock.unlock();
}
}
}
}
synchronized 与 lock的对比:
1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),
synchronized
是隐式锁,出了作用域自动
释放;
2. Lock只有代码块锁,
synchronized
有代码块锁和方法锁;
3. 使用Lock
锁,
JVM
将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更
多的子类)
优先使用顺序:
lock——>同步代码块——>同步方法
以上是关于线程的同步(解决多线程安全问题)的主要内容,如果未能解决你的问题,请参考以下文章
阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第3节 线程同步机制_4_解决线程安全问题_同步代码块