1:为什么需要同步
多个线程同时访问一个对象中的实例变量进行并发访问的时候会产生“非线程安全”的情况,产生的后果就是“脏读”,也就是取到的数据其实是被更改的;而“线程安全”就是以获得的实例变量的值进行同步处理的,不会出现脏读现象。
2:synchronized 同步方法
脏读问题存在于“实例变量”中,如果是方法内部私有变量则不会出现脏读情况。
2.1:方法内部私有变量为线程安全
public class t19 {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
class HasSelfPrivateNum {
public void addI(String username) {
try {
int num = 0;
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();
}
}
}
class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef){
this.numRef = numRef;
}
public void run(){
numRef.addI("a");
}
}
class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef){
this.numRef = numRef;
}
public void run(){
numRef.addI("b");
}
}
由上可见,方法变量中不存在费线程安全问题,永远都是线程安全的。这是方法内部私有变量造成的!!!
2.2:实例变量非线程安全
如果多个变量访问同一个对象中的实例变量,则有可能出现费线程安全的问题。
public class t20 {
public static void main(String[] args) {
HasSelfPrivateNum1 numRef = new HasSelfPrivateNum1();
ThreadA1 athread = new ThreadA1(numRef);
athread.start();
ThreadB1 bthread = new ThreadB1(numRef);
bthread.start();
}
}
class HasSelfPrivateNum1 {
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();
}
}
}
class ThreadA1 extends Thread {
private HasSelfPrivateNum1 numRef;
public ThreadA1(HasSelfPrivateNum1 numRef){
this.numRef = numRef;
}
public void run(){
numRef.addI("a");
}
}
class ThreadB1 extends Thread {
private HasSelfPrivateNum1 numRef;
public ThreadB1(HasSelfPrivateNum1 numRef){
this.numRef = numRef;
}
public void run(){
numRef.addI("b");
}
}
总结:在多个线程访问同一个对象中的同步方法时,一定是线程安全的;方法内部私有变量线程安全;类中实例变量线程不安全
2.3:多个对象多个锁
public class t21 {
public static void main(String[] args) {
HasSelfPrivateNum2 numRef1 = new HasSelfPrivateNum2();
HasSelfPrivateNum2 numRef2 = new HasSelfPrivateNum2();
ThreadA2 athread = new ThreadA2(numRef1);
athread.start();
ThreadB2 bthread = new ThreadB2(numRef2);
bthread.start();
}
}
class HasSelfPrivateNum2 {
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();
}
}
}
class ThreadA2 extends Thread {
private HasSelfPrivateNum2 numRef;
public ThreadA2(HasSelfPrivateNum2 numRef1){
this.numRef = numRef1;
}
public void run(){
numRef.addI("a");
}
}
class ThreadB2 extends Thread {
private HasSelfPrivateNum2 numRef;
public ThreadB2(HasSelfPrivateNum2 numRef2){
this.numRef = numRef2;
}
public void run(){
numRef.addI("b");
}
}
上面示例中是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果缺失以异步的方式运行的。这是因为示例中创建了两个业务对象,在业务中产生了两个锁,所以会产生异步的效果。
虽然示例中使用了 synchronized 关键字,但是打印的顺序去不是同步的,视交叉的,为什么是这样呢?
关键字 synchronized 取得的锁都是对象锁,而不是把一段代码或方法当做锁。所以在上面的示例中,哪个线程先执行带 synchronized 关键字的方法,哪个线程就持有该方法所属对象的 lock 锁,那么其他线程只能能呈等待状态,前提是多个线程访问同一个对象。
多个线程访问多个对象,则 JVM 会创建多个锁。所以上面示例中产生了两个锁,所以产生了异步。