安卓复习---Thread(转载加整理)

本文参考和引用如下网址:

https://blog.csdn.net/grey_mouse/article/details/84193844

https://blog.csdn.net/yincheng_917/article/details/82721424

https://www.cnblogs.com/renhui/p/6066852.html

https://www.cnblogs.com/xiaoxi/p/8311034.html

一,进程

进程的概念:

进程是操作系统分配一块独立空间和资源给一个运行的程序,这个程序包含一系列需要执行的任务。

单进程与多进程:

单进程的计算机一次只能做一件事情,而多进程的计算机可以做到一次做不同的事情,比如一边听音乐,一边听打游戏,这两件事情虽然感觉起来是在同时一起进行的,但其实是CPU在做着程序间的高效切换,这才让我们觉得是同时进行的。

进程的创建:

通过Runtime的exec()方法和ProcessBuilder的start()方法可以创建进程。

二,线程

线程就是进程中执行的某个任务。

单线程和多线程

单线程就是一个任务执行到底,期间不去做任务以外的事情,一个程序(app)几乎不可能一条线程跑到底,多线程是指程序同一时间段有多个任务一起执行,多线程的目的不是提供程序的运行速度,而是提高程序的使用率,多线程其实没有同时执行多个任务,而是根据线程执行时的使用频率等情况分配使用资源,只是操作系统运行速度快,让人以为是多个任务同时执行。

线程从创建到消亡,经历的状态包括:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。

java创建一个线程后,并不会马上马上进入准备就绪状态,等运行条件(如内存分配)都准备好后,线程进入就绪状态。当线程进入就绪状态后,并非立刻就能获取CPU执行时间,当得到CPU执行时间后正式进入运行状态。

线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。当由于突然中断或者子任务执行完毕,线程就会被消亡。

这副图描述了线程从创建到消亡之间的状态:

blocked、waiting、time waiting统称为阻塞状态。

java Thread类:

public
class Thread implements Runnable {
    //synchronized修饰的锁对象
    private final Object lock = new Object();
    //线程名称
    private volatile String name;
    //线程的优先级(最大值为10,最小值为1,默认值为5)
    private int            priority;
    private Thread         threadQ;
    private long           eetop;
    /* Whether or not to single_step this thread. */
    private boolean     single_step;
    //是否守护线程
    private boolean     daemon = false;
    //要执行的任务
    private Runnable target;

Thread中关系到线程运行状态的几个常用方法:

(1)start方法:启动一个创建好的线程,当调用此方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

(2)run方法:无需用户调用,通过start方法启动线程后,线程处于就绪状态并获得CPU执行时间后,自动进入run方法执行具体任务。

(3)sleep方法:让线程交出CPU,让CPU去执行其他任务。sleep方法有两个版本:

sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds)//第一参数为毫秒,第二个参数为纳秒

注意点:sleep不会主动释放锁,当线程持有某个对象的锁时,即使调用sleep方法,其他线程也无法访问此对象。具体参照的代码:

private class SleepThread extends Thread {
        private String tag;
        public void setTag(String tag) {
            this.tag = tag;
        }

        @Override
        public void run() {
            super.run();
            Log.e("SleepThread", tag + " start running");
            synchronized (lock) {
                try {
                    Log.e("SleepThread", tag + " sleeping now");
                    Thread.sleep(3000);
                } catch (Exception e) {
                }
                Log.e("SleepThread", tag + " sleeping end");
            }
        }
    }
private void threadSleep() {
        SleepThread s1 = new SleepThread();
        s1.setTag("Sleep-001");
        s1.start();
        SleepThread s2 = new SleepThread();
        s2.setTag("Sleep-002");
        s2.start();
    }

执行结果:

E/SleepThread: Sleep-001 start running
E/SleepThread: Sleep-001 sleeping now
E/SleepThread: Sleep-002 start running
E/SleepThread: Sleep-001 sleeping end
E/SleepThread: Sleep-002 sleeping now
E/SleepThread: Sleep-002 sleeping end

s1和s2都执行start方法,s1持有对象lock的锁,虽然s1调用sleep处于休眠状态,但是s1没有释放锁对象lock,所以s2没有执行操作,等s1执行完sleep并释放lock对象后,s2才开始执行。

(4)yield方法:跟sleep方法一样,调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程,跟调用yield方法同级或更高优先级的线程有更大概率抢的CPU,yield不能控制交出CPU的时间,调用yield方法线程会回到准备就绪状态,而不是像sleep方法一样阻塞线程。yield和sleep一样不会主动释放持有的锁对象。

(5)join方法:线程调用join方法后,主线程处于阻塞等待状态,等待此线程执行完毕(调用无参join方法)或指定时间(调用指定时间参数的join方法)后继续执行。

join()
join(long millis)//参数为毫秒
join(long millis,int nanoseconds)//第一参数为毫秒,第二个参数为纳秒

join方法实际调用Object的wait方法:

public final void join(long millis)
    throws InterruptedException {
        synchronized(lock) {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                lock.wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
        }
    }

wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。子线程调用join方法实际调用Object的wait方法,持有Object的主线程进入阻塞状态,等待子线程执行完毕。

(6)interrupt方法:中断一个处于阻塞状态的线程,处于运行状态的线程无法被interrupt方法中断,所以线程实际使用中最好设置标记tag让线程在需要的情况下可以被中断。

private RunThread r1;

private void startRunThread() {
        r1 = new RunThread();
        r1.start();
    }

private void interruptThread() {
        if(r1 != null) {
            r1.setRun(false);
            r1.interrupt();
        }
    }

private class RunThread extends Thread {

        private boolean run = true;

        public void setRun(boolean run) {
            this.run = run;
        }

