Java多线程详解

# 1线程的创建和启动

1.1thread类创建线程类

package ThreadLearnDemo01;

public class FirstThread extends Thread{
    private int i;
    public void run(){
        for(;i<100;i++)
        {
            System.out.println(this.getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            if (i == 30) {
                new FirstThread().start();
                new FirstThread().start();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

1.2runnable接口创建线程类

package ThreadLearnDemo01;

public class FirstThread extends Thread{
    private int i;
    public void run(){
        for(;i<100;i++)
        {
            System.out.println(this.getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            if (i == 30) {
                new FirstThread().start();
                new FirstThread().start();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

在运行的时候出现了一种奇怪的现象。
Java多线程详解_第1张图片
从整体上看,两个线程都是用同一个target firstthread来初始构造的,共享这一个target的资源,但是在运行过程中出现了乱序,这是一种不安全的线程。

1.3使用Callable和Future创建线程

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadLearnDemo02 {
    public static void main(String[] args) {
        //Callable是runable接口的升级版,提供一个call()方法可以作为线程执行体,
        //call可以有返回值,可以抛出异常
        //用lamdba创建一个Callable的无参构造
        //try catch会一直等到start的线程执行结束
        FutureTask<Integer> task=new FutureTask<Integer>((Callable <Integer>)()->{
            int j=0;
            for(;j<100;j++)
            {
                System.out.println(Thread.currentThread().getName()+" "+j);
            }
            return j;
        });
        int i=0;
        for(;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==3)
            {
                new Thread(task, "Callable创建的线程").start();
            }
        }
        try{
            System.out.println("子线程返回值"+task.get());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}

2线程的生命周期

2.1新建和就绪状态

new出来的线程是新建状态,该状态和其他的java对象一样,没有表现出任何线程的动态特征。
调用start()方法之后,java虚拟机会为其创建方法调用栈和程序计数器,线程何时开始运行,去决议jvm里线程调度器的调度。

package ThreadLearnDemo03;

public class ThreadLearnDemo03 extends Thread{
    private int i;
    public void run(){
        for(;i<100;i++)
        {
            //this.getName()返回的是该对象的名字
            //不是当前线程的名字
            //使用Thread.currentThread().getName()返回的是该线程的名字
            System.out.println(Thread.currentThread().getName()+" "+i+this.getName());
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++)
        {
            //调用了run之后就不再处于新建状态了,不要再调用start
            System.out.println(Thread.currentThread().getName());
            if(i==50){
                new ThreadLearnDemo03().run();
                new ThreadLearnDemo03().run();
            }
        }
    }
}

2.2运行和阻塞状态

什么时候会进入阻塞状态:

  • 线程调用sleep()方法主动放弃所占用的处理器资源。
  • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
  • 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有的。
  • 线程在等待某个通知
  • 程序调用了现成的suspend方法将该线程挂起,但这个方法容易导致死锁,所以应该尽量避免使用

针对上面情况。发生如下特定情况可以解除上面的阻塞。

  • 调用sleep方法的线程经过了指定时间
  • 线程调用的阻塞式IO方法已经返回
  • 线程成功的获得了试图取得的同步监视器
  • 线程正在等待某个通知,其他线程发出了一个通知。
  • 处于挂起状态的县城被调用了resume()恢复方法。

Java多线程详解_第2张图片

2.3线程死亡

线程结束方式:

  • run()或call()方法执行完成,线程正常结束
  • 线程抛出一个未捕获的Exception或Error
  • 直接调用该线程的stop方法结束该线程——该方法容易导致死锁,不推荐使用

当主线程结束时,其他线程不受影响,并不会随之结束,一旦子线程启动起来后,它就拥有和主线程相同的地位,不受主线程的影响
测试线程是否死亡的方法:isAlive() 当处与新建和死亡两种状态时,该方法返回false。
不要对一个已经死亡的线程调用start或者run,死亡就是死亡,如果调用会引发IllegalThreadState Exception异常

3.控制线程

3.1join线程

package ThreadLearnDemo04;

public class ThreadLearnDemo04 extends Thread{
    public ThreadLearnDemo04(String name)
    {
        super(name);
    }
    public void run(){
        for(int i=0;i<100;i++)
        {
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new ThreadLearnDemo04("新线程").start();
        for(int i=0;i<100;i++)
        {
            if(i==20)
            {
                ThreadLearnDemo04 threadLearnDemo04  = new ThreadLearnDemo04("被join的线程");
                threadLearnDemo04.start();
                threadLearnDemo04.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

主线程经过join处于等待状态,新线程和被join线程并发执行。
主线程必须等待被join线程执行完之后才能继续执行。

3.2后台线程

后台线程的任务是为其它线程提供服务,如果前台线程都死亡,后台会自动死亡。使用setDaemon(true)将指定线程设置成后台线程。

package ThreadLearnDemo05;

public class ThreadLearnDemo05 extends Thread{
    public void run()
    {
        for(int i=0;i<1000;i++)
        {
            System.out.println(getName()+" "+i+" "+this.getName());
        }
    }

    public static void main(String[] args) {
        ThreadLearnDemo05 threadLearnDemo05 = new ThreadLearnDemo05();
        threadLearnDemo05.setDaemon(true);
        threadLearnDemo05.start();
        for(int i=0;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

在这个程序中,创建的子线程不会跑完

3.3线程睡眠sleep

staic void sleep(long millis)
static void sleep(long millis,int nanos)

package ThreadLearnDemo06;

import java.util.Date;

public class ThreadLearnDemo06 {
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<10;i++){
            System.out.println("当前时间:"+new Date());
            Thread.sleep(1000);
        }
    }
}

这个程序会每隔一秒输出一条语句

3.4线程让步:yield

不建议使用,通过分配给线程优先级,然后yield让系统线程调度器使所有线程处于就绪态,重新调度一次,在多CUP机器效果不明显。

3.5改变线程优先级

package com.luojias.threadlearn.demo01;

public class Demo01 extends Thread{
    public Demo01(String name)
    {
        super(name);
    }
    public void run()
    {
        for(int i=0;i<1000;i++)
        {
            System.out.println(getName()+" "+getPriority()+" "+" "+i);
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setPriority(6);
        for(int i=0;i<30;i++)
        {
            if(i==10)
            {
                Demo01 low = new Demo01("低级");
                low.start();
                System.out.println("低级创建时候的优先级"+low.getPriority());
                low.setPriority(MIN_PRIORITY);
            }
            if(i==20)
            {
                Demo01 high=new Demo01("高级");
                System.out.println("高级创建时候的优先级"+high.getPriority());
                high.setPriority(MAX_PRIORITY);
                high.start();
            }
        }
    }
}

输出图:
开始:
Java多线程详解_第3张图片
最后:
Java多线程详解_第4张图片
得出如下结论:线程的默认优先级是主线程的优先级
优先度高的线程获得更多的执行机会。
另外需要注意:
由于操作系统不同,应该使用MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY三个静态常量来设置优先级

4.线程同步

4.1线程安全问题

银行取钱问题

package com.luojias.threadlearn.demo02;

public class Account {
    private String accountNo;
    private double balance;
    public Account(){}
    public Account (String accountNo,double balance)
    {
            this.accountNo=accountNo;
            this.balance=balance;
    }

    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;
    }
    //用hasCode判断账户编号是否相等
    public int hasCode()
    {
        return accountNo.hashCode();
    }
    //用equals判断账户是否相等
    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);//类相同时判断accountNo是否相同,就是这一步,因为两个相同对象的hascode必须相同,所以需要重写hashcode
        }
        return false;
    }
    //整个重写研究了好一会
}

package com.luojias.threadlearn.demo02;

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(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.setBalance(account.getBalance() - drawAmount);
            System.out.println("余额为:" + account.getBalance());
        } else {
            System.out.println("余额不足");
        }
    }
}

package com.luojias.threadlearn.demo02;

public class Demo02 {
    public static void main(String[] args) {
        Account acct=new Account("123456",1000);
        new DrawThread("甲",acct,900).start();
        new DrawThread("乙",acct,800).start();
    }
}

运行main函数后会出现各种错误,有的时候余额都是负数,有的时候都是整数,有的时候一正一负。

4.2同步代码块

为了解决上面出现的问题可以使用synchronized,也就是加锁操作

package com.luojias.threadlearn.demo02;

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() {
        synchronized (account) {
            if (account.getBalance() >= drawAmount) {
                System.out.println(getName() + "取钱成功吐出钞票:" + drawAmount);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance() - drawAmount);
                System.out.println("余额为:" + account.getBalance());
            } else {
                System.out.println("余额不足");
            }
        }
    }
}

这样有一个线程在修改的时候,account就会被锁住,别的线程,无法使用account,在修改完毕后,释放锁

4.3同步方法

同步方法能够方便地实现线程安全的类,具有以下特征

  • 该类的对象可以被多个线程安全的访问
  • 每个线程调用该对象的任一方法之后都将得到正确结果
  • 每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态

这里直接锁run是不能锁住的,因为每次run的是一个新的线程。
所以必须在共享的资源里加锁
把锁加在共享的Account解决问题

package com.luojias.threadlearn.demo02;

public class Account {
    private String accountNo;
    private double balance;
    public Account(){}
    public Account (String accountNo,double balance)
    {
            this.accountNo=accountNo;
            this.balance=balance;
    }

    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;
    }
    //用hasCode判断账户编号是否相等
    public int hasCode()
    {
        return accountNo.hashCode();
    }
    //用equals判断账户是否相等
    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);//类相同时判断accountNo是否相同,就是这一步,因为两个相同对象的hascode必须相同,所以需要重写hashcode
        }
        return false;
    }
    //整个重写研究了好一会
    public synchronized  void draw(double drawAmount) {
        if (balance >= drawAmount) {
            System.out.println( accountNo+ "取钱成功吐出钞票:" + drawAmount);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            setBalance(balance - drawAmount);
            System.out.println("余额为:" + balance);
        } else {
            System.out.println("余额不足");
        }
    }
}

4.4释放同步监视器的锁定

结束:

  • 方法或者代码块结束
  • 遇到return break终止了代码块方法
  • 出现了未处理的异常或者错误
  • 执行了wait方法,线程暂停

不会释放同步监视器的情况:

  • 调用sleep(),yield()暂停当前线程的执行。
  • 其他线程调用了该线程的suspend()方法将线程挂起,避免使用suspend()和resume()方法控制线程

4.5 同步锁(lock)

用lock可以设置时间。如果锁住的时间过长,然后释放

package com.luojias.threadlearn.demo02;

import java.util.concurrent.locks.ReentrantLock;

public class Account {
    private String accountNo;
    private final ReentrantLock lock=new ReentrantLock();
    private double balance;
    public Account(){}
    public Account (String accountNo,double balance)
    {
            this.accountNo=accountNo;
            this.balance=balance;
    }

    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;
    }
    //用hasCode判断账户编号是否相等
    public int hasCode()
    {
        return accountNo.hashCode();
    }
    //用equals判断账户是否相等
    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);//类相同时判断accountNo是否相同,就是这一步,因为两个相同对象的hascode必须相同,所以需要重写hashcode
        }
        return false;
    }
    //整个重写研究了好一会
    public synchronized  void draw(double drawAmount) {
        lock.lock();
        try {
            if (balance >= drawAmount) {
                System.out.println(accountNo + "取钱成功吐出钞票:" + drawAmount);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                setBalance(balance - drawAmount);
                System.out.println("余额为:" + balance);
            } else {
                System.out.println("余额不足");
            }
        }
        finally {
            lock.unlock();
        }
    }
}

4.6死锁

这是代码块都在等待对方释放资源,然后都无法继续进行的时候,称为死锁。

5线程通信

5.1传统的线程通信和condition控制的通信

两者用法基本相同,一个是调用wait notifyall notify另一个是调用await signal signalall
贴上测试代码,可能有点繁琐,其实可以把123三种方法写成同一个方法。

package com.luojias.threadlearn.demo03;

import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Account {
    private String accountNo;
    private double balance;
    private boolean flag=false;
    private final ReentrantLock lock= new ReentrantLock();
    private final Condition cond= lock.newCondition();
    //构造函数
    public Account(){ }
    public Account(String accountNo,double balance){this.accountNo=accountNo;this.balance=balance;}
    //get set方法
    public String getAccountNo() {
        return accountNo;
    }
    public double getBalance() {
        return balance;
    }
    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }
    //重写equals和hashCode函数
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return accountNo.equals(account.accountNo);
    }
    public int hashCode() {
        return Objects.hash(accountNo);
    }

