2.1 synchronized同步方法
2.1.1方法内声明的变量是线程安全的
2.1.2实例变量非线程安全
2.1.3 多个对象多个锁
关键字synchronized取得的锁都是对象锁,而不是一段代码或者方法当作锁,哪个线程先执行待synchronized关键字的方法,哪个线程就持有该方法所属对象的锁lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。
但如果多个线程访问多个对象,则JVM会创建多个锁。
synchronized声明的方法一定是排队运行的,只有共享资源的读写访问才需要同步化,如果不是共享资源,就没有必要同步化。
2.1.4 synchronized方法与锁对象
测试线程1在执行synchronized方法的同时,线程2执行非synchronized方法,看是否有影响。
两个自定义线程类分别调用不同的方法。
package p2;
public class ThreadA extends Thread{
private MyObject1 myObject1;
public ThreadA(MyObject1 object1) {
this.myObject1=object1;
}
public void run() {
myObject1.methodA();
}
}
package p2;
public class ThreadB extends Thread{
private MyObject1 myObject1;
public ThreadB(MyObject1 object1) {
this.myObject1=object1;
}
public void run() {
myObject1.methodB();
}
}
package p2;
public class Run1 {
public static void main(String[] args) {
MyObject1 myObject1=new MyObject1();
ThreadA threadA=new ThreadA(myObject1);
ThreadB threadB=new ThreadB(myObject1);
threadA.start();
threadB.start();
}
}
package p2;
public class MyObject1 {
synchronized public void methodA() {
try {
System.out.println("begin synchronized methodA");
Thread.sleep(5000);
System.out.println("end synchronized methodA ");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
public void methodB() {
try {
System.out.println("begin methodB");
Thread.sleep(5000);
System.out.println("end methodB");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如下:
将methodB()方法前加上synchronized关键字,运行结果如下
该实验的结论是:
(1)A线程先持有object对象的Lock锁,B线程可以异步的方式调用object对象中的非synchronized类型的方法。
(2)A线程先持有object对象的Lock锁,B线程如果这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。
2.1.5 synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时时可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
package p2;
public class Run2 {
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
myThread2.start();
}
}
package p2;
public class MyThread2 extends Thread{
public void run() {
Service service=new Service();
service.service1();
}
}
public class Service {
synchronized public void service1() {
System.out.println("service1");
service2();
}
synchronized public void service2() {
System.out.println("service2");
service3();
}
synchronized public void service3() {
System.out.println("service3");
}
}
运行结果:
“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
可重入锁也支持在父子继承的环境中,当存在父子继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
2.1.7 出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的所会自动释放。
2.2synchronized同步语句块
2.2.1
用关键字synchronized声明方法在某些情况下是存在弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间,在这样的情况下可以使用synchronized同步语句块来解决,synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。
同步代码块的使用:
synchronized(this){
同步的执行语句
}
不在synchronized块的就是异步执行,在synchronized块中的就是同步执行。
public class Task {
public void doLongTimeTask() {
for(int i=0;i<100;i++) {
System.out.println("no synchronized threadName"+Thread.currentThread().getName()+" i="+i);
}
System.out.println("----------------");
synchronized (this) {
for(int i=0;i<100;i++) {
System.out.println("synchronized threadName"+Thread.currentThread().getName()+" i="+i);
}
}
}
}
package p2;
public class Run3 {
public static void main(String[] args) {
Task task=new Task();
MyThread3 myThread3=new MyThread3(task);
myThread3.start();
MyThread3 myThread32=new MyThread3(task);
myThread32.start();
}
}
运行结果
在使用同步synchronized(this)代码块时需要注意,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他的synchronized(this)同步代码块的访问将被阻塞。
Java还支持对“任意对象”作为“对象监视器”来实现同步的功能,这个“任意对象”大多数是实例变量及方法的参数,使用格式为synchronized(非this对象),在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
package p2;
public class Run4 {
public static void main(String[] args) {
MyService1 myService1=new MyService1();
MyThread4 myThread4=new MyThread4(myService1);
myThread4.start();
MyThread4 myThread42=new MyThread4(myService1);
myThread42.start();
}
}
package p2;
public class MyThread4 extends Thread{
private MyService1 service;
public MyThread4(MyService1 service) {
this.service=service;
}
public void run() {
service.setUsernamePassword("a", "aa");
}
}
package p2;
public class MyService1 {
private String username;
private String passowrd;
private String anyString=new String();
public void setUsernamePassword(String username,String password) {
try {
synchronized (anyString) {
System.out.println(
"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
this.username = username;
Thread.sleep(3000);
this.passowrd = password;
System.out.println(
"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
锁非this对象具有一定的优点:如果一个类中有很多synchronized方法,这是虽然能实现同步,但会受到阻塞,影响运行效率,但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。
2.2.2静态同步synchronized方法与synchronized(class)代码块
关键字synchronized还可以应用在static静态方法上,如果是这样写,那是对当前*.java文件对应的Class类进行持锁,与对象加锁不同,两者之间是异步的,且Class锁可以对类的所有对象实例起作用。使用synchronized(class)和 static方法加锁效果一样。
2.2.3 String的常量池特征
在JVM中具有String常量池缓存的特征,因此在大多数情况下synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但他并不放在缓存中。
2.2.4 锁对象的改变
在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有锁对象,则这些线程之间是同步的,如果分别获得锁对象,这些线程之间是异步的。
package p2;
public class MyThread5 extends Thread{
private MyService2 service;
public MyThread5(MyService2 service) {
this.service=service;
}
public void run() {
service.testMethod();
}
}
package p2;
public class MyService2 {
private String lock="123";
public void testMethod() {
try {
synchronized (lock) {
System.out.println(
"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
lock="456";
Thread.sleep(2000);
System.out.println(
"线程名称" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package p2;
public class Run5 {
public static void main(String[] args) throws InterruptedException {
MyService2 myService2=new MyService2();
MyThread5 myThread5=new MyThread5(myService2);
myThread5.start();
MyThread5 myThread52=new MyThread5(myService2);
Thread.sleep(50);
myThread52.start();
}
}
50毫秒后,线程B取得的锁是“456”,锁对象发生了改变,因此变成了异步。
将Thread.sleep(50)去掉,线程A和B持有的锁都是“123”,虽然将锁改成了“456”,但结果还是同步的,因为A和B共同争抢的锁是“123”。
只要对象不变,即使对象的属性被改变,运行的结果还是同步的。