文章介绍Java多线程中的同步,主要
1:synchronized 的使用。
2:非线程安全如何出现。
3:volatile的总用。
synchronized同步方法。
方法内的变量是线程安全的,
多个线程共同访问一个对象中的实例变量,可能出现非线程安全。
public class Tian{
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(InterruptException e){
e.printStackTrace();
}
}
}
//threadA
public class ThreadA extends Thread {
private Tian tian;
public ThreadA(Tian tian) {
super();
this.tian = tian;
}
@Override
public void run() {
super.run();
tian.addI("a");
}
}
public class ThreadB extends Thread {
private Tian tian;
public ThreadB(Tian tian) {
super();
this.tian = tian;
}
@Override
public void run() {
super.run();
tian.addI("b");
}
}
public static void main(String[] args) {
Tian tian = new Tian();
ThreadA threadA = new ThreadA(tian);
threadA.start();
ThreadB threadB = new ThreadB(tian);
threadB.start();
}
上述代码是多个线程同时访问同一个实例对象中的没有同步的方法,
方法内对对象的变量进行的更改,但输出的结果为:
a set over
b set over
b num = 200
a num = 200
不是我们预想的结果,所以出现了“脏读”现象,这就是非线程安全。
要想实现我们预想的结果,就需要对方法进行同步处理,synchronized关键字就
可以同步方法。
eg:
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();
}
}
输出结果为:
a set over
a num = 100
b set over
b num = 200
多个线程方位同一个对象中的同步方法时,一定是线程安全的。
synchronized关键字的简单使用:
当方法被synchronized关键字修饰时,方法就进行了同步处理。当经过同步处理
的方法被多个线程调用时,一个线程开始调用方法就会获得对象锁,这个方法正在
执行,当另一个线程调用该方法时,先去获得对象锁,但对象锁以被上个线程获得,
所以会一直等待获得对象锁而暂停执行,当第一个线程执行完同步方法后,会释放
对象锁,这个时候第二个线程才能获得对象锁,继续执行。也就是说被同步处理的
方法,同一时间只能被一个线程调用。
public static void main(String[] args) {
Tian tian = new Tian();
ThreadA threadA = new ThreadA(tian);
threadA.start();
Tian tian1 = new Tian();
ThreadB threadB = new ThreadB(tian1);
threadB.start();
}
这个执行结果就是:
a set over
b set over
b num = 200
a num = 100
这个新建了2个对象,两个线程处理了两个对象,是异步的所以不会出现非线程安全。这时多个线程访问多个对象,
jvm会创建多个锁。
注意”共享“,只要出现多个线程使用同一个对象,就要注意线程安全问题
synchronized方法与锁对象及对象方法。规则
1:A线程调用synchronized方法,获得对象锁,B线程仍可通过异步调用该对象的非同步方法。
2:A线程调用synchronized方法,获得对象锁,B线程调用该对象的其他同步方法,仍需等待获得对象锁。
注意:”脏读“ 就是多线程时,对对象变量的赋值和读取,当赋值没有完成,就读取时会出现脏读
synchronized重入锁
就是线程A调用同步方法获得对象锁,可以再次调用该对象的另一个同步方法,再次获得对象锁,也就是对象的同步方法
中可以调用本类的其他同步代码块。 这个属性同样适用与子类同步代码调用父类同步代码块。
线程执行异常时,线程锁自动释放,线程停止。
同步不具有继承性,也就是父类方法有synchronized关键字修饰,是同步方法,但子类重写后不再是同步方法,
synchronized同步语句块
适用同步方法,在一些情况下是很有弊端的,例如,一个线程执行耗时操作时,B线程就需要等待很长时间,这个情况就
可以适用synchronized同步语句块。
public void MyTask{
private String getData;
public void doLongTask(){
try{
System.out.println("begin task");
Thread.sleep(30000);
synchronized(this){ //这里就是synchronized同步语句块。
getData = priveteData
}
}catch (interruptException e){//}
}
}
这里并没有同步整个方法,而是同步了一部分关键的语句块,这样则避免了同步方法的一些弊端。
注意:synchronized(this)是一个对象监视器,获得该对象的锁,如果已经获得该对象锁,则其他线程对于这个对象的所有synchronized(this)语句块都需要等待。
任意对象做对象监视器。
上面所有介绍的synchronized方法或者synchronized(this)语句块,都是讲该对象本身作为监视器,获得的锁也是
该对象本身的锁,其实我们更改this的内容,就可以将任何对象做为对象监视器。
public class Can{
private String userName;
private String anyString = new String();
public void setUserName(String name){
synchronized(anyString){
userName = name;
}
}
}
这个锁的对象是anyString ,
注意:
1:该程序获得的是anyString对象锁,如果其他线程要获得该对象中的anyString 对象锁,需要等待。
2:一个对象中可以有多个这样的anyString对象,每一个对象的用法和this一样,线程都是获得的anyString对象的锁,anyString对象锁同时也只能被一个线程获得。
优点如果一个类里有多个同步方法,这样会经常造成线程阻塞,多个对象可以很好解决这个问题
给静态方法上锁
静态方法加synchronized和普通方法加锁效果一致。但静态方法加锁有本质区别,普通方法加锁是获得对象锁,静态
方法加锁是对该类对应的class加锁,也就是说一个是对象锁,一个是class锁。
死锁:死锁必须被避免,死锁就是一个线程获得锁后不能释放,其他线程无法获得锁。
关键字volatile
作用是使变量再多个线程中可见。