-->

多线程

多线程笔记

线程简介

image


Process与Thread

  • 程序是指令和数据的有序集合,本身没有任何运行的含义,是一个静态的概念。
  • 进程(Process)则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位
  • 通常在一个进程中可以包含若干个线程(Thread),一个进程中至少有一个主线程(main)。线程是CPU调度和执行的单位

Tips:大多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,就有同时执行的错觉。


核心概念

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
  • main()称之为主线程,是系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由cpu安排调度,cpu是与操作系统紧密相关的,先后顺序不能人为干预
  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制
  • 线程会带来额外开销,如cpu调度时间,并发控制开销
  • 每个线程只能在自己的工作内存交互,内存控制不当会造成数据不一致。

线程创建

image

继承Thread类

image

package com.test.demo01;

//创建线程方式一:继承Thread类,重写run方法,调用start开启线程
//线程开启不一定立即执行,由CPU调度执行
public class TestThread extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for(int i=1;i<=10;i++){
            System.out.println("Thread:"+i);
        }
    }

    public static void main(String[] args) {
        //main线程,主线程

        //创建线程对象
        TestThread thread=new TestThread();

        //调用start方法开启线程
        thread.start();

        for(int i=1;i<=100;i++){
            System.out.println("main:"+i);
        }
    }
}

实现Runnable接口

image

package com.test.demo01;

//创建线程方式2 :实现Runnable接口,重写run方法,执行线程需要丢入Runnable接口实现类,调用start方法
public class TestRunnable implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for(int i=1;i<=10;i++){
            System.out.println("Thread:"+i);
        }
    }
    public static void main(String[] args) {
        //main线程,主线程y6

        //创建Runnable接口的实现类对象
        TestRunnable runnable=new TestRunnable();

//        //创建线程对象,通过线程对象开启线程,代理
//        Thread thread=new Thread(runnable);
//        //调用start方法开启线程
//
//        thread.start();

        new Thread(runnable).start();
        for(int i=1;i<=100;i++){
            System.out.println("main:"+i);
        }
    }
}

小结:

image


实现Callable接口

image

package com.test.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//练习 thread  ,实现多线程同步下载图片
public class TestCallable implements Callable<String> {
    private String url;  //图片地址
    private String name;  //保存的文件名  要加后缀名!

    public TestCallable(String url, String name) {
        this.url = url;
        this.name= name;
    }

    @Override
    public String call() {
        WebDownLoader webDownLoader=new WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下载了文件名为:"+name);
        return "下载了文件名为:"+name;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable thread1=new TestCallable("https://wx3.sinaimg.cn/mw690/0026VLyFgy1gukfxaiw37j635s2dc1ky02.jpg","早安.jpg");
        TestCallable thread2=new TestCallable("https://wx4.sinaimg.cn/mw690/0026VLyFly1guk1gauncxj60u00jztec02.jpg","雨夜.jpg");
        TestCallable thread3=new TestCallable("https://wx1.sinaimg.cn/mw690/0026VLyFgy1gujl757wejj60tv0tvwoc02.jpg","o(=•ェ•=)m.jpg");

        //创建线程池
        ExecutorService service= Executors.newFixedThreadPool(3);

        //提交服务执行
        Future<String> res1= service.submit(thread1);
        Future<String> res2= service.submit(thread2);
        Future<String> res3= service.submit(thread3);

        //获取返回值
        String r1= res1.get();
        String r2= res2.get();
        String r3= res3.get();
        System.out.println("最后的返回值按顺序输出");
        System.out.println(r1);
        System.out.println(r2);
        System.out.println(r3);

        //关闭服务
        service.shutdownNow();
    }
}


//下载器
class WebDownLoader{
    public void downLoader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,DownLoader出现问题!");
        }
    }
}

静态代理模式

package com.test.StaticProxy;

