java基础之线程

线程的生命周期

在这里插入图片描述

package xiancheng;

public class InvokeRun extends Thread{
    private int i;
    //重写run()方法,run()方法的方法体就是线程执行体

    @Override
    public void run() {
        for (;i<100;i++){
            //直接调用run()方法时,Thread的this.getName()返回的时该对象的名字
            //而不是当前线程的名字
            //使用Thread.currentTread().getName()总是获取当前线程的名字
            System.out.println(Thread.currentThread().getName()+""+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            //调用Thread的currentThread()方法获取当前线程
            System.out.println(Thread.currentThread().getName()+""+i);
            if(i==20){
                //直接调用线程对象的run()方法
                //系统会把线程对象当成普通对象,把run()方法当成普通方法
                //所以下面两行代码并不会启动两个线程,而是依次执行两个run()方法
                new InvokeRun().run();
                new InvokeRun().run();
            }
        }
    }
}

在这里插入图片描述

程序运行的结果是整个程序只有一个线程:主线程。还有一点如果直接调用线程对象的run()方法,则run()方法里不能直接通过getName()方法来获得当前线程的名字,而是需要使用Thread.currentThread()方法获取当前线程,再调用线程对象的getName()方法来获得线程的名字。
启动线程的正确方法是调用Thread对象的start()方法,而不是直接调用run()方法,否则就变成单线程程序了。

package xiancheng;

public class InvokeRun extends Thread{
    private int i;
    //重写run()方法,run()方法的方法体就是线程执行体
    @Override
    public void run() {
        for (;i<100;i++){
            System.out.println(getName()+""+i);
        }
    }

    public static void main(String[] args) {
        //创建线程对象
        InvokeRun sd= new InvokeRun();
        for(int i=0;i<300;i++){
            //调用Thread的currentThread()方法获取当前线程
            System.out.println(Thread.currentThread().getName()+""+i);
            if(i==20){
                //启动线程
                sd.start();
                //判断启动后线程的isAlive()的值,输出true
                System.out.println(sd.isAlive());
            }
            //当线程处于新建,死亡两种状态时,isAlive()方法返回false
            //当i>20时,该线程肯定已经启动过了,如果sd.isAlive()为假时
            //那就是死亡状态
            if(i>20&&!sd.isAlive()){
                //试图再次启动该线程
                sd.start();
            }
        }
    }
}

在这里插入图片描述
上面的程序为线程死亡,线程死亡的情况下不能调用start方法启动线程,因为i>20,线程已经启动过了,也就是运行后过一段时间就会进入死亡状态。

join线程

Thread提供了让一个线程等待另一个线程完成的方法–join()方法。当某个程序执行流中调用其他线程的join()方法时,调用程序将被阻塞,直到被join()方法加入的join线程执行完为止。

package xiancheng;

public class JoinThread  extends Thread{
    //提供一个有参数的构造器,用于设置该线程的名字
    public JoinThread(String name){
        super(name);
    }
    //重写run()方法,定义线程执行体
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) throws Exception{
        //启动子线程
        new JoinThread("新线程").start();
        for(int i=0;i<100;i++){
            if(i==20){
                JoinThread jt = new JoinThread("被join的线程");
                jt.start();
                //main线程调用了jt线程的join()方法,main线程
                //必须等jt执行结束才会向下执行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+"主线程 "+i);
        }
    }
}

在这里插入图片描述
上面程序一共有三个线程,主方法开始时就启动了名为“新线程”的子线程,该子线程将会和main线程并发执行。当主线程的循环变量i等于20时,启动了名为“被Join的线程”的线程,该线程不会和main线程并发执行,main线程必须等该线程执行结束后才可以向下执行。在名为“被join的线程”的线程执行时,实际上只有两个子线程并发执行,而主线程处于等待状态。
因为主线程执行到join,所以就要等待被join的那个线程。在java里,main方法里就是主线程。Android的话一般自己不new一个新线程的话基本上都是主线程。

线程同步

线程安全问题:银行取钱例子
账户类

package xiancheng;

public class Account {
    //封装账号编号,账户余额的两个成员变量
    private String accountNo;
    private double balance;
    public Account(){

    }
    //构造器
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }

    @Override
    public int hashCode() {
        return accountNo.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) {
            return true;
        }
            if(obj!=null&&obj.getClass()==Account.class){
                Account target = (Account) obj;
                return target.getAccountNo().equals(accountNo);
            }

