备忘 Android中打印当前进程中所有线程的堆栈:
private void getThreadTrack() {
Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces();
Log.e("albertThreadDebug","all start==============================================");
for (Map.Entry<Thread, StackTraceElement[]> entry : threadMap.entrySet()) {
Thread thread = entry.getKey();
StackTraceElement[] stackElements = entry.getValue();
Log.e("albertThreadDebug","name:"+thread.getName()+" id:"+thread.getId()+
" thread:"+thread.getPriority()+" begin==========");
for (int i = 0; i < stackElements.length; i++) {
StringBuilder stringBuilder = new StringBuilder(" ");
stringBuilder.append(stackElements[i].getClassName()+".") .append(stackElements[i].getMethodName()+
"(") .append(stackElements[i].getFileName()+":") .append(stackElements[i].getLineNumber()+")");
Log.e("albertThreadDebug",stringBuilder.toString());
}
Log.e("albertThreadDebug","name:"+thread.getName()+" id:"+thread.getId()+
" thread:"+thread.getPriority()+" end==========");
}
Log.e("albertThreadDebug","all end==============================================");
}
一,Java多线程技能
1.1 进程和多线程的概念
可以把一个正在操作系统中运行的程序理解为一个“进程”。进程是受操作系统管理的基本运行单元。
线程可以理解为在进程中独立运行的子任务。当打开浏览器,下载文件的同时,还可以打开看视频,这些功能就对应了多个线程在后台运行。使用多线程就是在使用异步,因为每个线程被调用的时机是随机的。
1.2 继承Thread类,实现Runnable接口。
实现多线程的方式有两种,一是继承Thread类,一是实现Runnable接口。
Thread类也实现了Runnable接口,他们之间具有多态关系。
继承Thread类,最大的局限是不能多继承,所以为了支持多继承,可以实现Runnable
接口。
使用多线程时,代码的运行结果与代码执行顺序或调用顺序是无关的。
调用thread的start方法启动的线程,是交给“线程规划器”安排处理,具有异步执行的效果;如果调用代码thread.run方法就不是异步执行了,而是同步,这个线程对象并不交给“线程规划器”处理,而是由main线程来调用run方法,也就是要等到run方法代码执行完才能执行后面的代码。
实现的Runnable接口,可以作为参数传给Thread类的构造函数。同时因为Thread类也实现了Runnable接口,所以也可以将一个Thread类对象传给Thread类的构造函数,实现吧把一个Thread对象中的run方法交由其他线程调用。
在多线程数据共享的情况下,可以通过synchronized关键字,synchronized关键字可以在任意对象及方法上加锁,而加锁的这段代码成为“互斥区”或“临界区”。
多线程操作中有个“非线程安全”的术语,主要是指多个线程对同一个对象中同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响到程序的执行流程。
1.3 currentThread()可返回代码段正在被那个线程调用的信息。如果没有调用thread的start方法,是不会创建新的线程的,它的run方法还是运行在原来的线程中。
1.4 isAlive() 判断当前线程是否处于活动状态。
活动状态就是线程已经启动且尚未终止,线程处于正在运行或准备运行的状态。
1.5 sleep()作用是在指定的毫秒内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指this.currentThread()返回的线程。
1.6 getId()方法的作用是取得线程的唯一标识。
1.7 停止线程。
大多数停止一个线程的操作使用Thread.interrupt()方法,尽管方法的名称是“停止,终止”的意思,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
Java中有3中方法可以终止正在运行的线程:
1) 使用退出标志,是线程正常退出,也就是当run方法完成后线程终止。
2) 使用stop方法强行终止线程,但是这个方法已经被作废了,因为stop和suspend、resume一样,都被作废了,他们可能产生不可预料的结果。
3) 使用interrupt方法终止线程。
1.7.1调用interrupt()方法仅仅是在当前线程中打了一个停止标记,并不是真的停止线程。
ThreadDemo threadDemo = new Thread();
threadDemo.interrrupt();//并没有停止线程。
1.7.2 判断线程是否是停止状态
Thread.java类里提供了两个方法:
1) this.interrrupted();测试当前线程是否已经中断。
2) this.isInterrupted();测试线程是否已经中断。
Interrupted()方法,测试当前线程是否已经中断,当前线程是指运行this.interrupted()方
法的线程,比如:
class MyThread extends Thread{
@Override
public void run(){
super.run();
for(int i=0; i<500000; i++){
System.out.println("i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.start();
Thread.sleep(500);
thread.interrupt();
System.out.println("是否停止 1? ="+thread.interrupted());
System.out.println("是否停止 2? ="+thread.interrupted());
}catch(InterruptedException e){
System.out.println("Main catch");
e.printStackTrace();
}
System.out.println("Main end.");
}
}
运行结果:
i=98140
是否停止 1? =false
i=98141
…
i=98224
是否停止 2? =false
Main end.
i=98225
从控制台输出结果看,MyThread线程并未停止,运行:thread.interrupted()这句代码的当前线程是main,它从未中断过,所以结果是false。
将上面的测试代码改为:
MyThread thread = new MyThread();
thread.start();
Thread.sleep(500);
//thread.interrupt();
//这会把主线程中断。
Thread.currentThread().interrupt();
System.out.println("是否停止 1? ="+thread.interrupted());
System.out.println("是否停止 2? ="+thread.interrupted());
运行结果:
i=89376
是否停止 1? =true
i=89377
….
i=89620
是否停止 2? =false
i=89621
第一个bool值,判断出主线程是停止状态,第二个bool值false,表示:线程的中断状态由该方法清除,所以再次调用interrupted()返回值是false。
在看另一个方法isInterrupted(),
测试代码:
MyThread thread = new MyThread();
thread.start();
Thread.sleep(500);
thread.interrupt();
System.out.println("是否停止 1? ="+thread.isInterrupted());
System.out.println("是否停止 2? ="+thread.isInterrupted());
运行结果:
i=94353
是否停止 1? =true
是否停止 2? =true
Main end.
i=94354
isInterrupted()方法不是static的,它是实例方法,测试的MyThread这个线程是不是暂停了,并且不会清除状态标记。
1.7.3 能停止的线程-异常法
借用前面的代码,MyThread的run方法修改如下,其他不变:
class MyThread extends Thread{
@Override
public void run(){
super.run();
try{
for(int i=0; i<500000; i++){
System.out.println("i="+i);
if(this.interrupted()){
System.out.println("线程已经停止,退出运行。");
//break;
throw new InterruptedException();
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
i=95742
线程已经停止,退出运行。
Main end.
java.lang.InterruptedException
atMyThread.run(ThreadDemo.java:11)
上面的代码中,如果只是调用break退出for循环,for循环之后的代码还是会继续执行,我们的目标是想要在判断出线程停止后,run中的语句不在继续执行,通过抛出异常的方式可以实现这个目标。
也可以通过return实现停止线程的效果,但是还是抛异常的方法更好,因为这个异常会继续往上抛,使线程停止的事件得以传播。
1.1 暂停线程
Java多线程中,可以使用suspend()方法暂停线程,是用resume()方法恢复线程的执行。
测试代码
class MyThread extends Thread{
private long i= 0;
public long getI(){
return i;
}
public void setI(long i){
this.i = i;
}
@Override
public void run(){
while(true){
i++;
}
}
}
public class SuspendResumeDemo {
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.suspend();
System.out.println("A= "+System.currentTimeMillis()+", i="+thread.getI());
Thread.sleep(1000);
System.out.println("A++= "+System.currentTimeMillis()+", i="+thread.getI());
thread.resume();
Thread.sleep(1000);
thread.suspend();
System.out.println("B= "+System.currentTimeMillis()+", i="+thread.getI());
Thread.sleep(1000);
System.out.println("B++= "+System.currentTimeMillis()+", i="+thread.getI());
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
A= 1515399519208, i=458693572
A++= 1515399520208, i=458693572
B= 1515399521210, i=915434631
B++= 1515399522211, i=915434631
从时间信息、i的值可以看出,线程被暂停了,又被恢复运行了。
1.8.2 suspend(),resume()方法都是被废弃的,因为他们使用不当,容易造成公共的同步对象的独占,使得其他线程无法访问公共的同步对象。
测试代码:
class SynchronizedObject{
synchronized public void printString(){
System.out.println("bengin");
if(Thread.currentThread().getName().equals("a")){
System.out.println("a 线程 suspend了。");
Thread.currentThread().suspend();
}
System.out.println("end");
}
}
public class SuspendResumeDemo {
public static void main(String[] args) {
try{
final SynchronizedObject object = new SynchronizedObject();
Thread thread1 = new Thread(){
@Override
public void run(){
object.printString();
}
};
thread1.setName("a");
thread1.start();
Thread.sleep(1000);
Thread thread2= new Thread(){
@Override
public void run(){
System.out.println("thread2 启动了,但无法进入printString()方法体,因为这个方法被a线程锁定,并且a线程现在暂停了。");
object.printString();
}
};
thread2.start();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
bengin
a 线程 suspend了。
thread2 启动了,但无法进入printString()方法体,因为这个方法被a线程锁定,并且a线程现在暂停了。
另外,还有一种独占锁的情况,测试代码:
class MyThread extends Thread{
private long i= 0;
@Override
public void run(){
while(true){
i++;
}
}
}
public class SuspendResumeDemo {
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.suspend();
System.out.println("main end");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
main end
这个结果没有什么意外,但是修改下代码:
While循环中加了一句打印。
class MyThread extends Thread{
private long i= 0;
@Override
public void run(){
while(true){
i++;
System.out.println("i="+i);
}
}
}
运行结果:
i=199905
i=199906
i=199907
i=199908
没有打印 "main end"
没有打印 "main end"的原因,看下println的源码:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
当程序运行到println()方法内部停止时,同步锁不会释放,所以main方法的这句:System.out.println("mainend");不能执行打印。
1.8.3 suspend(),resume()方法的缺点---不同步
使用suspend(),resume()时,容易因为线程的暂停而导致数据不同步的情况。
测试代码:
class MyObject{
private String username = "1";
private String password = "111";
public void setValue(String u, String p){
this.username = u;
if(Thread.currentThread().getName().equals("a")){
System.out.println("a线程停止!!!");
Thread.currentThread().suspend();
}
this.password = p;
}
public void printUserNamePassword(){
System.out.println(username+"---"+password);
}
}
public class SuspendResumeDemo {
public static void main(String[] args) {
try{
MyObject myobject = new MyObject();
Thread thread1 = new Thread(){
@Override
public void run(){
myobject.setValue("a", "aaa");
}
};
thread1.setName("a");
thread1.start();
Thread.sleep(500);
Thread thread2 = new Thread(){
@Override
public void run(){
myobject.printUserNamePassword();
}
};
thread2.start();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
a线程停止!!!
a---111
username和password出现了不同步的情况。
1.1yield方法
yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用cpu执行时间。但是放弃的时间不确定,有可能刚刚放弃,马上有获得cpu时间片。
Thread.yield();
1.2守护线程
Java中有两种线程,一种是用户线程,一种是守护线程。
守护线程是一种特殊的线程,特性是:当进程中不存在非守护线程时,守护线程才自动销毁。只要当前JVM实例中存在任何一个非守护线程没有结束,守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束。
典型的守护线程是垃圾回收线程GC。
通过thread1.setDaemon(true);标记一个线程是守护线程,这个方法要在start方法之前调用。