//好处
//代理角色可以增加真实角色的功能:如记录日志
//真实角色专注自己的功能
public class StaticProxy {
    public static void main(String[] args) {

        Person person=new Person();
        WeddingCompany company=new WeddingCompany(person);
        company.HappyMarry();

        //new WeddingCompany(new Person()).HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}

//真实角色
class Person implements Marry{
    public void HappyMarry() {
        System.out.println("我要结婚了");
    }
}

//代理角色
class WeddingCompany implements Marry{
    //代理谁--->真是目标角色
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    public void HappyMarry() {
        before();
        this.target.HappyMarry();
        after();
    }

    private void after() {
        System.out.println("结婚后");
    }

    private void before() {
        System.out.println("结婚前");
    }

}

Lamda表达式

image

package com.test.lamda;

//推导lamda表达式
public class LamdaTest {

    //3.静态内部类
    static class OU2 implements IOU {
        @Override
        public void lamda() {
            System.out.println("静态内部类:lamda is nice");
        }
    }

    public static void main(String[] args) {
        IOU ou=new OU1();
        ou.lamda();

        ou=new OU2();
        ou.lamda();

        //4.局部内部类
        class OU3 implements IOU{
            @Override
            public void lamda() {
                System.out.println("局部内部类:lamda is nice");
            }
        }
        ou=new OU3();
        ou.lamda();

        //5.匿名内部类:没有类的名称,必须借助接口或者父类
        ou=new IOU() {
            @Override
            public void lamda() {
                System.out.println("匿名内部类:lamda is nice");
            }
        };
        ou.lamda();

        //6.用lamda简化
        ou=()-> {
            System.out.println("lamda:lamda is nice");
        };
        ou.lamda();

    }
}

//1.定义一个函数式接口
interface IOU{
    void lamda();
}

//2.实现类
class OU1 implements IOU{
    @Override
    public void lamda() {
        System.out.println("实现类:lamda is nice");
    }
}

image

package com.test.lamda;

public class TestLamda {
    public static void main(String[] args) {
        Love love=((p1, p2) -> {System.out.println(p1+":I love you"); System.out.println(p2+":I love you too");});
        //数据类型要么全省要么都不省
        love.Person("JOJO","Ray");
    }

}

interface Love{
    void Person(String p1,String p2);
}

线程状态

image

image


线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
boolean isAlive 测试线程是否处于活动状态

线程停止

package com.test.State;

//测试stop
//建议线程正常停止——利用次数循环
//使用标志位
public class TestStop implements Runnable{

    //设置一个标志位
    private boolean flag=true;

    @Override
    public void run() {
        int i=0;
        while(flag){
            System.out.println("Run:"+i++);  //循环结束?线程应该没结束吧?
        }
    }

    //设置公开的方法停止线程,转换标志位
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop=new TestStop();

        new Thread(testStop).start();

        for(int i=1;i<=500;i++){
            System.out.println("main:"+i);
            if(i==250){
                //调用stop方法转换标志位。停止线程
                testStop.stop();
                System.out.println("线程结束!主线程继续执行到500");
            }
        }
    }
}

线程休眠

  • sleep(millions):指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

package com.test.State;

import java.text.SimpleDateFormat;
import java.util.Date;

//模拟倒计时
public class TestSleep2 {

    public static void main(String[] args) throws InterruptedException {
        //模拟倒计时
        time();

        System.out.println("开始计时!");
        //获取系统当前时间
        Date currenTime=new Date();

        while(true){
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(currenTime));
            Thread.sleep(1000);
            currenTime=new Date();
        }
    }

    public static void time() throws InterruptedException {
        int num=10;

        while(true){
            if(num<0){
                break;
            }
            else{
                System.out.println(num--);
            }
            Thread.sleep(1000);
        }
    }
}

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,不一定成功

package com.test.State;

//礼让就是让应执行的线程退回就绪状态,即与重新争取执行权
//礼让不一定成功,具有随机性,决定权在CPU
public class TestYield {

    public static void main(String[] args) {
        Yield yield=new Yield();

        new Thread(yield,"A").start();
        new Thread(yield,"B").start();
    }
}
class Yield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        if(Thread.currentThread().getName()=="A"){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"结束执行");
    }
}

线程强制执行

  • Join合并线程,待此线程执行完成后,再执行其他线程。其他线程为阻塞状态
package com.test.State;