        @Override
        public void run() {
            super.run();
            try {
                while(run) {
                    Log.e("RunThread", "while-running--");
                }
            } catch (Exception e) {
                Log.e("RunThread", "run-error");
            }
        }
    }

例子中有一个boolean参数run,当为true时while循环正常执行,此时无法被interrupt中断,调用interruptThread方法时将run的值设为false,while循环不执行,此时线程可被interrupt方法中断。

(7)stop方法:stop方法已经是一个废弃的且不安全的方法。因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。

(8)destroy方法:废弃的不安全方法,会抛出UnsupportedOperationException异常。

Thread中属性方法:

getId:获取线程ID

getName和setName:线程名称

getPriority和setPriority:线程优先级

currentThread:获取当前线程

setDaemon和isDaemon:设置和判断守护线程

守护线程和用户线程:守护线程依赖于创建它的线程,而用户线程则不依赖。如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

三,synchronize同步代码块

为了解决线程之间的安全问题,java引入synchronize同步锁。当多个线程都要去判断同步锁时,耗费资源又降低程序的运行效率。当一个线程取得某个锁的引用时,其他线程不能引用这个锁,除非正在引用锁的线程主动释放这个锁。

如果多个线程需要调用同一个同步锁对象,锁必须独立于所有需要调用它的线程的内存之外,而不能创建于其中的某个线程之内,否则失去同步锁的意义。

Lock接口:如果用户需要主动加锁和解锁,JDK5以后提供了Lock接口和它的ReentrantLock子类,用户可以收到加解锁。

参考此网址的例子可以明白如何加解锁:https://blog.csdn.net/grey_mouse/article/details/84193844

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class RunThread implements Runnable{
    
    int runtag = 100;
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();//加锁
            if (runtag > 0) {
                    try {
                        Thread.sleep(1000);//模拟网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.e("RunThread", "runtag");
                    runtag --;
                    lock.unlock();//解锁
                }else {
                    break;
                }
        }
    }
}

四,死锁问题:死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

参考:https://www.cnblogs.com/xiaoxi/p/8311034.html

Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了。导致了死锁。这是最容易理解也是最简单的死锁的形式。

产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。另一个原因是默认的锁申请操作是阻塞的

从上面的网址摘抄一个例子:

public class DeadLock implements Runnable{
    
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    private boolean flag;
    
    public DeadLock(boolean flag){
        this.flag = flag;
    }
    
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "运行");
        
        if(flag){
            synchronized(obj1){
                System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                synchronized(obj2){
                    // 执行不到这里
                    System.out.println("1秒钟后,"+Thread.currentThread().getName()
                                + "锁住obj2");
                }
            }
        }else{
            synchronized(obj2){
                System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                synchronized(obj1){
                    // 执行不到这里
                    System.out.println("1秒钟后,"+Thread.currentThread().getName()
                                + "锁住obj1");
                }
            }
        }
    }

}

public class DeadLockTest {

     public static void main(String[] args) {
         
         Thread t1 = new Thread(new DeadLock(true), "线程1");
         Thread t2 = new Thread(new DeadLock(false), "线程2");

         t1.start();
         t2.start();
    }
}

//运行结果
线程1运行
线程1已经锁住obj1
线程2运行
线程2已经锁住obj2

线程1锁住了obj1(甲占有桥的一部分资源),线程2锁住了obj2(乙占有桥的一部分资源),线程1企图锁住obj2(甲让乙退出桥面,乙不从),进入阻塞,线程2企图锁住obj1(乙让甲退出桥面,甲不从),进入阻塞,死锁了。 从这个例子也可以反映出,死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的。

避免死锁的方法:

(1)线程按照一定的顺序加锁,还是上面网址摘抄的例子:

public class SyncThread1 implements Runnable{

    private Object obj1;
    private Object obj2;
 
    public SyncThread1(Object o1, Object o2){
        this.obj1=o1;
        this.obj2=o2;
    }
    
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (obj1) {
            System.out.println(name + " acquired lock on "+obj1);
            work();
        }
        System.out.println(name + " released lock on "+obj1);
        synchronized(obj2){
            System.out.println("After, "+ name + " acquired lock on "+obj2);
            work();
        }
        System.out.println(name + " released lock on "+obj2);
        System.out.println(name + " finished execution.");
    }
    
    private void work() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadDeadTest1 {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
 
        Thread t1 = new Thread(new SyncThread1(obj1, obj2), "t1");
        Thread t2 = new Thread(new SyncThread1(obj2, obj3), "t2");
        Thread t3 = new Thread(new SyncThread1(obj3, obj1), "t3");
 
        t1.start();
        Thread.sleep(1000);
        t2.start();
        Thread.sleep(1000);
        t3.start();
 
    }
}

//运行结果
t1 acquired lock on java.lang.Object@60e128
t2 acquired lock on java.lang.Object@18b3364
t3 acquired lock on java.lang.Object@76fba0
t1 released lock on java.lang.Object@60e128
t2 released lock on java.lang.Object@18b3364
After, t1 acquired lock on java.lang.Object@18b3364
t3 released lock on java.lang.Object@76fba0
After, t2 acquired lock on java.lang.Object@76fba0
After, t3 acquired lock on java.lang.Object@60e128
t1 released lock on java.lang.Object@18b3364
t1 finished execution.
t2 released lock on java.lang.Object@76fba0
t3 released lock on java.lang.Object@60e128
t3 finished execution.
t2 finished execution.

从结果中看,没有出现死锁的局面。因为在run()方法中,不存在嵌套封锁。

避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。

(2)设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。

(3)主动设置加锁实现,通过Lock和ReentrantLock类的tryLock主动设置超时时限,超时后主动返回失败信息,使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间,这样就打破了死锁的闭环。

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值