synchronized同步方法
方法内的变量为线程安全
“非线程安全”问题存在于”实例变量”中,如果是方法内部的局部变量则不存在”非线程安全”问题,因为每个方法内部的变量都是私有的不存在共享的原因造成的。
实例变量非线程安全
假设有如下代码:
public class HasSelfPrivateNum {
private int num = 0;
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);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
其中的num即表示是实例变量(区别于加了static关键字的类变量),当两个线程同时操作add函数,一个线程传入的username是a,另一个是b,此时对于变量num就存在非线程安全的问题了。
public class ThreadA extends Thread {
private HasSelfPrivateNum hsp;
public ThreadA(HasSelfPrivateNum hsp) {
super();
this.hsp = hsp;
}
@Override
public void run() {
super.run();
hsp.add("a");
}
}
public class ThreadB extends Thread {
private HasSelfPrivateNum hsp;
public ThreadB(HasSelfPrivateNum hsp) {
super();
this.hsp = hsp;
}
@Override
public void run() {
super.run();
hsp.add("b");
}
}
此时启动这两个线程就会出现对变量num的访问出现了问题。
a set over
b set over
b 200
a 200
此时的a的值也变成了由b设置的200。此时只需要在方法add的前面加上synchronized的关键字即可。此时的访问就是a在访问add函数的时候b就被阻塞了,等a访问完了之后才开始b的访问,打印结果就会正常(是同步的输出):
a set over
a 100
b set over
b 200
多个对象有多个锁
sychronized获得锁是当前对象的锁,不同的对象就会产生不同的锁,将不同的对象传入不同的线程,两个线程访问相应的同步方法的时候,产生的是异步的效果,因为即时加了锁,但是这个锁是对不同的对象的。
public static void main(String[] args) {
HasSelfPrivateNum hsp = new HasSelfPrivateNum();
HasSelfPrivateNum hsp2 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(hsp);
ThreadB threadB = new ThreadB(hsp2);
threadA.start();
threadB.start();
}
输出的结果如下:
b set over
a set over
b 200
a 100
如果有多个同步方法
- 如果线程A拥有object对象的锁,此时线程B如果访问的是同一个对象,则此时的B线程可以异步访问object对象中的非synchronized方法。
- 如果在A拥有object的对象的锁之后,比如A访问object的同步方法methodA,此时线程B访问的是同一个对象的不同的同步方法methodB,此时的B需要等待获取到该对象的锁才能访问。
以上也就证明了sychronized的锁是对象的锁,而不是某个方法或者变量的锁。
如,在线程B中访问的是方法sub():
public void run() {
super.run();
hsp.sub("b");
}
在HasSelfPrivateNum中新增加了同步方法sub:
public synchronized void sub(String username) {
try {
if (username.equals("a")) {
num = -100;
System.out.println("sub a set over");
Thread.sleep(2000);
}else {
num = -200;
System.out.println("sub b set over");
}
System.out.println(username+" " +num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
一下是输出的结果:
add a set over
a 100
sub b set over
b -200
从结果中可以看出来当线程A访问HasSelfPrivateNum的对象hsp的add方法的时候,获取了该对象的锁,此时线程B想要访问hsp对象的sub方法,但是此时没有hsp对象的锁,所以排队等候。等线程A访问结束之后将对像hsp的锁释放了之后线程B才能正常访问sub方法。
synchronized 锁重入
关键字sychronized具有锁重入的功能,当一个线程得到一个对象的锁之后,此时在还没有释放锁的时候是可以继续请求该对象的锁。比如,在一个同步方法service1中调用了另一个同步方法service2,如果是不可重入的的锁,在访问service1获取对象锁之后,如果再获取该对象的锁来访问service2就会出现死锁的情况。但是synchronized的可重入锁,就可以在一个同步方法中调用该对象的另一个同步方法。
可重入锁对于父子继承关系也是成立的。
比如,进程获取之类的实例锁之后,在访问子类实例的方法a的时候,可以在方法a中访问父类的同步方法b,此时也是可以直接获取到锁的。
比如,父类Main如下:
public class Main {
public int i = 10;
synchronized public void oper() {
try {
i--;
System.out.println("main class i="+i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
子类Sub如下:
public class Sub extends Main {
synchronized public void oper2() {
try {
while (i > 0) {
i--;
System.out.println("sub class i="+i);
Thread.sleep(100);
this.oper();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当线程获取Sub的对象锁之后,调用oper2方法,在给方法中调用了父类的oper()方法,由于sychronized是重入锁,所以可以正常访问父类的oper方法,如果是非可重入锁则会出现死锁。
public class MyThread extends Thread {
@Override
public void run() {
super.run();
Sub sub = new Sub();
sub.oper2();
}
}
输出结果为:
sub class i=9
main class i=8
sub class i=7
main class i=6
sub class i=5
main class i=4
sub class i=3
main class i=2
sub class i=1
main class i=0
同步方法内出现异常,锁会自动释放
如果线程A访问一个同步方法的时候就会获取这个方法所在的对象的锁,此时B线程也访问这个方法的时候就会排队等候。如果此时A线程访问的方法内出现了异常,此时A拥有的该对象的锁会立即被释放,此时B就可以获取到这个对象的锁了。
同步不具有继承性
当子类Sub继承了父类Main,且在父类中的方法oper是同步方法,继承过来之后如果没有重写父类的oper方法,则在调用父类的同步方法oper的时候依旧是正常的同步方法的调用,如果继承过来之后,子类重写了父类的oper方法,子类中的oper方法也必须加上sychronized关键字才能起到同步的作用。
使用sychronized方法,有明显的弊端就是,当同步方法中有耗时很长的操作,则会很影响多个线程的访问时间
synchronized同步代码块
synchronized代码块的使用
使用如下的形式将一段代码块进行包括:
synchronized(this) {
//code here
...
}
当某个线程访问到这个代码块的时候,就获取到这个对象的锁,然后在这一段代码执行完之后才会释放这个对象的锁,其他的线程才能正常的访问这一段代码块。实际上使用synchronized将一段代码进行包围起来就是缩小了代码同步的范围。
不在synchronized同步块内的代码是异步执行的,在synchronized同步块里面的代码是同步执行的(即排队执行)。
synchronized 代码块间的同步性
synchronized(this)使用的也是对象监视器,即获取的锁是针对该类的对象来说的。也就是说,当一个线程访问某个对象object中的一个代码同步块的时候,其他的线程对同一个对象的所有的其他的synchronized(this)块的访问都会被阻塞。
和synchronized方法一样,synchronized(this)代码块锁定的是当前对象
等待通知机制
wait()、wait(n)、notify()、notifyAll()、sleep()
sleep(n)
睡眠n毫秒,通过Thread.sleep(n)方法调用,睡眠的时候不释放锁。
wait()
在wait方法处停止运行,等待notify或者中断为止。在进行wait()之前,必须获得Object对象级(wait是Object的方法,又因为Object是所有的父类,即其子类的对象即可)的锁,所以必须和synchronized一起使用。在使用wait之后会释放当前获得的对象锁。
Note:wait()是指无限制等待直到notify/notifyAll或者中断,wait(n)在时间n毫秒之后会自动唤醒
与wait相对应的就是notify了,两个一起构成了等待/通知机制来是先线程之间的通信:
notify()
也必须获得Object对象级的锁。调用成功后会随机选择一个处于等待状态中的线程,使其或者该对象的对象锁。
notifyAll()
就是对所有的线程都进行notify操作
注意:notify之后,当前线程不会立马释放当前获得的对象锁,所以被唤醒的线程也不会立马被唤醒获得对象锁,而是要等到notify所在的同步方法或者同步块执行完后才会释放锁。
所以:可知wait方法会释放锁,notify不会释放锁
过早就进行了通知
在通知之前如果都没有wait,则会导致在wait的地方一直处于等待,导致程序逻辑错误,此时可以通过设置标志位来进行使用,如果已经通知了,则不进行wait操作。
ReentrantLock类
相比sychronized而言,具有更多的功能和更为灵活的用法。
简单使用
1、先创建一个ReentrantLock lock对象
2、在需要加锁的地方使用lock.lock()即可
3、在释放锁的地方使用lock.unlock()即可
使用Condition对象来实现等待、通知
是什么
是一种对象监视器,每个lock对象可以创建多个监视器,在使用condition之前必须要获取到当前对象的锁
如何使用
和ReentrantLock使用一样,先需要创建一个Condition对象,创建的方式就是使用当前的lock对象来进行创建:
Condition con = lock.newCondition();
con.await()方法相当于Object中的wait()方法
con.await(long time, TimeUnit unit)方法相当于Object中的wait(long time)
con.signal()相当于Object中的notify()
con.signalAll()相当于Object中 的notifyAll()
Note : TimeUnit是一个枚举类型,用来指明传入的等待时间的单位,比如:TimeUnit.MILLISECONDS(毫秒)
使用Condition对象实现通知部分线程
由于一个lock对象可以获取多个Condion对象,所以,可以通过获取多个Condition对象,然后不同的方法中使用不一样的condition去进行await操作,这样,在多线程中,使用不同的方法的线程就可以调用不同的condition进行signal操作。
public class TestReentrantLock {
private ReentrantLock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
public void methodA() {
try {
lock.lock();
System.out.println("in A");
conditionA.await();
System.out.println("out A");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void methodB() {
try {
lock.lock();
System.out.println("in B");
conditionB.await();
System.out.println("out B");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void sigA(){
lock.lock();
System.out.println("sig A");
conditionA.signal ();
lock.unlock();
}
public void sigB(){
lock.lock();
System.out.println("sig B");
conditionB.signal();
lock.unlock();
}
}
实现生产者和消费者模式:多对多交替打印
(有待实现)