//可以想象为插队
//前100 VIP和main同时执行,当到了100时,VIP插队,main线程需等待其执行完后再执行
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程VIP:"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin join=new TestJoin();
        Thread thread=new Thread(join);
        thread.start();

        for (int i = 0; i < 200; i++) {
            if(i==100){
                thread.join();
            }
            System.out.println("main:"+i);
        }
    }
}

线程状态观测

  • NEW

    尚未启动的线程处于此状态

  • RUNNABLE

    在Java虚拟机中执行的线程

  • BLOCKED

    被阻塞等待监视器锁定的线程

  • WAITING

    正在等待另一个线程执行待定动作的线程

  • TIMED_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程

  • TERMINATED

    已退出的线程


package com.test.State;

public class TestState {
    public static void main(String[] arg) {
        Time time=new Time();
        Thread thread=new Thread(time);
        Thread.State state=thread.getState();
        System.out.println(state);// new

        thread.start();
        state=thread.getState();//
        System.out.println(state);//RUNNABLE

        while(state!=Thread.State.TERMINATED){ //线程未结束一直输出状态
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            state=thread.getState();
            System.out.println(state);
        }
    }
}

class Time implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
    }

}

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用1~10表示,默认为5
    • Thread.MIN_PRIORITY=1
    • Thread.MAX_PRIORITY=10
    • Thread.NORM_PRIORITY=5
  • 使用getPriority()获取优先级
  • 使用setPriority(int num)设置优先级

package com.test.State;

public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"——>"+Thread.currentThread().getPriority());

        MyThread myThread=new MyThread();
        Thread thread1=new Thread(myThread);
        Thread thread2=new Thread(myThread);
        Thread thread3=new Thread(myThread);
        Thread thread4=new Thread(myThread);
        Thread thread5=new Thread(myThread);

        //需在线程启动前指定优先级;优先级只是让概率变高,并不会直接影响线程调度(等于没用?,还是得看我CPU大哥)
        thread1.setPriority(1);
        thread1.start();
        thread2.setPriority(2);
        thread2.start();
        thread3.setPriority(Thread.MIN_PRIORITY);
        thread3.start();
        thread4.setPriority(4);
        thread4.start();
        thread5.setPriority(Thread.MAX_PRIORITY);
        thread5.start();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"——>"+Thread.currentThread().getPriority());
    }
}

守护(daemon)线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 守护线程用于后台记录操作日志,监控内存,垃圾回收等待。。
package com.test.State;

import com.sun.org.apache.bcel.internal.generic.NEW;

public class TestDaemon {
    public static void main(String[] args) {
        JOJO jojo= new JOJO();
        RAY ray=new RAY();

        Thread thread=new Thread(jojo);
        thread.setDaemon(true);  //默认为false 用户线程

        thread.start(); //JOJO启动
        new Thread(ray).start(); // RAY启动

        //虚拟机停止需要等待时间,此时守护线程JOJO仍继续跑
    }
}

class JOJO implements Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println("JOJO LOVE RAY!");
        }
    }
}

class RAY implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("RAY 很 开 心");
        }
        System.out.println("RAY LOVE JOJO");
    }
}

线程同步

并发

  • 同一个对象多个线程同时操作
    • 上万人同时100张票
    • 网上,银行同时取钱
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,需要线程同步。
  • 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用

队列和锁

  • 线程同步需要队列+锁,保证线程安全
  • 每个对象都有锁
    • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可
    • 存在以下问题
      • 一个线程持有锁会导致其他所有需要此锁的线程挂起
      • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
      • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

三大不安全案例

买票

package com.test.syn;

//不安全的买票
//会造成多人买同一张票的情况
public class UnsafeBuyTickets {
    public static void main(String[] args) {
        BuyTicket buyTicket=new BuyTicket();

        new Thread(buyTicket,"JOJO").start();
        new Thread(buyTicket,"RAY").start();
        new Thread(buyTicket,"FISH").start();
    }
}

class BuyTicket implements Runnable{

    private int TicketNums=10;
    boolean flag=true;

    @Override
    public void run() {
        while (flag){
            Buy();
        }
    }

    private void Buy()  {
        if(TicketNums<=0){
            flag=false;
        }
        //模拟延时  放大问题发生性
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"拿到了第"+TicketNums--+"张票");
    }
}

