im项目中都会存在离线消息,我们在接受到消息后,开启子线程,处理相关业务逻辑。因为业务逻辑需遵循一定的处理顺序,我们将部分代码加上了锁。但是在离线消息太多时,却出现了线程问题:OutOfMemoryError: pthread_create (1040KB stack) failed: Try again 本地创建线程时发现内存不够,栈内存溢出。花费了不少时间,一方面添加了队列,另一方面将项目中的同步代码好好梳理了一遍,总算将这个问题解决了。在这里,将线程同步的知识点梳理梳理,记录如下:
1.关键字synchronize修饰的同步方法,它的锁对象是谁?
答:同步方法的锁对象是调用者本身。如方法加锁后,对象p在不同线程调用该方法,会造成堵塞。
public void useMethodInThread(final int type, final String tag) {
new Thread(new Runnable() {
@Override
public void run() {
switch (type) {
case 1:
method1(tag);
break;
case 2:
method2(tag);
break;
case 3:
method3(tag);
break;
case 4:
method4(tag);
break;
case 5:
method5(tag);
break;
default:
break;
}
}
}).start();
}
/**
* 同步方法
*/
public synchronized void method1(final String str) {
Log.i(tag, "start method1 " + str);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Log.i(tag, "start method1 after sleep " + str);
}
}
//调用
useMethodInThread(1, "001");
useMethodInThread(1, "002");
日志:
10-26 15:18:32.572 I/TestLockActivity: start method1 001
10-26 15:18:34.572 I/TestLockActivity: start method1 after sleep 001
10-26 15:18:34.572 I/TestLockActivity: start method1 002
10-26 15:18:36.572 I/TestLockActivity: start method1 after sleep 002
第一次调用后,该方法被堵塞,直到调用完毕后,才进行第二次的调用。没毛病。
2.同步方法与同步代码块synchronized(this),会造成堵塞吗?
答:同步代码块synchronized(this)使用了当前对象作为锁对象,与同步方法中的锁对象一致,会造成线程堵塞。
public void useMethodInThread(final int type, final String tag) {
new Thread(new Runnable() {
@Override
public void run() {
switch (type) {
case 1:
method1(tag);
break;
case 2:
method2(tag);
break;
default:
break;
}
}
}).start();
}
/**
* 同步代码块
*/
public void method2(String str) {
synchronized (this) {
Log.i(tag, "start method2 " + str);
}
}
//调用
useMethodInThread(1, "001");
useMethodInThread(2, "002");
日志:
10-26 15:38:05.342 I/TestLockActivity: start method1 001
10-26 15:38:07.342 I/TestLockActivity: start method1 after sleep 001
10-26 15:38:07.392 I/TestLockActivity: start method2 002
同步方法中使用了线程sleep2秒,此时同步代码块一直在等待锁释放后,才执行代码。
3.同步代码块后的非同步代码,会在同步堵塞时,优先执行吗?
答:不会。这就像跑步,同步代码块是独行桥,桥上堵塞后,哪怕桥后的是多跑道,依然需要等待前面的先过桥。
/**
* 同步代码与非同步代码
*/
byte[] bytes = new byte[1];
public void method3(String str) {
Log.i(tag, "start method3 first " + str);
synchronized (bytes) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Log.i(tag, "start method3 synchronized " + str);
}
}
Log.i(tag, "start method3 last " + str);
}
//调用:
useMethodInThread(3, "001");
useMethodInThread(3, "002");
日志:
10-26 16:02:59.522 I/TestLockActivity: start method3 first 001
10-26 16:02:59.522 I/TestLockActivity: start method3 first 002
10-26 16:03:01.522 I/TestLockActivity: start method3 synchronized 001
10-26 16:03:01.522 I/TestLockActivity: start method3 last 001
10-26 16:03:03.572 I/TestLockActivity: start method3 synchronized 002
10-26 16:03:03.572 I/TestLockActivity: start method3 last 002
第二次调用时,在同步代码块中造成堵塞,释放锁后,各子线程独立运行。
4.同步方法中添加handler延迟,会释放锁对象吗?
答:会。创建handler时,一般需要设置looper,默认使用主线程的looper,这时线程切换,释放了锁对象。
private synchronized void method4(String str) {
Log.i(tag, "start method4 first " + str);
Message obtain = Message.obtain();
obtain.what = 0;
obtain.obj = str;
handler.sendMessageDelayed(obtain, 2000);
}
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0) {
Log.i(tag, "start method4 handler " + msg.obj);
}
}
};
//调用:
useMethodInThread(4, "001");
useMethodInThread(4, "002");
日志:
10-26 16:25:19.312 I/TestLockActivity: start method4 first 001
10-26 16:25:19.312 I/TestLockActivity: start method4 first 002
10-26 16:25:21.312 I/TestLockActivity: start method4 handler 001
10-26 16:25:21.312 I/TestLockActivity: start method4 handler 002
5.同步方法中添加timer延迟,会释放锁对象吗?
答:会。timer内部应该也做了线程切换,释放了锁对象。
private synchronized void method5(String str) {
Log.i(tag, "start method5 first " + str);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Log.i(tag, "start method5 sleep " + str);
}
runTimer(str);
}
private void runTimer(final String str) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
Log.i(tag, "start method5 timerTask " + str);
}
};
Timer timer = new Timer();
timer.schedule(timerTask, 2000);
}
//调用
useMethodInThread(5, "001");
useMethodInThread(5, "002");
日志:
10-26 16:45:09.912 I/TestLockActivity: start method5 first 001
10-26 16:45:11.912 I/TestLockActivity: start method5 sleep 001
10-26 16:45:11.962 I/TestLockActivity: start method5 first 002
10-26 16:45:13.912 I/TestLockActivity: start method5 timerTask 001
10-26 16:45:13.962 I/TestLockActivity: start method5 sleep 002
10-26 16:45:15.962 I/TestLockActivity: start method5 timerTask 002
启动timer后,立即释放了锁,第二次调用的代码立即执行,打印了method5 first 002,后面的timer部分代码在各子线程中分别执行。
写在最后:加锁同步只在多线程中才有存在的意义,单线程中必然是顺序执行。将线程看作是跑道,单跑道就不存在超越的问题。多线程就是多条跑到,同步代码或者同步方法就是跑到交叉处,必须容前面的先通过,后面的才能动身。