        return false;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

取钱的线程类

package xiancheng;

public class DrawThread extends Thread{
    //模拟用户账户
    private Account account;
    //当前取钱线程所希望取的钱数
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
    //当多个线程修改同一个共享数据时,将涉及数据安全问题
    public void run(){
        //账户余额大于取钱数目
        if(account.getBalance()>=drawAmount){
            //吐出钞票
            System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //修改余额
            account.setBalance(account.getBalance()-drawAmount);
            System.out.println("\t余额为:"+account.getBalance());
        }else {
            System.out.println(getName()+"取钱失败!余额不足!");
        }

    }
}

测试类

package xiancheng;

public class DrawTest {
    public static void main(String[] args) {
        //创建一个账户
        Account account = new Account("1234567", 1000);
        //模拟两个线程对同一个账户取钱
        new DrawThread("甲",account,800).start();
        new DrawThread("乙",account,800).start();
    }
}

在这里插入图片描述
问题出现了,余额出现负数,这不是银行希望出现的结果,程序中有两个并发线程在修改Account对象,而且系统在睡眠1s的时候切换到另一个线程去修改。这样不具有同步安全性。

同步代码块

package xiancheng;

public class DrawThread extends Thread{
    //模拟用户账户
    private Account account;
    //当前取钱线程所希望取的钱数
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
    //当多个线程修改同一个共享数据时,将涉及数据安全问题
    public void run(){
        //使用account作为同步监视器,任何线程进入下面同步代码块之前
        //必须先获得对account账户的锁定--其他线程无法获得锁,也就无法修改它
        //这种做法符合:“加锁-修改-释放锁”的逻辑
        synchronized (account) {
            //账户余额大于取钱数目
            if (account.getBalance() >= drawAmount) {
                //吐出钞票
                System.out.println(getName() + "取钱成功!吐出钞票:" + drawAmount);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改余额
                account.setBalance(account.getBalance() - drawAmount);
                System.out.println("\t余额为:" + account.getBalance());
            } else {
                System.out.println(getName() + "取钱失败!余额不足!");
            }
        }
        //同步代码块结束,该线程释放同步锁
    }
}

在这里插入图片描述
为了解决这个问题,java多线程引入同步监听器,使用同步监听器的通用方法就是同步代码块。同步代码块的语法格式如下:

synchronized(obj){
...
//此处的代码就是同步代码块
}

上面语法格式中,synchronized后的括号里的obj就是同步监听器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监听器的锁定。
虽然java程序允许使用任何对象作为同步监听器,但想一下同步监听器的目的:阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监听器。对于上面的取钱程序,考虑是用账户(Account)作为同步监听器。

同步方法

对于synchronized修饰的实例方法,无需显示指定同步监听器,同步方法的同步监听器是this,也就是调用该方法的对象。
根据之前的取钱程序修改一下

package xiancheng;

public class Account {
    //封装账号编号,账户余额的两个成员变量
    private String accountNo;
    private double balance;
    public Account(){

    }
    //构造器
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }

    public synchronized void draw(double drawAmount){
        //账户余额大于取钱数目
        if (balance >= drawAmount) {
            //吐出钞票
            System.out.println(Thread.currentThread().getName()  + "取钱成功!吐出钞票:" + drawAmount);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //修改余额
            balance -= drawAmount;
            System.out.println("\t余额为:" + balance);
        } else {
           // System.out.println(getName() + "取钱失败!余额不足!");
            //该方法不是run方法,所以程序不认识getName()只能使用Thread.currentThread().getName()
            System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
        }
        }

    @Override
    public int hashCode() {
        return accountNo.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) {
            return true;
        }
            if(obj!=null&&obj.getClass()==Account.class){
                Account target = (Account) obj;
                return target.getAccountNo().equals(accountNo);
            }

        return false;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

package xiancheng;

public class DrawThread extends Thread{
    //模拟用户账户
    private Account account;
    //当前取钱线程所希望取的钱数
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
    //当多个线程修改同一个共享数据时,将涉及数据安全问题
    public void run(){
        //直接调用account对象的draw()方法来执行取钱操作
        //同步方法的同步监听器是this,this代表调用draw()方法的对象
        //也就是说,线程进入draw()方法之前,必须先对account对象加锁
        account.draw(drawAmount);
    }
}

上面程序增加了一个代表取钱的draw()方法,并使用synchronized关键字修饰该方法,把该方法变成同步方法,该同步方法的同步监听器是this,因此对于同一个Account账户而言,任何时刻只能有一个线程获得Account对象的锁定,然后进入draw()方法执行取钱操作–这样也可以保证多个线程并发取钱的线程安全。
由于已经使用synchronized关键字修饰了draw()方法,同步方法的同步监听器是this,而this总代表调用该方法的对象–在上面示例中,调用draw()方法的对象是account,因此多个线程并发修改一份account之前,必须先对account对象加锁。这也符合"加锁-修改-释放锁“的逻辑。

同步锁

在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。使用该lock对象可以显示地加锁,释放锁,通常使用ReentranLock的代码格式如下:

class X{

//定义锁对象
ReentranLock lock=new ReentrantLock();
//...
//定义需要保证线程安全
public void m(){
//加锁
lock.lock();
try
{
//需要保证线程安全的代码
//...method body
}
//使用finally块来保证释放锁
{
lock.unlock();}

}
package xiancheng;

import java.util.concurrent.locks.ReentrantLock;

public class Account {
    //定义锁对象
    private final ReentrantLock lock=new ReentrantLock();
    //封装账号编号,账户余额的两个成员变量
    private String accountNo;
    private double balance;
    public Account(){

    }
    //构造器
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }

