synchronized同步方法
方法内的变量为线程安全
“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题”,所得的结果也就是“线程安全”的了。
实例变量非线程安全
如果两个线程同时操作业务对象中的实例变量,则有可能出现“非线程安全”问题。
HasSellPrivateNum
public class HasSellPrivateNum {
private int num = 0;
public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + "num+=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ThreadA
public class ThreadA extends Thread {
private HasSellPrivateNum rumRed;
public ThreadA(HasSellPrivateNum rumRed) {
this.rumRed = rumRed;
}
@Override
public void run() {
super.run();
rumRed.addI("a");
}
}
ThreadB
public class ThreadB extends Thread {
private HasSellPrivateNum rumRed;
public ThreadB(HasSellPrivateNum rumRed) {
this.rumRed = rumRed;
}
@Override
public void run() {
super.run();
rumRed.addI("b");
}
}
Run
public class Run {
public static void main(String[] args) {
HasSellPrivateNum numRed = new HasSellPrivateNum();
ThreadA threadA = new ThreadA(numRed);
threadA.start();
ThreadB threadB = new ThreadB(numRed);
threadB.start();
}
}
运行结果
修改代码在方法前添加synchronized 关键字使结果同步
public class HasSellPrivateNum {
private int num = 0;
synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + "num+=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
两个线程访问同一个对象中的同步方法时一定是线程安全的。
多个对象多个锁
Run
public class Run {
public static void main(String[] args) {
HasSellPrivateNum numRed1 = new HasSellPrivateNum();
HasSellPrivateNum numRed2 = new HasSellPrivateNum();
ThreadA threadA = new ThreadA(numRed1);
threadA.start();
ThreadB threadB = new ThreadB(numRed2);
threadB.start();
}
}
运行结果
从结果看,虽然使用了synchronized关键字,但是打印顺序确实不同步的,是交叉的。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能程等待状态,前提是多个线程访问的是同一个对象。
如果多个线程访问多个对象,则JVM会创建多个锁。
调用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的需要。
A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
A线程先持有object对象的Lock锁,B线程如果这时调用object对象中的synchronized类型的方法则需等待,也就是同步。
synchronize 锁重入
代码示例
创建Service类
public class Service {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
}
线程类MyThread
public class MyThread extends Thread{
@Override
public void run() {
super.run();
Service service = new Service();
service.service1();
}
}
运行类Run.java
public class Run {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
运行结果
关键字synchronize拥有锁重入的功能,也就在使用synchronized时,当一个线程得到一个对象锁后,再次请求对象锁时是可以再次得到该对象的锁的。这也就证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
“可重入锁”的概念是:自己可以再次获取自己的内部所。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的。如果不重入锁的话,就会造成死锁。
出现异常锁自动释放
同步不具有继承性
synchronized同步语句
synchronized方法是对当前对象进行加锁,而synchronized 代码块是对某一个对象进行加锁。
使用synchronized(非this对象X)同步代码块,格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行结果就是一部调用了,就会交叉运行。
“synchronized(非this对象x)”格式的写法是将x对象本身作为“对象监视器”,这样可以得出以下三个结论:
1)当多个线程同时执行synchronized(x){}同步代码时程同步效果。
2)当其他线程执行X对象中的synchronized同步方法时呈同步效果。
3)当其他线程执行X对象方法里面的synchronized(this)代码块时也呈现同步效果。
synchronized关键字加到static静态方法上是给class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。
数据类型String的常量池特性
两个线程持有相同的锁,所以造成锁不释放的问题。
volatile关键字
关键字volatile的主要作用是使变量在多个线程间可见。
测试项目
PrintString类
public class PrintString implements Runnable {
private boolean isContinuePrint = true;
public boolean isContinuePrint() {
return isContinuePrint;
}
public void setContinuePrint(boolean continuePrint) {
isContinuePrint = continuePrint;
}
public void printStringMethod() {
try {
while (isContinuePrint == true) {
System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
printStringMethod();
}
}
运行类Run
public class Run {
public static void main(String[] args) {
try {
PrintString printString = new PrintString();
new Thread(printString).start();
System.out.println("我要停止它!stopThread="+Thread.currentThread().getName());
printString.setContinuePrint(false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
使程序运行在服务器模式下,其效果是一直处于死循环状态,
停不下来的原因就是main线程一直在处理while()循环,导致程序不能继续执行后面的代码。
当JVM被设置成-server模式下时,
变量 isContinuePrint = true ,存在于公共堆栈及线程的私有栈中。在JVM设置为-server模式时为了线程运行的效率,线程一直在私有栈中取得isContinuePrint 的值是true。而代码printString.setContinuePrint(false);虽然被执行,更新的却是公共堆栈中的isContinuePrint 变量值false,所以一直就是死循环状态。
这个问题其实就是私有堆栈中的值和公共堆栈的值不同步造成的。解决这样的问题就要使用volatile关键字了,它主要的作用就是当线程访问isContinuePrint 这个变量时,强制性从公共堆栈中进行取值。
使用volatile关键字增加了实例变量在多个线程之间的可见性。
synchronized和volatile比较
- 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大的。
- 多线程访问volatile不会发生阻塞,而synchronized会出现堵塞。
- volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为他会将私有内存和公共内存中的数据做同步。
- 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。
synchronized代码块有volatile同步的功能
同步synchronize不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修饰效果。