当代码中多个线程任务访问同一共享资源时,就会引发冲突,目前解决多线程冲突问题都是采用序列化访问共享资源的解决方案,即将共享资源放在某一代码块中并加锁,某一时刻只能有一个线程访问该代码块.
同步的规则:"如果你正在写一个变量,它接下来可能被另一个线程读取,或者你正在读取一个已经被其他线程改写的变量,那么你需要使用同步,并且读写线程必须使用相同的监视器同步".
1)Synchronized同步方法
假如synchronized同步块在另外一个对象上同步,那么会出现同一时间两个线程分别访问increment()和get()方法;
Lock对象必须被显示的创建,锁定和释放.使用时需要注意两点:1)必须在finally中释放锁;2)return语句必须在try语句中,确保unlock不会过早发生.
同步的规则:"如果你正在写一个变量,它接下来可能被另一个线程读取,或者你正在读取一个已经被其他线程改写的变量,那么你需要使用同步,并且读写线程必须使用相同的监视器同步".
1)Synchronized同步方法
共享资源在内存中以对象的形式存在,访问该对象的方法如果需要加锁,则添加synchronized关键字构成同步方法.当在一个线程中调用某对象的同步方法时,在同一时间其他线程中不能再调用该对象的其他同步方法或同步块;
public class SynchBrock {
public static void main(String[] args) {
Task t=new Task();
for(int i=0;i<5;i++){
new Thread(){
public void run(){
while(true){
t.increment();
}
}
}.start();
}
while(true){
if(t.get()%2!=0){
System.out.println(t.get());
System.exit(0);
}
}
}
}
class Task{
public int i=0;
public synchronized void increment(){
i++;
i++;
}
public synchronized int get(){
return this.i;
}
}
在上例中共享资源为t.i,访问共享变量的共有两个方法increment()和get(),根据同步规则,两个方法都需要同步.同一时间只会有一个线程访问其中的一个方法,因此不会出现奇数的情况.
2)Synchronized同步块
使用Synchronized关键字创建同步块(临界区)时需要给定一个在其上进行同步的对象,最合理的是使用调用当前方法的对象即:synchronized(this),此时在其他线程中就不能访问该对象的其他synchronized方法或临界区;
public class SynchBrock {
public static void main(String[] args) {
Task t=new Task();
for(int i=0;i<5;i++){
new Thread(){
public void run(){
while(true){
t.increment();
}
}
}.start();
}
while(true){
if(t.get()%2!=0){
System.out.println(t.get());
System.exit(0);
}
}
}
}
class Task{
public int i=0;
public void increment(){
synchronized(this){
i++;
i++;
}
}
public synchronized int get(){
return this.i;
}
}
上例中将increment()方法中关键代码部分改成synchronized同步块,由于此时同步块同步的对象为this,因此get()同步方法也会在任务之间互斥,因此同一时间只会有一个线程访问其中的一个方法;假如synchronized同步块在另外一个对象上同步,那么会出现同一时间两个线程分别访问increment()和get()方法;
public class SynchBrock {
public static void main(String[] args) {
Task t=new Task();
for(int i=0;i<5;i++){
new Thread(){
public void run(){
while(true){
t.increment();
}
}
}.start();
}
while(true){
if(t.get()%2!=0){
System.out.println(t.get());
System.exit(0);
}
}
}
}
class Task{
public int i=0;
Object ob=new Object();
public void increment(){
synchronized(ob){
i++;
i++;
}
}
public synchronized int get(){
return this.i;
}
}
3)Lock同步块Lock对象必须被显示的创建,锁定和释放.使用时需要注意两点:1)必须在finally中释放锁;2)return语句必须在try语句中,确保unlock不会过早发生.
public class SynchBrock {
public static void main(String[] args) {
Task t=new Task();
for(int i=0;i<5;i++){
new Thread(){
public void run(){
while(true){
t.increment();
}
}
}.start();
}
while(true){
if(t.get()%2!=0){
System.out.println(t.get());
System.exit(0);
}
}
}
}
class Task{
public int i=0;
private Lock lock=new ReentrantLock();
public void increment(){
lock.lock();
try{
i++;
i++;
return;
}
finally{
lock.unlock();
}
}
public synchronized int get(){
return this.i;
}
}
多线程访问共享资源则需要加锁,加锁必须明白要对谁加锁,同步方法是对该方法的对象进行加锁,同步块必须指出要加锁的对象,当访问某加锁对象的同步方法时,同一时刻其他线程中不能访问该对象的其他同步方法或同步块.