Java线程同步中的一个重要的概念synchronized.
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
问题的引入:
在程序中,如果主线程想要获得子线程的最终运行结果而不是获取到中间某一时刻的结果,就要调用线程的
join()方法,但是在下面代码中发现,即使调用了join()也得不到想设想的结果
public class firat {
public static void main(String[] args) {
Math math = new Math();
AddMath[] threadArray = new AddMath[4];
// 创建Add操作的线程并启动
for (int i = 0; i < threadArray.length; i++) {
threadArray[i] = new AddMath(math);
}
for (int i = 0; i < threadArray.length; i++) {
threadArray[i].start();
}
for (int i = 0; i < threadArray.length; i++) {
try {
threadArray[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(math.getValue());
}
}
class Math {
private int a;
public void addValue() {
a++;
}
public int getValue() {
return a;
}
}
class AddMath extends Thread {
private Math math;
public AddMath(Math math) {
this.math = math;
}
public void run() {
for (int i = 0; i < 1000; i++) {
math.addValue();
}
}
}
如果四个线程可以同步对math对象执行那么最终结果应该为4000,因为这里的threadArray数组里面包含的四个math是一个引用,所以对数组的四个元素操作都是对这个math操作的,四个线程都是执行AddMath方法,虽然这里在主线程中用了join()来确定四个子线程运行完毕再输出结果,但是四个线程并发(一个数据被两个线程读取到了),导致四个线程读取到的结果并不一定是上一个线程处理完的,虽然一定是运行了4000次但是因为线程并发导致计算结果不足4000。
解决方案:
①synchronized修饰整个方法
class Math {
private int a;
public void synchronized addValue() {//这样就同步了结果为4000
a++;
}
public int getValue() {
return a;
}
}
②synchronized代码块:
这种方案适用于同一方法中一部分代码需要同步,一部分可以并发
class Math {
private int a;
public String lock="";
public void addValue() {
System.out.println("1");//两个线程可以一起执行这块,但是下面就不行了
synchronized(lock){
a++;
}//只同步这块
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");//这不同步,但是必须等到上面的同步执行完了才能到这
}
public int getValue() {
return a;
}
}
不用lock变量的写法,用this,因为代码块本质上锁定的是本对象
class Math {
private int a;
public void addValue() {
synchronized(this){
a++;
}//只同步这块
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");
}
public int getValue() {
return a;
}
}
问题推广:
①.多线程对于同一个对象有synchronized修饰和没有synchronized修饰的方法是互不影响的,也就是说如果有方法A有synchronized修饰,方法B无synchronized修饰,则方法A是不能够并发的,方法B仍然是可以并发的。如下面的例子:
public class first {
public static void main(String[] args) {
Math math = new Math();
MathThread [] threadArray = new MathThread [4];
MathThread2 [] threadArray2 = new MathThread2 [4];
// 创建Add操作的线程并启动
for (int i = 0; i < threadArray.length; i++) {
threadArray[i] = new MathThread (math);
}
for (int i = 0; i < threadArray.length; i++) {
threadArray[i].start();
}
// 创建minus操作的线程并启动
for (int i = 0; i < threadArray2.length; i++) {
threadArray[i] = new MathThread2 (math);
}
for (int i = 0; i < threadArray2.length; i++) {
threadArray2[i].start();
}
for (int i = 0; i < threadArray.length; i++) {
try {
threadArray[i].join();
threadArray2[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(math.getValue());
}
}
class Math {
private int a;
public void synchronized addValue() {
a++;
}
public void minusValue() {
a--;
}
public int getValue() {
return a;
}
}
class MathThread extends Thread {
private Math math;
public MathThread (Math math) {
this.math = math;
}
public void run() {
for (int i = 0; i < 1000; i++) {
math.addValue();
}
}
class MathThread2 extends Thread {
private Math math;
public MathThread2 (Math math) {
this.math = math;
}
public void run() {
for (int i = 0; i < 1000; i++) {
math.minusValue();
}
}
}
计算结果并不是0而是一个正数,说明加操作没有并行,减操作并行了。
②.这里如果把math类改一下,让加减操作都有synchronized那么结果就是0,如下:
class Math {
private int a;
public void synchronized addValue() {
a++;
}
public void synchronized minusValue() {
a--;
}
public int getValue() {
return a;
}
}
说明多个线程对于同一对象的多个synchronized方法均是互斥的,实例方法中使用synchronizedsynchronized关键字锁定的是对象,所以说如果对于同一个math对象里面还有另一个synchronized方法的话,虽然两个线程执行的方法不一样但是因为他们是互斥的所以并不能同时执行这两个方法,若是两个math对象则不影响。
③.如果是static修饰minusValue方法,运行结果并不一直是0,即synchronized加在类方法上,那么锁定的是当前类,线程1、2执行加操作,则是互斥的;线程3、4执行减操作,则也是互斥的;但是线程1和3却不是互斥的,因为两个方法锁定的不是一个东西,加操作锁定的是当前对象,减操作锁定的是当前类。对于同一个类的多个synchronized方法是互斥的
class Math {
private static int a;
public void synchronized addValue() {
a++;
}
public static void synchronized minusValue() {
a--;
}
public int getValue() {
return a;
}
}
总结:
1.多线程对于同一个对象有synchronized修饰和没有synchronized修饰的方法是互不影响的
2.如果有一个方法有synchronized另一个没有,那么没有的那个可以并发,这样既可以提高效率又保证执行不重复
3.如果synchronized加在类方法上那么锁定的是当前类,对于同一个类的多个synchronized方法是互斥的
4.如果有一个static synchronized,一个synchronized方法那么他们两个没影响,一个锁类一个锁对象