两人一卡同时取钱

package com.test.syn;

public class UnsafeBank {
    public static void main(String[] args) {
        User user=new User("奖学金",2000);
        Draw Owner=new Draw(user,500,"我");
        Draw Parents=new Draw(user,2000,"父母");

        Owner.start();
        Parents.start();
    }
}

class User{
    String name;
    int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

//模拟取钱
class Draw extends Thread{
    User user; //账户
    int DrawMoney; //取的钱数
    int HandMoney;//现在余额

    public Draw(User user,int DrawMoney,String name){
        super(name);   //=Thread(name)
        this.user=user;
        this.DrawMoney=DrawMoney;
    }

    //取钱
    @Override
    public void run() {
        //账户余额-取的钱数<=0,钱不够
        if(user.money-DrawMoney<=0){
            System.out.println(Thread.currentThread().getName()+"余额不足,无法取款!");
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //现在的余额=账户余额-取出的钱数
        user.money=user.money-DrawMoney;
        //手上的钱=原来持有的钱+取出的钱
        HandMoney = HandMoney +DrawMoney;

        System.out.println(Thread.currentThread().getName()+"取出"+DrawMoney+"元成功!");
        System.out.println(user.name+"余额:"+user.money); //可能取钱的人不是银行卡持有者
        //Draw 继承 Thread,Thread.currentThread().getName()=this.getName();
        System.out.println(this.getName()+"持有:"+HandMoney);
    }
}

ArrayList

package com.test.syn;

// 多个线程看到的是同一片内存,造成复用
import java.util.ArrayList;
import java.util.List;

public class UnsafeArrayList {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();   //lamda
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(list.size());
    }
}

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要对方法提出一套机制,这套机制就是 synchronized 关键字

    • synchronized 方法

      public synchronized void method(int args){}
      
    • synchronized 块

      synchronized (Obj){}
      
      • Obj称为 同步监视器
      • Obj可以是任何对象,推荐使用共享资源(即需要变化的对象)作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
  • synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程就获得这个锁,继续执行该方法。

    缺陷:方法里需要修改的资源才需要锁,锁的太多,造成资源浪费,若一个大的方法申明为synchronized方法将会影响效率


同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁定,然后锁定并访问

买票

package com.test.syn;

//不安全的买票
//会造成多人买同一张票的情况
public class UnsafeBuyTickets {
    public static void main(String[] args) {
        BuyTicket buyTicket=new BuyTicket();

        new Thread(buyTicket,"JOJO").start();
        new Thread(buyTicket,"RAY").start();
        new Thread(buyTicket,"FISH").start();
    }
}

class BuyTicket implements Runnable{

    private int TicketNums=100;
    boolean flag=true;

