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,线程已经启动过了,也就是运行后过一段时间就会进入死亡状态。
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销毁后,但是线程还存活的话会执行直到死亡