java如何实现多线程(下)

174 阅读6分钟

(1)死锁以及如何避免死锁

死锁:

当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。

一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

例子:

小红和小明都想画画,小红有画板,小明有画笔,为了可以画画,他们必须同时拥有画板和画笔。

image.png

运行结果:

通过运行结果得知,他们两个同时进入的堵塞的状态

image.png

如何避免:

  1. 尽量不要使用锁的嵌套
  2. 不要使用锁,而使用安全类。java.util.concurrent
  3. 设置锁的超时时间,Lock锁,到达指定时间没有获取锁,则不会再等待该锁。

(2)线程通信

在Java中,线程之间可以通过wait()和notify()方法进行通信。wait()方法使当前线程进入等待状态,直到其他线程调用notify()方法来唤醒它。wait()方法必须在synchronized块中使用,因为它只有在获得对象的锁时才能被调用。

等待:

public final void wait()

public final void wait(long timeout)

必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其拥有的锁标记,并且进入等待队列,只有被notify()唤醒时才会进入就绪状态进行抢夺时间片。

唤醒:

public final void notify()

public final void notifyAll()

线程之间可以通过wait()和notify()方法进行通信。notify()方法用于随机唤醒一个等待中的线程,使其从wait()方法中返回,并开始竞争时间片。而notifyAll()方法用于唤醒所有等待中的线程,使其从wait()方法中返回。并开始竞争时间片

示例:

package communication;

/**
 * 银行卡
 */
public class BankCard  {
//余额
   private double balance;
   //是否够一次转账
   private boolean flag;//true表示有钱,false表示没钱
//存
public synchronized void save(double money){
   if(flag){
      try {
         //如果有钱就进入等待队列
         wait();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   //没钱就去增加钱
   balance=money+balance;
   System.out.println("往卡里存入了1000元,卡中余额"+balance+"元");
   flag=true;
   //唤醒等待队列的线程
   notify();
   }
   //取
   public synchronized void take(double money){
   if(flag==false){
      try {
         //如果没钱就进入等待队列
         wait();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   //有钱就去花钱
      balance=balance-money;
      System.out.println("从卡中取出了1000元,卡中余额"+balance+"元");
      flag=false;
      //唤醒等待队列的线程
      notify();

   }


}
package communication;

/**
 * 存钱
 */
public class Save implements Runnable{
//通信中间表
    private BankCard bankCard;

    public Save(BankCard bank){
        bankCard=bank;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            bankCard.save(1000);
        }
    }
}
package communication;

/**
 * 取钱
 */
public class Take implements Runnable{

    private BankCard bankCard;
    public Take(BankCard bank){
        bankCard=bank;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            bankCard.take(1000);
        }
    }
}
package communication;

public class Test {
    public static void main(String[] args) {
        BankCard bankCard = new BankCard();

        Save save = new Save(bankCard);
        Take take = new Take(bankCard);

         Thread save1 = new Thread(save);
         Thread take2 = new Thread(take);
         save1.start();
         take2.start();
    }
}

wait sleep方法的区别

//1. wait需要使用notify或notifyAll唤醒,而sleep到时间自动唤醒。

//2. wait来自于Object类中,sleep来自Thread类中

//3. wait会是否锁资源,sleep不会释放锁资源。

//4. wait必须放在同步代码中,而sleep可以放在任意位置。

(3)线程的状态

NEW : 新建状态。当new一个线程对象时进入该状态;

RUNNABLE:运行状态,调用完start并获取时间片进入运行状态

BLOCKED:堵塞状态。当没有获取锁资源时,进入堵塞状态

WAITING:等待状态,当执行了wait方法时,该线程进入等待状态

TIOMED_WAITING:限时等待状态,当执行 sleep方法时,进入该状态。

TERMINATED:终止状态,当线程执行完毕获取出现异常中断的时候,进入限时等待状态

以红字状态为准 1685930324269.png

(4)线程池

为什么要使用线程池?

线程是宝贵的内存资源,单个线程约占位1MB的空间,过多分配容易造成内存溢出。

频繁的创建以及销毁线程会增加虚拟机回收频率,资源开销,造成程序性能下降

线程池的优点

线程容器,可以设定线程分配的数量上限。 将预先创建的线程对象存入池中,并重用线程池中的线程对象,避免频繁的创建和销毁。

将任务交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程。

image.png

如何创建jdk自带的线程池

  • 常用的线程池接口和类(所在包java.util.concurrent);

  • Executor :线程池的顶级接口,里面只有一个execute方法;void execute(Runnable command);,执行任务的方法

  • ExecutorService: 线程池接口,可通过submit(Runnable task)提交任务代码。他是Executor的子接口,里面包含了很多方法:

  • 1.shutdown():关闭线程池---当前线程池如果还有任务,则要等任务完成后才会关闭。

  • 2.shutdownNow():关闭线程池---这个是直接立刻关闭线程池,无论池子中有没有任务

  • 3.isShutdown():查看线程池是否处于关闭状态

  • 4.isTerminated():判断线程池是否已经终止。

  • 5.submit(Callable task): 执行线程任务。

    submit(Runnable task):

  • Executors工厂类: 通过此类可以获得一个线程池。

    1--3都使用submit执行线程任务,4-使用schedule定时执行任务

    线程池的种类:

    1.获取固定长度的线程池:

    Executors.newFixedThreadPool(5);

    2.创建单一的线程池,池子中只有一个线程的对象,适合任务有序执行:

    Executors.newSingleThreadExecutor();

    3.可变线程池:

    Executors.newCachedThreadPool();

    4.延迟线程池:

    Executors.newScheduledThreadPool(5);

//延迟线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

        for (int i = 0; i < 100; i++) {
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("10秒后执行");
                }
            },10,TimeUnit.SECONDS);
        }

