对象及变量的并发访问
1.synchronized同步方法
synchronized关键字可用来保障原子性、可见性、和有序性。
我们需要掌握的是:
1)synchronized对象监视器为Object时的使用方法
2)synchronized对象监视器为Class的使用方法
1.1方法内的变量为线程安全
非线程安全问题存在于实例变量中,对于方法内部的私有变量,则不存在线程安全问题。
1.2 实例变量非线程安全问题
如果多个线程共同访问一个对象中的实例变量,则有可能会出现非线程安全问题。
两个线程同时访问同一个对象中的同步方法时一定是线程安全的。
1.3 多个对象多个锁
创建类HasSelfPrivateNum:
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void add(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();
}
}
}
创建两个线程类:
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.add("a");
}
}
ThreadB:
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.add("b");
}
}
最后的运行结果为:
a set over
b set over
bnum=200
anum=100
总结:本示例创建了两个业务对象(numRef1,numRef2),在系统中产生了两个锁,线程和业务对象属于一对一的关系,每个线程执行自己所属业务对象中的同步方法,不存在争抢关系,所以运行结果是异步的,更具体的讲,在上面的这个示例中创建了两个业务对象,所以产生两份实例变量,每个线程访问自己的实例变量,所以加不加synchronized关键字都是线程安全的。
如果运行结果为a set over,anum =100,b set over,bnum = 200,才说明线程是同步进行的。
1.4 关于synchronized同步锁方法的结论
1.4.1 A线程持有Object对象的lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
1.4.2 A线程持有object对象的lock锁,B线程如果在这时调用object对象中的synchronized类型的方法,则需要等待。
1.4.3 在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象
1.4.4 在Java中只有“将对象作为锁”这种说法,并没有“锁方法”这种说法
1.4.5 在Java中,“锁”就是对象,“对象”可以映射成“锁”,哪个线程拿到这把锁,哪个线程就可以执行这个对象中的synchronized同步方法
1.4.6 如果在X对象中使用了synchronize的关键字声明非静态方法,则X对象就被当成锁。
1.5 脏读
通过synchronized关键字可以解决脏读的问题。
拿一个对象A的setValue()和getValue()为例,其中setValue()被synchronized修饰,而getValue()没有。
现在有两个线程A和线程B
A先执行,setValue(),A线程未执行完,B线程无法调用setValue(),但是getValue()可被B线程调用,此时出现脏读
解决方案:给getValue()也加上synchronized修饰。
1.6 为了解决synchronized同步方法的等待时间过长的方案------->synchronized同步代码块synchronized(this)
synchronized同步代码块的作用:(synchronized同步代码块也是一样)
1.对其他synchronized同步方法或synchronized(this)同步代码块调用呈同步效果
2.同一时间只有一个线程可以执行synchronized同步方法中代码
1.7 synchronized(非this对象)
1.7.1 除了使用synchronized(this)格式来创建同步代码块,其实Java还支持将“任意对象”作为锁来实现同步的功能,这个“任意对象”大多数是实例变量及方法的参数,使用格式为
1.7.2 synchronized(非this对象x)同步代码块的作用:当多个线程争抢相同的“非this对象x”的锁时,同一时间只有一个线程可以执行synchronized(非this 对象x)同步代码块中的代码s
1.7.3 锁非this 对象具有一定的优点:如果一个类中有很多个synchronized方法,则这时虽然能实现同步,但影响运行效率,如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,因为有两把锁,不与其他锁this同步方法争抢this 锁,可大大提高运行效率。
1.8 多个锁就是异步执行
使用synchronized(非this对象x)同步代码“代码块”操作时,锁必须是同一个,否则就是异步调用,交叉执行。
下面使用个例子证明多个锁时是异步执行的结果:
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
this.service = service;
}
@Override
public void run() {
service.a();
}
}
ThreadB:
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
this.service = service;
}
@Override
public void run() {
service.b();
}
}
创建个模拟业务类:Service
public class Service {
private String anyString = new String();
public void a(){
try {
synchronized (anyString){
System.out.println("a begain");
}
Thread.sleep(3000);
System.out.println("a end!");
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void b(){
System.out.println("b begain");
System.out.println("b end!");
}
}
最后运行我们的测试类:
public class RunTest {
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();
}
}
最后的运行结果我们看到当A线程还没跑完的时候,B线程也已经在开始了,由于锁不同,所以运行结果是异步调用的;
a begain
b begain
b end!
a end!
和synchronized(非this对象X)和synchronized(this对象或者方法)不同的是,后者多个线程时,得等先拿到锁的那个线程跑完了才能执行其他synchronized的方法而synchronized(非this对象x)可以和其他synchronized方法异步执行。
1.9 细化synchronize(非this对象x)的三个结论:synchronize的(非this对象x)是将x对象本身作为“对象监视器”
1.9.1 当多个线程同时执行synchronized(x){}同步代码块时呈现同步效果
1.9.2 当其他线程执行x对象中synchronized同步方法时呈现同步效果
1.9.3 当其他线程执行x对象方法里面的synchronized(this)代码块时呈现同步效果
需要注意的是,如果其他线程调用不加synchronized关键字的方法,则还是异步调用,而且锁只是类继承的环境。
1.20 静态同步synchronized方法与synchronized(class) 代码块
关键字synchronized应用在static静态方法上时,那是对当前的*.java问对应的Class类对象进行持锁,Class类的对象是单例的。更具体的说,在静态static方法上使用synchronized关键字声明同步方法时,使用当前静态方法所在类对应Class类的单例对象作为锁。
而将synchronized加在非static方法上是将方法所在类的对象作为锁。
下面验证两个不是同一个锁的。
Service.java:
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 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 RunTest {
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();
}
}
最后的运行结果如图:方法C是异步执行,同步synchronized放在static方法上可以对类的所有对象的实例起作用,比如此时的线程A和B,不同的service对象,但是对printA和printB依然是同步执行
线程的名称为:a在1592551267059进入了printA
线程的名称为:c在1592551267062进入了printC
线程的名称为:c在1592551267062离开了printC
线程的名称为:a在1592551270059离开了printA
线程的名称为:b在1592551270059进入了printB
线程的名称为:b在1592551270060离开了printB
1.21 同步synchronized(A.class)和synchronized A中的static方法是同样作用,可以对类的所有对象的实例起作用,但是非static的synchronized只能对同一个实例有作用
1.22 String常量池特性与同步相关的问题与解决方案
问题:当synchronized(String对象)时,假设两个线程的值都是“AA”,String的特性会判断“AA"=="AA"为true,则此时两个线程的锁相同,B线程将不会被执行。
解决:所以大多数情况下,不适用String作为锁对象,而改用其他,而用new Object()实例化一个新的Object对象,它并不放入缓存池中,或者执行new String ()创建不同的字符串对象,形成不同的锁。