非线程安全是多个线程对同一个对象中的实例变量进行并发访问时发生的,取到的数据其实是被更改过的。而线程安全就是以获得的实例变量的值是经过同步处理的
1.synchronized同步方法
只有共享资源的读写访问需要同步化
1.出现异常,锁会自动释放
2.同步不具有继承性
(1) 方法内的变量线程安全
每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。
理解:
局部变量(方法内部的私有变量)是线程安全的,程序在new Object() 时会为类中的属性成员变量开辟空间,在方法区开辟一个内存空间并且只存一份是共用的代码段(引用在栈区,变量在堆区);而方法中的私有变量只有在调用时在对应调用线程中开辟出内存空间,有几个线程调用则每个线程都会在自己的线程空间的栈中为局部变量申请几个引用,同时在堆中为变量申请对应的空间 (即多线程在调用时只会处理自己线程内的方法的私有变量)
(2) 实例变量非线程安全
多个线程共同访问同个对象中的实例变量,则有可能出现非线程安全问题。
(3) 多个对象多个锁
关键字 synchronized取得的锁都是对象锁,哪个线程先执行带 synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,如果多个线程访问多个对象,则JVM会创建多个锁,运行结果是异步的。
如果在静态方法上加synchronized关键字,表示锁定类级别的锁,独占class类,这时候多个线程访问的是相同的锁。
当A线程调用Object对象synchronized关键字修饰的X方法时,A线程就获得了X方法所在对象的锁,其他线程必须等A线程执行完毕才可以调用Ⅹ方法,而B线程如果调用Object对象synchronized关键字修饰的非ⅹ方法时 (可以调用Object对象的非synchronized修饰的方法),必须等A线程将ⅹ方法执行完,也就是释放对象锁后才可以调用。
(4) synchronized锁重入
可重入锁是:自己可以再次获取自己的内部锁。使用 synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到。即synchronized方法/块的内部调用本类的其他 synchronized方法/块时,是可以得到锁的。
public class Run {
public static void main(String[] args) {
ThreadIV thread = new ThreadIV();
thread.start();
}
}
public class ThreadIV extends Thread {
@Override
public void run() {
Service service = new Service();
service.serviceA();
}
}
public class Service {
public synchronized void serviceA() {
System.out.println("serviceA");
serviceB();
}
public synchronized void serviceB() {
System.out.println("serviceB");
}
}
线程A获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(支持父子类继承),如果不可锁重入的话,就会造成死锁。
2.synchronized同步语句块
(1) 同步方法和同步代码块比较
同步方法弊端:当业务逻辑复杂时,同步方法影响运行性能
当一个线程访问Object中synchronized修饰的方法或synchronized(this) 同步代码块时,其他线程对同一个 Object中synchronized修饰的不同方法或其他 synchronized (this))同步代码块的访问将被阻塞。synchronized(this)代码块是锁定的当前对象
注意:
同步代码块(synchronized(this))放在非同步方法(无synchronized修饰)中进行声明,并不能保证线程调用的方法体同步执行(同步代码块中执行是同步的)
(2) 静态同步方法和synchronized(Class)比较
synchronized关键字加到static静态方法上是给Class类上锁,而 synchronized关键字加到非static静态方法上是对象上锁,Class锁可以对类的所有对象实例起作用。
同步 synchronized(Class)代码块的作用和synchronized static方法的作用一样。
3.多线程死锁
不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续进行;在设计程序时要避免双方互相持有对方锁的情况,只要互相等待对方释放锁就可能出现死锁。
例如:
① synchronize嵌套的代码结构来形成死锁
② synchronize方法中出现无限循环
public synchronized void threadRun() {
while (true) {
}
}
4.volatile关键字
(1) 解决死循环问题
public class ThreadV implements Runnable {
private boolean isRunning = true;
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("开始调用多线程!");
while (isRunning = true) {
System.out.println("thread is running");
}
System.out.println("多线程调用结束!");
}
}
public class RunMain {
public static void main(String[] args) throws InterruptedException {
ThreadV threadV = new ThreadV();
new Thread(threadV).start();
Thread.sleep(100);
threadV.setRunning(false);
System.out.println("已经赋值false!");
}
}
运行结果:程序进入死循环(循环输出 “thread is running”)
原因:
在启动线程时,变量 private boolean isRunning=true存在于公共堆栈及线程的私有堆栈中。为了运行的效率,线程只在私有堆栈中取得 isRunning的值是true。而代码 threadV.setRunning(false)被执行时,更新的却是公共堆栈中的 isRunning变量值 false,所以一直就是死循环的状态。
解决:
通过使用关键字 volatile强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
(2) volatile和synchronized比较
① volatile是线程同步的轻量级实现,只能修饰于变量;而 synchronized可以修饰方法和代码块。
② 多线程访问 volatile不会发生阻塞,而 synchronized会出现阻塞。
③ volatile能保证数据的可见性,但不能保证原子性;而 synchronized可以保证原子性,也可以间接保证可见性(它会将私有内存和公共内存中的数据做同步)
④ volatile解决的是变量在多个线程之间的可见性;而 synchronized关键字解决的是多个线程之间访问资源的同步性。