    public void draw(double balance) {
        lock.lock();
        try {
            if (!flag) {
                cond.await();
            } else {
                System.out.println("取钱成功" + balance + " " + Thread.currentThread().getName());
                flag = false;
                balance = 0;
                cond.signalAll();
            }
        }catch(InterruptedException er){
            er.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
    public void deposit(double money) {
        lock.lock();
        try {
            if(flag){
                cond.await();
            }
            else{
                balance=money;
                flag=true;
                System.out.println("存钱成功1");
                cond.signalAll();
            }
        }catch (InterruptedException ex) {
            ex.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void deposit2(double money) {
        lock.lock();
        try {
            if(flag){
                cond.await();
            }
            else{
                balance=money;
                flag=true;
                System.out.println("存钱成功2");
                cond.signalAll();
            }
        }catch (InterruptedException ex) {
            ex.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void deposit3(double money) {
        lock.lock();
        try {
            if(flag){
                cond.await();
            }
            else{
                balance=money;
                flag=true;
                System.out.println("存钱成功3");
                cond.signal();
            }
        }catch (InterruptedException ex) {
            ex.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


package com.luojias.threadlearn.demo03;

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;
    }

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

package com.luojias.threadlearn.demo03;

public class DepositThread2 extends Thread{
    private Account account;
    private double depositAmount;
    public DepositThread2(String name,Account account,double depositAmount){
        super(name);
        this.account=account;
        this.depositAmount=depositAmount;
    }

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

package com.luojias.threadlearn.demo03;

public class DepositThread3 extends Thread{
    private Account account;
    private double depositAmount;
    public DepositThread3(String name,Account account,double depositAmount){
        super(name);
        this.account=account;
        this.depositAmount=depositAmount;
    }

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

package com.luojias.threadlearn.demo03;

public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.drawAmount=drawAmount;
        this.account=account;
    }

    @Override
    public void run() {
        for(int i=0;i<100;i++)
        {
            account.draw(drawAmount);
        }
    }
}

package com.luojias.threadlearn.demo03;

public class Test {
    public static void main(String[] args) {
        Account acct=new Account("1234567",0);
        new DrawThread("取钱者",acct,800).start();
        new DepositThread("存期者1",acct,800).start();
        new DepositThread2("存期者2",acct,800).start();
        new DepositThread3("存期者3",acct,800).start();

    }
}

5.2BlockingQuene控制线程通信

小demo

package com.luojias.threadlearn.demo04;

import java.util.concurrent.ArrayBlockingQueue;

public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> bq=new ArrayBlockingQueue<String>(2);
        bq.put("Java");//等同于bq.offer bq.add
        bq.put("Java");
        bq.put("Java");//在此处阻塞线程
        
    }
}

实例:

package com.luojias.threadlearn.demo05;

import java.util.concurrent.BlockingQueue;

public class Consumer extends Thread{
    private BlockingQueue<String>bq;
    public Consumer(BlockingQueue<String>bq){
        this.bq=bq;
    }
    public void run(){
        while (true){
            System.out.println(getName()+" 消费者准备消费集合");
            try {
                Thread.sleep(200);
                bq.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+"消费完成"+bq);
        }
    }
}

package com.luojias.threadlearn.demo05;

import java.util.concurrent.BlockingQueue;

public class Producer extends Thread{
    private BlockingQueue<String>bq;
    public Producer(BlockingQueue<String>bq){
        this.bq=bq;
    }
    public void run(){
        String []strArr=new String[]{
                "java","i","love"
        };
        for(int i=0;i<9999999;i++){
            System.out.println(getName()+"生产者准备生产集合元素");
            try {
                Thread.sleep(200);
                bq.put(strArr[i%3]);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+" 生产完成 "+bq);
        }
    }
}

package com.luojias.threadlearn.demo05;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueTest2 {
    public static void main(String[] args) {
        BlockingQueue<String>bq=new ArrayBlockingQueue<String>(1);
        new Producer(bq).start();
        new Producer(bq).start();
        new Producer(bq).start();
        new Consumer(bq).start();
    }
}

5.3线程池

未完待续

你可能感兴趣的:(java基础,java,开发语言)