    @Override
    public void run() {
        while (flag){
            Buy();

            //模拟延时  放大问题发生性
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    private synchronized void Buy()  {
        if(TicketNums<=0){
            flag=false;
            return;//停止,退出run方法
        }

        System.out.println(Thread.currentThread().getName()+"拿到了第"+TicketNums--+"张票");
    }
}

两人一卡同时取钱

package com.test.syn;

public class UnsafeBank {
    public static void main(String[] args) {
        //这里new了两个user,锁的是两个不同的对象,单纯在方法上加syn锁不到两个对象,要用sync块
        User user=new User("奖学金",2000);
        Draw Owner=new Draw(user,500,"我");
        Draw Parents=new Draw(user,2000,"父母");

        Owner.start();
        Parents.start();
    }
}

class User{
    String name;
    int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

//模拟取钱
class Draw extends Thread{
    User user; //账户
    int DrawMoney; //取的钱数
    int HandMoney;//现在余额

    public Draw(User user,int DrawMoney,String name){
        super(name);   //=Thread(name)
        this.user=user;
        this.DrawMoney=DrawMoney;
    }

    //取钱
    @Override
    public  void run() {
        synchronized (user){
            //账户余额-取的钱数<=0,钱不够
            if(user.money-DrawMoney<=0){
                System.out.println(Thread.currentThread().getName()+"想取"+DrawMoney);
                System.out.println(user.name+"余额不足,无法取款!");
                return; //停止,退出run方法
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //现在的余额=账户余额-取出的钱数
            user.money=user.money-DrawMoney;
            //手上的钱=原来持有的钱+取出的钱
            HandMoney = HandMoney +DrawMoney;

            System.out.println(Thread.currentThread().getName()+"取出"+DrawMoney+"元成功!");
            System.out.println(user.name+"余额:"+user.money); //可能取钱的人不是银行卡持有者
            //Draw 继承 Thread,Thread.currentThread().getName()=this.getName();
            System.out.println(this.getName()+"持有:"+HandMoney);
        }
    }
}

ArrayList

package com.test.syn;

// 多个线程看到的是同一片内存,造成复用
import java.util.ArrayList;
import java.util.List;

public class UnsafeArrayList {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();

        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();   //lamda
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(list.size());
    }
}

tip:没有sleep线程运行过快导致锁不住


死锁

多个线程独自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有两个以上对象的锁,就可能发生死锁。(你需要我的资源,我需要你的资源,双方都想对方先给)

package com.test.Lock;

//多个线程互相拿着对方需要的资源,造成僵持的局面
public class DeadLock {
    public static void main(String[] args) {
        Eat eat=new Eat(0,"JOJO");
        Eat eat1=new Eat(1,"Ray");

        eat.start();
        eat1.start();
    }
}

//水果
class Fruit{

}

//蔬菜
class Vegetable{

}

class Eat extends Thread{
    //static保证需要的资源只有一份
    static Fruit fruit=new Fruit();
    static Vegetable vegetable=new Vegetable();

    int choice; //选择
    String name; //吃货

    public Eat(int choice,String name){
        this.choice=choice;
        this.name=name;
    }

    @Override
    public void run() {
        try {
            eat();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //互相拿着对方想吃的食物
    private void eat() throws InterruptedException {
        if(choice==0){
            synchronized (fruit){
                System.out.println(this.name+"获得水果的锁");
                Thread.sleep(1000);
//                //一秒钟后想吃蔬菜
//                synchronized (vegetable){
//                    System.out.println(this.name+"获得蔬菜的锁");
//                }
            }
            //先把水果的锁释放,再去拿现在需要的蔬菜,不能既拿着水果的锁,又拿着蔬菜的锁,鱼和熊掌不可兼得
            synchronized (vegetable){
                System.out.println(this.name+"获得蔬菜的锁");
            }
        }
        else{
            synchronized (vegetable){
                System.out.println(this.name+"获得蔬菜的锁");
                Thread.sleep(2000);
//                //两秒钟后想吃水果
//                synchronized (fruit){
//                    System.out.println(this.name+"获得水果的锁");
                }
                //先把蔬菜的锁释放,再去拿现在需要的水果,不能既拿着水果的锁,又拿着蔬菜的锁,鱼和熊掌不可兼得
                synchronized (fruit){
                System.out.println(this.name+"获得水果的锁");
            }
        }
    }
}

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个线程使用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

只要想办法破坏其中任意一个或多个条件就可以避免死锁发生


Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。

    锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

  • ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁释放锁

class A{
    private final ReentrantLock lock=new ReentrantLock();
    public void m(){
        lock.lock();
        try{
            //保证线程安全的代码
        }
        finally{
            lock.unlock();
            //如果同步代码有异常,要将unlock()写入finally语句块,必然执行
        }
    }
}

package com.test.Lock;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();

        new Thread(ticket,"JOJO").start();
        new Thread(ticket,"Ray").start();
        new Thread(ticket,"X").start();
    }
}

class Ticket implements Runnable{
    int tickets=10;

    //定义可重入锁ReentrantLock
    private ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try {
                //加锁
                lock.lock();
                if(tickets>0){
                    System.out.println(Thread.currentThread().getName()+"拿到第"+tickets--+"张票");
                }
                else{
                    System.out.println("没有票了");
                    break;
                }
            }
            finally {
                //解锁
                lock.unlock();
            }
        }
    }
}

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁)
  • synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更高的可扩展性(提供更多子类)
  • 优先使用顺序
    • Lock——>同步代码块(已经进入了方法体,分配了相应资源)——>同步方法(在方法体之外)

线程协作

线程通信