如何创建自定义线程池

线程池中参数的意思:

corePoolSize---->核心线程数。

maximumPoolSize---->最大线程数。

keepAliveTime---->等待时长。

unit ---->等待时长单位.

workQueue----> 等待队列对象类型为BlockQueue,想要设置有多少等待队列的数量,需要用到BlockQueue的实现类,在实现类中接入参数,然后传入自定义线程池。(设置可以处于等待状态的对象有几个)


    //自定义线程池
    ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(5);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, arrayBlockingQueue);

for (int i = 0; i < 16; i++) {
    int e = i;
    threadPoolExecutor.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"跑到了"+e);
        }
    });
}

实现Callable创建多线程---第三种

Callable和runnable的区别 callable任务体有返回值。

callable自动抛出异常

callable是runnable的补充

   //Callable创建多线程
public class MyCallable implements Callable<Double> {

   @Override
   public Double call() throws Exception {
       double e =0;
       for (int i = 0; i < 100; i++) {
           e += i;
       }
       return e;
   }
}
 //测试
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        //单一线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<Double> submit = executorService.submit(myCallable);
        System.out.println(submit.get());
    }
}

(5)手动锁和自动锁的区别

1.synchronized可以给代码块和方法上锁,而lock只能给代码块加锁。

2.synchronized自动上锁和解锁,而lock需要手动上锁和解锁。

3.lock可以知道是否获取锁资源。

获取手动锁需要自己new

Lock lock=new ReentrantLock();

lock.lock();//上锁

lock.unlock();//释放锁

lock.tryLock(10, TimeUnit.SECONDS); //时间一到,自动解锁,释放锁资源

public  class MyLock implements  Runnable{
    private int tickets = 100;
     Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
                lock.lock();//上锁
            try {
                if (tickets > 0) {
                    tickets--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩票数" + tickets);
                } else {
                    break;
                }
            }finally {
                lock.unlock();//释放锁
            }
        }
    }
}
public  class MyLock implements  Runnable{
    private int tickets = 100;
     Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                lock.tryLock(10, TimeUnit.SECONDS); //时间一到,自动解锁,释放锁资源
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tickets > 0) {
                    tickets--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩票数" + tickets);
                } else {
                    break;
                }

        }
    }
}