    public void draw(double drawAmount) {
        //加锁
        lock.lock();
        try {
            //账户余额大于取钱数目
            if (balance >= drawAmount) {
                //吐出钞票
                System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改余额
                balance -= drawAmount;
                System.out.println("\t余额为:" + balance);
            } else {
                // System.out.println(getName() + "取钱失败!余额不足!");
                //该方法不是run方法,所以程序不认识getName()只能使用Thread.currentThread().getName()
                System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
            }
        }
        finally {
            //修改完成,释放锁
            lock.unlock();
        }
    }

    @Override
    public int hashCode() {
        return accountNo.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) {
            return true;
        }
            if(obj!=null&&obj.getClass()==Account.class){
                Account target = (Account) obj;
                return target.getAccountNo().equals(accountNo);
            }

        return false;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

上面程序定义了一个ReentrantLock对象,程序中实现draw()方法时,进入方法开始执行后立即请求对ReentrantLock对象进行加锁,当执行完draw()后的取钱逻辑后,程序使用finally块来确保释放锁。

死锁

当两个线程相互等待对方释放同步监听器时就会发生死锁

package xiancheng;
class A{
    public synchronized void foo(B b){
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了A实例的foo()方法");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B实例的last()方法");//3号
            b.last();
    }
    public synchronized void last(){
        System.out.println("进入了A类的last()方法");
    }
}
class B{
    public synchronized void bar(A a){
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了B实例的bar()方法");
        try {
            Thread.sleep(200);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A实例的last()方法");//4号
        a.last();
    }
    public synchronized void last(){
        System.out.println("进入了B类的last()方法内部");
    }

}

public class DeadLock implements Runnable{
    A a=new A();
    B b=new B();


    @Override
    public void run() {
     Thread.currentThread().setName("副线程");
     //调用b对象的bar()方法
        b.bar(a);
        System.out.println("进入了副线程之后");
    }
    public void init() {

        Thread.currentThread().setName("主线程");
        //调用a对象的foo()方法
        a.foo(b);
        System.out.println("进入了主线程之后");
    }

    public static void main(String[] args) {
        DeadLock d1 = new DeadLock();
        //以d1为target启动新线程
        new Thread(d1).start();
        //调用init()方法
        d1.init();
    }
}

在这里插入图片描述
主要发生在last()方法,直到3号代码处希望调用B对象的last()方法–执行该方法之前必须先对B对象加锁,但此时副线程正保持着B对象的锁,所以主线程阻塞;接下来副线程应该也醒过来继续向下执行,直到4号代码处希望调用A对象的last()方法–执行该方法之前必须先对A对象加锁,但此时主线程没有释放A对象的锁–但此时主线程没有释放A对象的锁–至此,就出现了主线程保持着A对象的锁,等待B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁,两个线程互相等待

线程通信

假设现在系统中有两个线程,这两个线程分别代表存款者和取钱着–现在假设有一种特殊的要求,系统要求存款者和取钱者不断重复存款,取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不允许取钱者两次取钱。
为了实现这种功能,可以借助Object类提供的wait(),notify()和notifyAll()三个方法,这三个方法不属于Thread类,属于Object类。但这三个方法必须由同步监听器对象来调用。
对于使用synchronized修饰的同步方法,因为本身所在类的默认实例(this)就是同步监听器,所以可以直接在同步方法中调用这三个方法。
对于使用synchronized修饰的同步代码块,同步监听器是synchronized后括号里的对象,所以必须使用该对象调用
wait():导致当前线程等待,直到有notify方法或notifyAll()方法来唤醒该线程。调用wait方法的当前线程会释放对该同步监听器的锁定。
notify():唤醒此同步监听器上等待的单个线程。只有当前线程释放对该同步监听器的锁定,才可以执行被唤醒的线程,也就是执行到wait方法
notifyAll():唤醒在此同步监听器上等待所有的线程。只有当前线程释放对该同步监听器的锁定,才可以执行被唤醒的线程,也就是执行到wait方法

package xiancheng;


public class Account {

    private String accountNo;
    private double balance;
    //标识账户中是否已有存款的旗标
    private boolean flag=false;
    public Account(){

    }
    //构造器
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }

    //因为账户余额不允许随便修改,所以只为balance提供getter方法
    public double getBalance(){
        return this.balance;
    }
    public synchronized void draw(double drawAmount) {
        try {
            //如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
            if(!flag){
                wait();
            }
            else {
                //执行取钱操作
                System.out.println(Thread.currentThread().getName() + "取钱" + drawAmount);
                balance -= drawAmount;
                System.out.println("账户余额为:" + balance);
                //将标识账户是否已有存款的旗帜设为false
                flag=false;
                //唤醒其他线程
                notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void deposit(double depositAmount){
        try {
            //如果flag为真,表明账户中已有人存钱进去,存钱方法阻塞
            if(flag){
                wait();
            }else {
                //执行存款操作
                System.out.println(Thread.currentThread().getName() + "存款" + depositAmount);
                balance+=depositAmount;
                System.out.println("账户余额为:"+balance);
                //将表示账户是否已有存款的旗标设为true
                flag=true;
                //唤醒其他线程
                notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public int hashCode() {
        return accountNo.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) {
            return true;
        }
            if(obj!=null&&obj.getClass()==Account.class){
                Account target = (Account) obj;
                return target.getAccountNo().equals(accountNo);
            }

        return false;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }



    public void setBalance(double balance) {
        this.balance = balance;
    }
}

package xiancheng;

public class DrawThread extends Thread{
    //模拟用户账户
    private Account account;
    //当前取钱线程所希望取的钱数
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
    //重复100次执行取钱操作
    @Override
    public void run() {
       for(int i=0;i<2;i++){
           account.draw(drawAmount);
       }
    }
}

package xiancheng;

public class DepositThread extends Thread{
    //模拟用户账户
    private Account account;
    //当前存款线程所希望存的钱数
    private double depositAmount;
    public DepositThread(String name,Account account,double depositAmount){
        super(name);
        this.account=account;
        this.depositAmount=depositAmount;
    }
    //重复100次执行存款操作

    @Override
    public void run() {
        for(int i=0;i<2;i++){
            account.deposit(depositAmount);
        }
    }
}

package xiancheng;

public class DrawTest {
    public static void main(String[] args) {
        //创建一个账户
        Account acc = new Account("1234567", 0);
        //取钱线程
        new DrawThread("取钱者",acc,800).start();
        //存钱线程
        new DepositThread("存款者甲",acc,800).start();
        new DepositThread("存款者乙",acc,800).start();
        new DepositThread("存款者丙",acc,800).start();

    }
}

在这里插入图片描述
因为在上面程序中,同步监听器对象都是account(因为调用同步方法中的this是调用方法的对象,都是account)。所以在notifyAll时不仅会唤醒所在同步方法里的线程,也会唤醒另一个同步方法的线程。

package xiancheng;

public class TestMain {
    //对象锁一
    private static Object lock1 = new Object();
    //对象锁二
    private static Object lock2 = new Object();

    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    System.out.println("线程一拿到了lock1锁");
                    System.out.println("线程一准备获取lock2锁");
                    synchronized (lock2) {
                        System.out.println("线程一拿到了lock2锁");
                        try {
                            System.out.println("线程一释放了lock1锁");
                            //先让出lock1锁,不设置超时时间
                            lock1.wait();
                            //唤醒lock1等待的线程
                            lock1.notify();
                            System.out.println("线程一运行结束");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //睡眠一秒,让线程一能够成功运行到wait()方法,释放lock1锁
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("线程二拿到了lock1锁,开始运行");
                    System.out.println("线程二准备获取lock2锁");
                    //唤醒lock1等待的线程
                    lock1.notify();
                    try {
                        //先让出lock1锁,不设置超时时间
                        lock1.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lock2) {
                        System.out.println("线程二拿到了lock2锁,开始运行");
                        System.out.println("线程二运行结束");
                    }
                }
            }
        });
        thread1.start();
        thread2.start();
    }

}

在这里插入图片描述
可以看到,线程一和线程二都能够顺利执行结束了。其实本质上就是让他们互相协作,完成工作。wait方法只会释放当前对象的锁,不会释放所有锁。thread2中有句"线程二准备获取lock2锁",但这时候lock2锁是被线程一持有着的,而线程一其实只让出了lock1锁,同时处于wait状态而不是等待着锁的阻塞,所以这时候需要线程二lock1.notify()方法,把线程一唤醒,让它继续执行,只有线程一继续执行结束后,线程二才能拿到lock2锁,才能进入lock2同步代码块执行(在同步方法synchronized执行结束后,也就会自动释放锁)。
还要需要注意的一点是,wait方法释放锁后,只有在等待锁处于阻塞状态的线程可以继续执行(也就是想要进入synchronized的线程),而那些处于wait状态的线程,是需要被唤醒,才能继续执行的,这也是为什么线程二要调用notify方法的原因了。

当你在所在的Activity开启了一个后台线程,线程在执行过程中要遇到死亡或阻塞条件才结束,所以当你Activity销毁后,但是线程还存活的话会执行直到死亡

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值