如何解决线程安全问题呢?
前面我们已经说过了解决思路:对于多条共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
为了解决这个问题,Java的多线程支持引入同步监视器来解决这个问题。使用同步监视器的方式有两种:同步代码块以及同步方法。
1.同步代码块
同步代码块的语法格式如下:
sychronized(同步监视器对象){
//不能在括号中直接new对象,new了就没效果了
需要被同步的代码块
}
同步代码块注意事项 |
---|
这个同步代码块保证数据安全性的一个主要因素就是这个同步监视对象。注意这个对象,要定义为静态成员变量,才能被所有的线程所共享。需要将这个对象被所有的线程对象所共享。这个对象其实就是一把锁,我们习惯称这个对象叫做监视器。 |
在Java编程中,经常需要使用到同步,而用得最多的也许是synchronized关键字,因为synchronized关键字涉及到锁的概念,所以需要给大家说一些相关的锁知识。
- Java的内置锁:每个Java对象都可以用做一个实现同步的锁,这些锁称为内置锁。线程进入同步代码块或者方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或者方法。
- Java内置锁是一个互斥锁,这就是意味着最多一个线程能够获得该锁。当线程A尝试着去获得线程B持有的内置锁时,线程A必须处于等待或者阻塞,直到B释放了该锁,A才能获得该锁。如果B线程不释放这个锁,那么A线程将永远等待下去。
上面代码的含义,线程开始执行同步代码块之前,必须先获得同步监视器的锁定,换句话说就是没有获得同步监视器的锁定,就不能进入同步代码块的执行,线程就会处于阻塞状态,直到对方释放了对同步监视器的锁定。
- 任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然会释放对同步监视器对象的锁定。
Java程序运行使用任何对象来作为同步监视器对象,只要保证共享资源的这几个线程,锁的是同一个同步监视器对象即可。
public class CellRunnable implements Runnable {
//锁对象,锁对象为静态的,表示为多线程共享锁对象
static Object obj=new Object();
//设置票为共享数据
static int piao=100;
@Override
public void run(){
while (true) {
/*出现线程线程安全问题该如何解决:
我们可以使用同步代码块操作:把有可能出现线程安全问题的代码包裹起来
synchronized (同步监视器的对象){
需要同步的代码
}
同步代码块的锁对象可以为任意对象,但是多线程共享资源的锁对象只能是同一个
*/
synchronized (obj) {
if (piao > 0) {
try {
//模拟网络延迟
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + (piao--) + "张票");
}
}
}
}
}
public class MyTest {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
2.同步方法
与同步代码块对应的,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法为同步方法。
同步方法解决多线程安全问题 |
---|
对于同步方法而言,无需显示指定同步监视器。静态方法的同步监视器对象是当前类的Class对象,非静态方法的同步监视器对象是调用当前方法的this对象 |
- 非静态方法作为同步方法:
public class CellRunnable implements Runnable {
//定义共享资源
static int piao=100;
int i=0;
@Override
public void run() {
while(true){
synchronized (this){
if(i%2==0){
if(piao>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+(piao--)+"张票");
}
}
}
else{
sellTicket();
}
i++;
}
}
//在这里需要注意的是同步代码块需要的锁对象可以是任意的,但共享资源的多线程的锁对象必须是同一个
//同步方法就是在普通方法前面加上 synchronized关键字
//对于同步方法而言,无需显示指定锁对象。非静态方法的同步锁对象是调用当前方法的this对象。
public synchronized void sellTicket() {
if(piao>0){
//模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到第"+(piao--)+"张票");
}
}
}
public class MyTest {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
- 静态方法作为同步方法:
public class CellRunnable implements Runnable {
//定义静态常量即为共享变量
static int piao=100;
int i=0;
@Override
public void run() {
while(true){
//静态同步方法的锁对象是该类的字节码文件对象
synchronized (CellRunnable.class){
if(i%2==0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(piao>0)
System.out.println(Thread.currentThread().getName()+"抢到了第"+(piao--)+"张票");
}
else{
cellTicket();
}
i++;
}
}
}
//同步代码块的锁对象可以是Java中任意对象,但共享资源的多线程的锁对象必须是同一个
//给方法加上synchronized关键字,该方法为同步方法,同步方法的锁对象是this
//静态同步方法的锁对象是该类字节码文件对象
public synchronized static void cellTicket() {
if (piao > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + (piao--) + "张票");
}
}
}
3.Lock锁应用
- Lock锁的概述:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上锁,在哪里释放锁。为了更加清晰的表达如何加锁和释放锁,JDK1.5之后提供了一个新的锁对象Lock。
- Lock是一个接口,已知子类为ReentrantLock。一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
import java.util.concurrent.locks.ReentrantLock;
public class CellRunnable implements Runnable{
//定义静态成员变量,即为共享变量
static int piao=100;
//定义Lock锁对象
public static final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){
//加锁
lock.lock();
if(piao>0){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"抢到了第"+(piao--)+"张票");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
//释放锁
lock.unlock();
}
}
}
}
}
public class MyTest {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable,"窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
总结
简单的来说解决Java中多线程安全问题的三种方式为:同步代码块,同步方法以及Lock锁的应用。小弟去搬砖了,再见了。