第一节:
- 方法中的变量不存在非线程安全问题,永远都是线程安全的,这是方法内部的变量是私有的特性造成的;
- “非线程安全”会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”;
- “脏读”,就是说取到的数据其实是被更改过的;
- “线程安全”,就是已获得实例变量的值经过同步处理的,不会出现脏读的现象;
- 当多个线程访问多个对象时,JVM就会创建多个锁;多个线程分别访问同一个类的多个不同的实例的相同名称的同步方法,效果却是以异步的方式运行的;
- 同步的单词为sunchronized,异步的单词为asynchronized;
- 关键字synchronized取得的锁都是对象锁;
- 调用关键字synchronized声明的方法一定是排队运行的。另外,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要;
- 实验结论: *
A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法 *
A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需要等待,也就是同步; - 脏读(dirtyRead):脏读是通过synchronized解决的;这里有个注意点:如果线程A此时调用了synchronized声明的X方法时,A线程就获得了该方法所在对象的锁,其他线程必须等A线程执行完毕后才可以调用X方法,而如果B线程此时调用了synchronized声明的非X方法时,也必须等待A线程将X方法执行完了才可以调用;
- synchronized关键字拥有锁重入的功能,也就是说当一个线程得到一个对象锁的时候,在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是可以永远得到锁的;
- 可重入锁:自己可以再次获取自己的内部锁。比如有1条线程获取了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果锁不可重入的话,就会造成死锁;
- 可重入锁也支持在父子类继承的环境中,子类是完全可以通过“锁重入”调用父类的同步方法的;
- 出现异常的锁会自动释放;
- 同步不具有继承性;
第二节:synchronized同步语句块:
- synchronized关键字声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长的时间,在这种情况下可以使用同步代码块来解决问题;
- 当两个并发线程访问同一个对象object中的sunchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该段代码,
- 不再synchronized块中就是异步执行,在synchronized块中就是同步执行;
- 同步代码块,当一个线程访问object的一个synchronized同步代码块时,其他线程对同一个object中所有其他synchronized同步代码块的访问将被阻塞,说明synchronized使用的“对象监视器”是一个;
- synchronized(this)代码块是锁定当前对象的;
- Java还支持将任意对象作为对象监视器,这个任意对象大多数是实例变量及方法参数,格式:synchronized(非this对象X);注意,使用“synchronized(非this对象X)同步代码块”格式进行同步操作时,对象监视器必须时同一个对象,如果不是,运行的结果就是异步调用了;
//观察对象监视器不是同一个时
//运行出现异步执行的情况
class Service4{
private String anyString = new String();
public void a(){
synchronized (anyString){
try {
synchronized (anyString){
System.out.println("a begin");
Thread.sleep(2000);
System.out.println("a end");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized public void b(){
System.out.println("b begin");
System.out.println("b end");
}
}
class Thread4A implements Runnable{
private Service4 service4;
public Thread4A(Service4 service4) {
this.service4 = service4;
}
@Override
public void run() {
service4.a();
}
}
class Thread4B implements Runnable{
private Service4 service4;
public Thread4B(Service4 service4) {
this.service4 = service4;
}
@Override
public void run() {
service4.b();
}
}
public class Test4 {
public static void main(String[] args) {
Service4 service4 = new Service4();
Thread t1 = new Thread(new Thread4A(service4));
Thread t2 = new Thread(new Thread4B(service4));
t1.start();
t2.start();
}
}
- 锁非this对象优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会收到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高运行效率;
- 同步代码块放在synchronized方法中声明,并不能保证调用方法的线程的执行顺序,虽然在同步代码块中执行的顺序是同步的,这样很容易出现脏读问题;使用”synchronized(非this对象)代码块“可以解决这个问题;
- 三个结论: * 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果 *
当其他线程执行x对象synchronized同步方法时呈同步效果 *
当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果 - 锁主Class类,可以对类的所有对象实例起作用,有两种方法:
* synchronized关键字加到static静态方法上;
* synchronized(X.Class)----类比synchronized(this);
package Day10.Test;
//锁住类的Class对象
//synchronized加到静态方法上;
class Service6{
synchronized public static void printA(){
try {
System.out.println("线程名称为:"
+Thread.currentThread().getName()
+" 在 "+System.currentTimeMillis()+"进入printA");
Thread.sleep(1000);
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");
}
}
//自定义三个线程
class Thread6A implements Runnable{
private Service6 service6;
public Thread6A(Service6 service6) {
this.service6 = service6;
}
@Override
public void run() {
service6.printA();
}
}
class Thread6B implements Runnable{
private Service6 service6;
public Thread6B(Service6 service6) {
this.service6 = service6;
}
@Override
public void run() {
service6.printB();
}
}
class Thread6C implements Runnable{
private Service6 service6;
public Thread6C(Service6 service6) {
this.service6 = service6;
}
@Override
public void run() {
service6.printC();
}
}
public class Test6 {
public static void main(String[] args) {
Service6 service6 = new Service6();
Thread t1 = new Thread(new Thread6A(service6));
Thread t2 = new Thread(new Thread6B(service6));
Thread t3 = new Thread(new Thread6C(service6));
t1.start();
t2.start();
t3.start();
}
}
运行结果:
使用synchronized(class)来锁住Class类:
- 数据类型String的常量池特性:JVM中具有常量池缓存的功能,所以当synchronized(String)同步块与String联合使用时,就会出现这样的情况,两个线程持有相同的锁,同一个“String”对象;
- 同步方法容易造成死循环;可以用同步块的方式来解决:
- 内部类中有两个同步方法,但使用的却是不同的锁,打印结果也是异步的,因为持有不同的“对象监视器”;
- 对内部类class2进行synchronized(class2)后,其他线程只能以同步的方式调用class2中的静态同步方法;
- 锁对象的改变:将任何数据类型作为同步锁时,如果有多个线程同时持有锁对象,如果持有相同的锁对象,那么这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的;
举个例子:如果线程B落后于线程A启动,那么就是异步的,分别获取锁对象的;如果同时启动,就是同时持有相同对象; - “对象监视器”如果是类实例的话,只要对象不变,即使对象的属性变了,运行的结果还是同步的;
第三节:volatile
- 关键字volatile主要作用就是使变量在多个线程间可见;强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值;解决私有堆栈中的值和公共堆栈中的值不同步造成的。
- 待续-------