一。什么是非线程安全:
1. 非线程安全:多个线程对同一个对象中的实例变量进行了并发访问,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。
2. 线程安全:获得实例变量的值是经过同步处理的,不会出现脏读的现象。(如按顺序读取)
3. “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题”。
4. 实例变量的非线程安全:
1.如果对象中有多个实例变量,运行的结果可能出现交叉的情况,如仅有一个实例变量,则可能出现覆盖的情况。
二。线程占用的锁是对象锁。
1.两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,jvm会加两个锁,互不影响。
synchronized 取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,线程会获得实例对象中同步方法所属的实例对象的锁,所以上面互不影响。
结论:
1. A线程通过调用methodA先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法methodC。
2. A线程通过调用methodA先持有object对象的Lock锁,B线程如果在这时调用object对象中的所有synchronized类型的方法都需要等待锁(methodA或methodB)。
三。脏读
1.线程在读取实例变量时,此值已经被其他线程更改过了。
场景:线程A执行对象的某个同步方法设置值时,如果过程比较慢只设置了一部分的变量的值,此时调用此对象的另一个未同步的取值方法时取到的值
是只设置了一半值。
四。synchronized锁重入:
1。当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到改对象的锁的,也就是在一个synchronized方法内部调用此对象的其他
synchronized 方法时是永远可以得到锁的(不能就坏事了,到处是死锁)
2. 当存在父子继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
五。锁的其他知识汇总
1.出现异常,锁自动释放,当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
2.同步不具有继承性。
1.如果父类的某一方法是同步的,但是其子类重写的同名方法并不直接具备同步性,必须给子类方法也加上synchronized才是同步方法。
3.使用synchronized同步语句块,减少同步范围,尽量减少其他线程的等待时间。
4.当线程访问object的一个synchronized(this)同步代码块时,其他线程对同一object中其他的synchronized(this)同步代码块的访问将被阻塞。
5.synchronized(this)代码块也是锁定当前对象的。
6.synchronized(非this对象),如使用实例对象中的一个字符串变量作为同步对象也是可以的。
7.synchronized(非this对象) 与 synchronized(this)/synchronized 之间的锁互不影响,他们之间不会阻塞。
如下示例:
如果Service里的判断条件之前不加synchronized (list),则当MyThread1和MyThread2会同时进入判断条件且满足,然后顺序执行list的同步add方法,导致size=2
注意:此处必须用list作为同步锁对象,因为两个线程的Service是不同的,所以用synchronized (this)并不能阻塞另一个线程,只有list是两个线程公用的,
所以它上面的锁会阻塞另一个线程获取此锁,必须等待前一个处理完毕才能去判断是否满足if条件
8.synchronized(非this对象X)与X对象上的synchronized方法和synchronized(this)方法是同步阻塞的,也就是线 程A通过前面三种任意一种获取了此对象
X的锁后未释放前,X上的synchronized方法和synchronized(this)对其他线程也是阻塞的。
9.synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是 给对象上锁。
Class锁可以对类的所有对象实例起作用(注意是对所有实例再次调用synchronized static方法时,而不是不带static的对象锁)。
synchronized(class)代码块的作用和synchronized static方法的作用一样。
如下列:
11.如果要为一个Service中两个方法分别控制锁,可以对两个方法内用synchronized同步代码块控制,分别用不同 的对象锁,这样才能相互不影响,不要直接在方法上synchronized,这样会获得对象锁。
实例1:B永远没有机会运行
12.死锁的产生和检测
jps:找出run程序的pid
jstack -l pid:显示线程堆栈信息,从中可以看到两个线程各自locked了对方waiting to lock的锁,从而导致死锁。
13.如果以对象作为锁,当对象的属性发生变化时,锁是不变的。
字符串对象是个特殊,当他的值发生改变后相当于新建了一个对象,所以锁会变。
五。volatile关键字
1。作用:使变量在多个线程间可见,相当于直接在主内存而不是线程内存中操作带volatile关键字的变量。
2. synchronized和volatile的比较:
1.volatile只能修饰于变量,而synchronized可以修饰方法、以及代码块。
2.volatile不会阻塞,而synchronized会出现阻塞。
3.volatile能保证数据的可见性,但不能保证原子性,而synchronized可以保证原子性。
4.volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
3.使用场景:在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用。
4.原子类型:时一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全(如AtomicInteger)
5.JVM设置为-server时就出现死循环的问题:
原因:非-server环境时,线程的变量存在于公共堆栈及线程的私有堆栈中,线程在私有和公共之间交互数据(read,load,assign,write等)。
-server环境时,为了线程的运行效率,线程一直在其私有堆栈中取得线程的变量值,从而导致其他线程更新此变量的值不可见(更新到公共堆栈中)。
read:从主内存读取
load:加载到工作内存
use:使用
assign:赋值
store:存储到工作内存
write:写入主内存
1. 非线程安全:多个线程对同一个对象中的实例变量进行了并发访问,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。
2. 线程安全:获得实例变量的值是经过同步处理的,不会出现脏读的现象。(如按顺序读取)
3. “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题”。
4. 实例变量的非线程安全:
1.如果对象中有多个实例变量,运行的结果可能出现交叉的情况,如仅有一个实例变量,则可能出现覆盖的情况。
解决方法一: 使用synchronized同步方法,多线程访问同一个对象中的同步方法时一定是线程安全的。(同步:可理解为线程里更改的值同步到主内存中,
使其他线程能够看到)
二。线程占用的锁是对象锁。
1.两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,jvm会加两个锁,互不影响。
synchronized 取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,线程会获得实例对象中同步方法所属的实例对象的锁,所以上面互不影响。
结论:
1. A线程通过调用methodA先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法methodC。
2. A线程通过调用methodA先持有object对象的Lock锁,B线程如果在这时调用object对象中的所有synchronized类型的方法都需要等待锁(methodA或methodB)。
public class MyObject {
synchronized public void methodA() {
...
}
synchronized public void methodB() {
...
}
public void methodC() {
...
}
}
三。脏读
1.线程在读取实例变量时,此值已经被其他线程更改过了。
场景:线程A执行对象的某个同步方法设置值时,如果过程比较慢只设置了一部分的变量的值,此时调用此对象的另一个未同步的取值方法时取到的值
是只设置了一半值。
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void getValue() {
System.out.println("getValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
}
}
public class Test {
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadA thread = new ThreadA(publicVarRef);
thread.start();
Thread.sleep(200);// 打印结果受此值大小影响
publicVarRef.getValue();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
四。synchronized锁重入:
1。当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到改对象的锁的,也就是在一个synchronized方法内部调用此对象的其他
synchronized 方法时是永远可以得到锁的(不能就坏事了,到处是死锁)
2. 当存在父子继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
public class Sub extends Main {
synchronized public void operateISubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
this.operateIMainMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public int i = 10;
synchronized public void operateIMainMethod() {
try {
i--;
System.out.println("main print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
Sub sub = new Sub();
sub.operateISubMethod();
}
}
public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
五。锁的其他知识汇总
1.出现异常,锁自动释放,当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
2.同步不具有继承性。
1.如果父类的某一方法是同步的,但是其子类重写的同名方法并不直接具备同步性,必须给子类方法也加上synchronized才是同步方法。
3.使用synchronized同步语句块,减少同步范围,尽量减少其他线程的等待时间。
4.当线程访问object的一个synchronized(this)同步代码块时,其他线程对同一object中其他的synchronized(this)同步代码块的访问将被阻塞。
5.synchronized(this)代码块也是锁定当前对象的。
6.synchronized(非this对象),如使用实例对象中的一个字符串变量作为同步对象也是可以的。
7.synchronized(非this对象) 与 synchronized(this)/synchronized 之间的锁互不影响,他们之间不会阻塞。
如下示例:
如果Service里的判断条件之前不加synchronized (list),则当MyThread1和MyThread2会同时进入判断条件且满足,然后顺序执行list的同步add方法,导致size=2
注意:此处必须用list作为同步锁对象,因为两个线程的Service是不同的,所以用synchronized (this)并不能阻塞另一个线程,只有list是两个线程公用的,
所以它上面的锁会阻塞另一个线程获取此锁,必须等待前一个处理完毕才能去判断是否满足if条件
public class Service {
public MyOneList addServiceMethod(MyOneList list, String data) {
try {
synchronized (list) {
if (list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
public class MyOneList {
private List list = new ArrayList();
synchronized public void add(String data) {
list.add(data);
};
synchronized public int getSize() {
return list.size();
};
}
public class MyThread1 extends Thread {
private MyOneList list;
public MyThread1(MyOneList list) {
super();
this.list = list;
}
@Override
public void run() {
Service msRef = new Service();
msRef.addServiceMethod(list, "A");
}
}
public class MyThread2 extends Thread {
private MyOneList list;
public MyThread2(MyOneList list) {
super();
this.list = list;
}
@Override
public void run() {
Service msRef = new Service();
msRef.addServiceMethod(list, "B");
}
}
8.synchronized(非this对象X)与X对象上的synchronized方法和synchronized(this)方法是同步阻塞的,也就是线 程A通过前面三种任意一种获取了此对象
X的锁后未释放前,X上的synchronized方法和synchronized(this)对其他线程也是阻塞的。
9.synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是 给对象上锁。
Class锁可以对类的所有对象实例起作用(注意是对所有实例再次调用synchronized static方法时,而不是不带static的对象锁)。
synchronized(class)代码块的作用和synchronized static方法的作用一样。
如下列:
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printA();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printB();
}
}
public class ThreadC extends Thread {
private Service service;
public ThreadC(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printC();
}
}
public class Service {
synchronized public static void printA() {
try {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void printB() {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开printB");
}
synchronized public void printC() {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入printC");
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开printC");
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
ThreadC c = new ThreadC(service);
c.setName("C");
c.start();
}
}
// 测试结果:
// A先启动占用Service的class锁,B线程也申请Class锁被阻塞,C线程申请的是Service的对象锁而不是Class锁所以能进入。
public class Run {
public static void main(String[] args) {
Service service1 = new Service();
Service service2 = new Service();
ThreadA a = new ThreadA(service1);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service2);
b.setName("B");
b.start();
}
}
// 测试结果:A先启动占用Service的class锁,B线程也申请Class锁,但是Class锁是对所有对象实例起作用的,所以B被阻塞等待A线程执行完毕再执行。
10.synchronized代码块都不要用String作为锁对象
比如new Object()实例化一个Object对象,因为jvm对String常量有缓存,两个值相同的不同String对象实际上是一个对象。
11.如果要为一个Service中两个方法分别控制锁,可以对两个方法内用synchronized同步代码块控制,分别用不同 的对象锁,这样才能相互不影响,不要直接在方法上synchronized,这样会获得对象锁。
实例1:B永远没有机会运行
public class Service {
synchronized public void methodA() {
System.out.println("methodA begin");
boolean isContinueRun = true;
while (isContinueRun) {
}
System.out.println("methodA end");
}
synchronized public void methodB() {
System.out.println("methodB begin");
System.out.println("methodB end");
}
}
// 实例2:methodA和methodB分别上锁:
public class Service {
Object object1 = new Object();
public void methodA() {
synchronized (object1) {
System.out.println("methodA begin");
boolean isContinueRun = true;
while (isContinueRun) {
}
System.out.println("methodA end");
}
}
Object object2 = new Object();
public void methodB() {
synchronized (object2) {
System.out.println("methodB begin");
System.out.println("methodB end");
}
}
}
12.死锁的产生和检测
public class DealThread implements Runnable {
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setFlag(String username) {
this.username = username;
}
@Override
public void run() {
if (username.equals("a")) {
synchronized (lock1) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("按lock1->lock2代码顺序执行了");
}
}
}
if (username.equals("b")) {
synchronized (lock2) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1代码顺序执行了");
}
}
}
}
}
public class Run {
public static void main(String[] args) {
try {
DealThread t1 = new DealThread();
t1.setFlag("a");
Thread thread1 = new Thread(t1);
thread1.start();
Thread.sleep(100);
t1.setFlag("b");
Thread thread2 = new Thread(t1);
thread2.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
查看死锁:
jps:找出run程序的pid
jstack -l pid:显示线程堆栈信息,从中可以看到两个线程各自locked了对方waiting to lock的锁,从而导致死锁。
13.如果以对象作为锁,当对象的属性发生变化时,锁是不变的。
字符串对象是个特殊,当他的值发生改变后相当于新建了一个对象,所以锁会变。
五。volatile关键字
1。作用:使变量在多个线程间可见,相当于直接在主内存而不是线程内存中操作带volatile关键字的变量。
2. synchronized和volatile的比较:
1.volatile只能修饰于变量,而synchronized可以修饰方法、以及代码块。
2.volatile不会阻塞,而synchronized会出现阻塞。
3.volatile能保证数据的可见性,但不能保证原子性,而synchronized可以保证原子性。
4.volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
3.使用场景:在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用。
4.原子类型:时一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全(如AtomicInteger)
注意:但是多个原子类型组合操作时,虽然能保证最终结果正确,但是顺序有可能被打乱,解决它的方法也很简单,
把多个原子操作变成一个原子操作即给方法或多个原子操作的
代码块加上synchronized关键字即可。 public class MyService {
public static AtomicLong aiRef = new AtomicLong();
public void addNum() {
// 下面两个原子操作在多线程的情况下是不能保证一块执行的,解决它需要给addNum加上synchronized关键字
aiRef.addAndGet(100);
aiRef.addAndGet(1);
}
}
5.JVM设置为-server时就出现死循环的问题:
原因:非-server环境时,线程的变量存在于公共堆栈及线程的私有堆栈中,线程在私有和公共之间交互数据(read,load,assign,write等)。
-server环境时,为了线程的运行效率,线程一直在其私有堆栈中取得线程的变量值,从而导致其他线程更新此变量的值不可见(更新到公共堆栈中)。
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.runMethod();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.stopMethod();
}
}
public class Service {
private boolean isContinueRun = true;
public void runMethod() {
// String anyString = new String();
while (isContinueRun == true) {
// synchronized (anyString) {
// }
}
System.out.println("停下来了!");
}
public void stopMethod() {
isContinueRun = false;
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
try {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.start();
Thread.sleep(1000);
ThreadB b = new ThreadB(service);
b.start();
System.out.println("已经发起停止的命令了!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
6. synchronized 也具有同步数据的功能
如上例中Service中去掉注释,就具有了同步数据的功能,即使设置为-server模式,线程也会从主内存中
同步线程相关的变量数据,从而在下一次循环中得到被其他线程更改的isContinueRun的值true,线程正常停止。7. 线程工作内存与主内存的交互
其中在线程的工作内存中load,use,assign三个步骤是出现线程不安全的主要因素,考虑一个累加操作,当A线程执行到load是i的值
是5,此时切换线程,B,C线程同样读取到主内存中的i的值5,然后三个线程继续执行,A,B,C回写主内存值都为6,导致少加了2,解决方法也很简单,给相应增加i值得方 法 或代码块加上synchronized关键字,使A线程执行完一次从read-write过程后其他线程才能继续执行他们自己的过程。read:从主内存读取
load:加载到工作内存
use:使用
assign:赋值
store:存储到工作内存
write:写入主内存