1.synchronized方法与锁对象
示例代码如下:
public class Test {
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();
}
}
class MyObject {
public void methodA(){
try{
System.out.println("begin methodA threadName == " + Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("end");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object){
this.object = object;
}
@Override
public void run(){
super.run();
object.methodA();
}
}
class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object){
this.object = object;
}
@Override
public void run(){
super.run();
object.methodA();
}
}
打印输出如下:
begin methodA threadName == B
begin methodA threadName == A
end
end
分析: 线程没有排队运行,a线程访问公共类没结束,线程B就进来访问公共类methodA,导致不同步。
解决办法,在methodA方法前面加关键字synchronized。修改后输出如下:
begin methodA threadName == A
end
begin methodA threadName == B
end
分析:调用关键字synchronized声明的方法一定是排队运行的。
另外只有共享资源才需要同步化,如果线程A,B访问不同的方法或者变量,则不需要同步化。
脏读:读取变量时,此值已被其他线程更改过了。
上面例子前提是多线程访问同一个对象,如果是多个对象,则情况就有所不同
修改测试代码如下:
MyObject objectA = new MyObject();
ThreadA a = new ThreadA(objectA);
a.setName("A");
MyObject objectB = new MyObject();
ThreadB b = new ThreadB(objectB);
b.setName("B");
即线程用不同的对象构造,虽然methodA使用了synchronized修饰,但输出如下:
begin methodA threadName == A
begin methodA threadName == B
end
end
说明关键字synchronized取得的锁都是基于对象的锁,而不是基于方法的锁。
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,准确讲是获得了对象的锁,其它线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其它非Synchronized同步方法。
如果B线程想要访问声明了synchronized的非X方法,则必须等线程A执行完,也就是释放掉对象锁之后才可以调用。
synchronized锁重入
当一个线程获得了对象锁之后,再次请求对象锁时可以再次获得该对象的锁。
一个synchronized方法的内部调用奔雷其它synchronized方法,是永远可以得到锁的。
可重入锁也支持父子类继承的环境中。
即某线程获得子类对象的锁以后,可以通过子类synchronized方法访问父类synchronized方法。
出现异常,锁自动释放
当一个线程执行异常时,其所持有的锁会自动释放。
同步synchronized不具备继承性
父类方法有synchronized方法修饰,子类继承的方法,必须也加synchronized才会具有同步性,否则为非同步方法。
synchronized不光可以修饰整个方法,还可以同步语句块来使用
当一个synchronized方法执行时间很长的时候,其它线程则必须一直等待,在这样的情况下可以使用synchronized同步语句块来解决。
示例代码如下:
public class Test {
public static long aa;
public static long bb;
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();
try{
Thread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("总计:" + String.valueOf(bb-aa));
}
}
class MyObject {
synchronized public void methodA(){
try{
if(Thread.currentThread().getName().equals("A")){
Test.aa = System.currentTimeMillis();
}
System.out.println(Thread.currentThread().getName() +" begin task : " + System.currentTimeMillis());
Thread.sleep(3000);
if(Thread.currentThread().getName().equals("B")){
Test.bb = System.currentTimeMillis();
}
System.out.println(Thread.currentThread().getName() + " end:" + System.currentTimeMillis());
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object){
this.object = object;
}
@Override
public void run(){
super.run();
object.methodA();
}
}
class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object){
this.object = object;
}
@Override
public void run(){
super.run();
object.methodA();
}
}
输出如下:
A begin task : 1475498046580
A end:1475498049591
B begin task : 1475498049591
B end:1475498052605
总计:6025
说明A和B2个线程用了6秒完成任务。
修改代码如下:
class MyObject {
public void methodA(){
try{
if(Thread.currentThread().getName().equals("A")){
Test.aa = System.currentTimeMillis();
}
System.out.println(Thread.currentThread().getName() +" begin task : " + System.currentTimeMillis());
Thread.sleep(3000);
synchronized(this){
if(Thread.currentThread().getName().equals("B")){
Test.bb = System.currentTimeMillis();
}
System.out.println(Thread.currentThread().getName() + " end:" + System.currentTimeMillis());
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
输出如下:
A begin task : 1475498182777
B begin task : 1475498182777
A end:1475498185790
B end:1475498185790
总计:3013
使用sychronized同步代码块,可以提高运行效率,不用让其它线程一直等待整个方法,而是等待方法中某一块代码。
sychronized代码块与synchronized方法一样,都是锁定对象。
多个线程调用一个同步方法,也会出现脏读,示例代码如下:
public class Test {
public static long aa;
public static long bb;
public static void main(String[] args) {
MyList myList = new MyList();
ThreadA a = new ThreadA(myList);
a.setName("A");
ThreadB b = new ThreadB(myList);
b.setName("B");
a.start();
b.start();
}
}
class MyList {
private ArrayList<String> list = new ArrayList<String>();
synchronized public void add(String username){
System.out.println("ThreadName= " + Thread.currentThread().getName() + "执行了add方法");
list.add(username);
System.out.println("ThreadName= " + Thread.currentThread().getName() + "退出了add方法");
}
}
class ThreadA extends Thread {
private MyList list;
public ThreadA(MyList list){
this.list = list;
}
@Override
public void run(){
for(int i=0;i<100;i++){
list.add("ThreadA" + (i+1));
}
}
}
class ThreadB extends Thread {
private MyList list;
public ThreadB(MyList list){
this.list = list;
}
@Override
public void run(){
for(int i=0;i<100;i++){
list.add("ThreadB" + (i+1));
}
}
}
实例中add方法已经synchronized,但运行结果如下:
ThreadName= B执行了add方法
ThreadName= B退出了add方法
ThreadName= A执行了add方法
ThreadName= A退出了add方法
ThreadName= B执行了add方法
ThreadName= B退出了add方法
ThreadName= B执行了add方法
ThreadName= B退出了add方法
虽然线程打印成对的“进入”与“退出”,但是线程A和B交替执行,会导致脏读。解决办法:
将Mylist list对象“同步化”,虽然add方法是“同步化”的,但是“list”对象确没同步,修改代码如下:
class MyList {
private ArrayList<String> list = new ArrayList<String>();
public void add(String username){
synchronized(list){
System.out.println("ThreadName= " + Thread.currentThread().getName() + "执行了add方法");
list.add(username);
System.out.println("ThreadName= " + Thread.currentThread().getName() + "退出了add方法");
}
}
}
修改后执行结果如下:
ThreadName= A执行了add方法
ThreadName= A退出了add方法
...
ThreadName= A执行了add方法
ThreadName= A退出了add方法
ThreadName= A执行了add方法
ThreadName= A退出了add方法
ThreadName= B执行了add方法
ThreadName= B退出了add方法
ThreadName= B执行了add方法
ThreadName= B退出了add方法
...
进入退出"同步化",并且A和B线程执行也"同步化"
synchronized(非this对象x)的结论:
a. 当多个行程同时执行synchronized(){}同步代码块时呈同步效果
b. 当其它线程执行x对象中synchronized同步方法时呈同步效果
c. 当其他线程执行x对象方法里面的synchronized(this)代码块时呈同步效果
静态同步synchronized方法
运行效果同非static方法一致,区别在于:
synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。
同步synchronized(class)代码块的作用 = synchronized static方法的作用一样。