  • 应用场景:生产者和消费者问题
    • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
    • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品后又需要马上通知消费者消费
    • 对于消费者,在消费之后,要通知生产者消费完毕,需要生产新的产品以供消费

  • Java提供几个方法解决线程之间的通信问题

    方法名 作用
    wait() 表示线程一直等待,直到其他线程通知,会释放锁
    wait(long timeout) 指定等待的毫秒数
    notify() 唤醒一个处于等待状态的线程
    notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的优先调度

    Tips:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException


管程法(缓冲区)

image

package com.test.PtoCProblem;

//生产者消费者问题——>利用缓冲区解决
//管程法

//生产者  消费者  产品  缓冲区
public class Moniters {
    public static void main(String[] args) {
        SynContainer container=new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    }
}

//生产者
class Productor extends Thread{
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }

    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i+"号牛肉");
            container.setBeefs(new Beef(i));  //把当前牛肉放入容器
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了"+container.getBeefs().id+"号牛肉");
        }
    }
}

//产品
class Beef {
    int id; //产品编号

    public  Beef(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer {
    //需要一个容器大小
    Beef[] beefs=new Beef[10];

    //容器计数器
    int count=0;

    //生产者放入产品
    public synchronized void setBeefs(Beef beef){
        //容器满了,等待消费者消费
        while(count==beefs.length){  //if语句中醒来的线程 不会再一次进行判断了 而while会重新再判断
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //没满,需要放入产品
        beefs[count]=beef;
        count++; //容器里的产品+1

        //通知消费者取出产品
        this.notifyAll();
    }

    //消费者取走产品
    public synchronized Beef getBeefs(){
        //判断能否取走
        while(count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //能取走
        count--;  //因为生产者生产完会+1,最后的数组下标会多1,所以这里要拿到应该-1
        Beef beef=beefs[count];
        //通知生产者生产
        this.notifyAll();

        return beef;  //返回取走的产品
    }

}

信号灯法(标志位)

package com.test.PtoCProblem;

import com.sun.org.apache.bcel.internal.generic.NEW;

//生产者消费者问题——>标志位
//信号灯法
public class Signer {
    public static void main(String[] args) {
        Bv bv=new Bv();
        new UP(bv).start();
        new User(bv).start();
    }
}

//生产者——>UP主
class UP extends Thread{
    Bv bv;

    public UP(Bv bv) {
        this.bv = bv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100;i++) {
            if(i%2==0){
                this.bv.Upload("JOJO");
            }
            else {
                this.bv.Upload("Ray");
            }
        }
    }
}

//消费者——>用户
class User extends Thread{
    Bv bv;

    public User(Bv bv) {
        this.bv = bv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            this.bv.view();
        }
    }
}

//产品——>视频
class Bv{
    //UP主上传,用户等待 T
    //用户观看,UP主等待 F
    String name;
    boolean flag=true;

    //上传
    public synchronized void Upload(String name){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("UP主上传"+name);

        //通知用户观看
        this.notifyAll();

        this.name=name;
        this.flag=!this.flag;

    }

    //观看
    public synchronized void view(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("用户观看"+name);

        //通知UP主上传
        this.notifyAll();
        this.flag=!this.flag;

    }
}

线程池

背景:

  • 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

思路:

  • 提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中。

    可以避免频繁创建销毁,实现重复利用

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间会终止

使用线程池

  • JDK 5.0 起提供了线程池相关API:ExecutorServieExecutors
  • ExecutorService:真正的线程池接口。常见子类:ThreadPoolExecutor
    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    • Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
    • void shutdown():关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

package com.test.ThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class TestPool {
    public static void main(String[] args) {
        //创建服务,建立线程池
        ExecutorService service= Executors.newFixedThreadPool(10); //参数为线程池大小

        //服务
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭服务
        service.shutdown();
    }

}

class MyThread implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}

相关源码

点击查看源码

posted @ 2022-03-07 15:20  满城悬新筝  阅读(85)  评论(0)    收藏  举报