非线程安全:多个线程对同一个对象的中的实例变量进行并发访问,产生后果就是脏读,也就是获取的数据被更改。
非线程安全问题存在与“实例变量”中,如果是方法内部的私有变量,就不存在“非线程安全问题”。
线程安全:获得实例变量的值是经过同步处理的,不会出现脏读现象。
结论:只有共享资源才需要被同步,如果不是共享资源,则没有必要同步。
使用Synchronized关键字
方法内部变量,不存在非线程安全问题,以下代码不会发生“非线程安全”问题:
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
public class HasSelfPrivateNum {
public void addI(String username) {
try {
int num; /////////num定义在在方法内部
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
运行结果 互不影响:
a set over!
b set over!
b num=200
a num=100
多个线程访问同1个对象的实例变量,则有可能出现“非线程安全”问题
,用线程访问的对象中如果有多个实例变量,则运行结果会有可能出现交叉的情况。
以下代码会发生“非线程安全”问题:
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
public class HasSelfPrivateNum {
private int num = 0;/////////num定义在方法外部
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
运行结果: 发生了脏读
a set over!
b set over!
a num=200
b num=200
此例如果需要解决“非线程安全”问题,需要在addI(String username)前加上关键字synchronized即可。代码如下:
synchronized public void addI(String username) {
private int num = 0;/////////num定义在方法外部
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 (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
结论:在两个线程访问同一个对象中的同步方法时,一定是线程安全的
如果把上述代码中的Run类修改为:
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef1);
athread.start();
ThreadB bthread = new ThreadB(numRef2);
bthread.start();
}
}
运行结果为异步:
a set over!
b set over!
b num=200
a num=100
这是因为,在run中创建了两个对象,分别对应两个线程,也就是产生了两把锁,互不影响。
这也说明另一个问题,通过关键组synchronized获取的锁都是对象锁,而不是把方法作为锁。
Synchronized方法和非synchronized方法并存
如果一个类中,部分方法为Synchronized方法,其他方法为非synchronized方法,该如何,看以下代码:
public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName="
+ Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB() {
try {
System.out.println("begin methodB threadName="
+ Thread.currentThread().getName() + " begin time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
运行结果为:
begin methodA threadName=A
begin methodB threadName=B begin time=1533711232153
end
end endTime=1533711237153
由此我们可以得出结论:
A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中非synchronized类型的方法。
A线程先持有object对象的Lock锁,B线程如果调用object对象中synchronized类型的方法则需要等待,也就是同步。
synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的,在一个synchronized方法的内部调用本类的其他synchronized方法时,是永远可以得到锁的。
public class MyThread extends Thread {
@Override
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");
}
}
public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
运行结果:
service1
service2
service3
可重入锁:在获取到该对象的一个锁并且没有释放,可以再次获取自己的内部锁。
并且子类完全可以通过“可重入锁”调用父类的同步方法。
其他
Synchronized同步方法的弊端:效果过低,使用Synchronized同步方法,会导致整个方法同步,部分不必要同步的代码段也会同步。A线程调用同步方法执行一个长时间的任务,那B线程就需要等待较长时间,这种情况得用synchronized同步语块来解决。
使用synchronized同步代码块
当一个线程访问Object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized同步代码块。因此可以说不在synchronized中就是异步执行,在synchronized块中就是同步执行。实现一半同步一半异步。
直接看代码:
public class Task {
public void doLongTimeTask() throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println("nosynchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
System.out.println("");
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("synchronized threadName="
+ Thread.currentThread().getName() + " i=" + (i + 1));
}
}
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
try {
task.doLongTimeTask();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
try {
task.doLongTimeTask();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}
运行结果为:
nosynchronized threadName=Thread-0 i=1
nosynchronized threadName=Thread-1 i=1
nosynchronized threadName=Thread-0 i=2
nosynchronized threadName=Thread-1 i=2
nosynchronized threadName=Thread-0 i=3
nosynchronized threadName=Thread-1 i=3
nosynchronized threadName=Thread-0 i=4
nosynchronized threadName=Thread-1 i=4
nosynchronized threadName=Thread-0 i=5
nosynchronized threadName=Thread-1 i=5
nosynchronized threadName=Thread-0 i=6
nosynchronized threadName=Thread-1 i=6
nosynchronized threadName=Thread-0 i=7
nosynchronized threadName=Thread-1 i=7
nosynchronized threadName=Thread-0 i=8
nosynchronized threadName=Thread-1 i=8
nosynchronized threadName=Thread-0 i=9
nosynchronized threadName=Thread-1 i=9
nosynchronized threadName=Thread-0 i=10
nosynchronized threadName=Thread-1 i=10
synchronized threadName=Thread-1 i=1
synchronized threadName=Thread-1 i=2
synchronized threadName=Thread-1 i=3
synchronized threadName=Thread-1 i=4
synchronized threadName=Thread-1 i=5
synchronized threadName=Thread-1 i=6
synchronized threadName=Thread-1 i=7
synchronized threadName=Thread-1 i=8
synchronized threadName=Thread-1 i=9
synchronized threadName=Thread-1 i=10
synchronized threadName=Thread-0 i=1
synchronized threadName=Thread-0 i=2
synchronized threadName=Thread-0 i=3
synchronized threadName=Thread-0 i=4
synchronized threadName=Thread-0 i=5
synchronized threadName=Thread-0 i=6
synchronized threadName=Thread-0 i=7
synchronized threadName=Thread-0 i=8
synchronized threadName=Thread-0 i=9
synchronized threadName=Thread-0 i=10
非同步代码块是异步执行, 而同步代码块是同步执行,所以是一半同步一半异步。
同步代码块synchronized(this) 锁定的也是当前对象,根据此特定,说明在一个类中,有多个synchronized代码块的情况下,当一个线程访问的一个synchronized同步代码块时,其他线程对同一个object中所有其他synchronized(this)的访问都将被阻塞,说明一个类里,synchronized的对象监视器是同一个。
验证代码:
public class ThreadA extends Thread {
private ObjectService service;
public ThreadA(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodA();
}
}
public class ThreadB extends Thread {
private ObjectService service;
public ThreadB(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodB();
}
}
public class ObjectService {
public void serviceMethodA() {
try {
synchronized (this) {
System.out.println("A begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end end=" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
public void serviceMethodB() {
synchronized (this) {
System.out.println("B begin time=" + System.currentTimeMillis());
System.out.println("B end end=" + System.currentTimeMillis());
}
}
}
}
public class Run {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
运行结果:A begin time=1533714188830
A end end=1533714190830
B begin time=1533714190830
B end end=1533714190830
将任意对象作为对象监视器而非this
synchronized同步方法:
1对其他synchronized同步方法或者synchronized(this)同步代码块调用呈阻塞状态。
2同一时间只有一个线程可以执行synchronized同步方法中的代码
synchronized(this)同步代码块:
1对其他synchronized同步方法或者synchronized(this)同步代码块调用呈阻塞状态。
2同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
锁非this对象具有一定的优点:如果一个类中有多个synchronized方法,这是虽然能实现同步,但是会收到阻塞,所以影响运行效率;但是如果使用同步代码块锁非this对象(这里是关键,是关于synchronized代码块和synchronized方法的阻塞情况),则synchronized(非this)代码块中的程序与同步方法是异步的,不会与其他锁this同步犯法争抢this锁,可以提高效率。
上代码:
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.a();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.b();
}
}
public class Service {
private String anyString = new String();
public void a() {
try {
synchronized (anyString) {
System.out.println("a begin");
Thread.sleep(3000);
System.out.println("a end");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void b() {
System.out.println("b begin");
System.out.println("b end");
}
}
public class Run {
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 begin
b begin
b end
a end
--------------------------------------------over------------------------------------------------