多线程的基础了解

多线程

进程与线程

进程:系统资源分配的单位。包含多个线程
线程:CPU的调度和执行单位。
一个进程至少包含一个线程。
线程的创建一共有三种方式:

方式一:
继承Thread类,重写run方法,调用start方法启动线程

public class TestThread1 extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码"+i);
        }
    }

    public static void main(String[] args) {
        TestThread1 testThread1=new TestThread1();
        testThread1.start();
        //主线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程"+i);
        }
    }
}

在这里插入图片描述
方式二:
实现Runnable接口,重写run方法,调用start方法启动线程(推荐使用此方法,因为java是单继承,但可以实现多个接口)

public class TestRunable implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        TestRunable testRunable=new TestRunable();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        new Thread(testRunable).start();
        //主线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程"+i);
        }
    }
}

在这里插入图片描述
方式三:
实现Callable接口。实现Callable接口需要返回值类型,重写call方法需要抛出异常。

/**
 * callable的好处
 * 可以定义返回值
 * 可以抛出异常
 */
public class TestCallable implements Callable<Boolean> {
    private String url;
    private String name;

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

    @Override
    public Boolean call() {
        WebDownLoader webDownLoader=new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载图片名l"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1=new TestCallable("https://pic88.com/pic/12020080510373687334.html","1.jpg");
        TestCallable t2=new TestCallable("https://pic88.com/pic/120200717072546119636.html","2.jpg");
        TestCallable t3=new TestCallable("https://pic88.com/pic/120200805204640200949.html","3.jpg");
        //创建执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1=ser.submit(t1);
        Future<Boolean> r2=ser.submit(t2);
        Future<Boolean> r3=ser.submit(t3);
        //获取结果
        boolean rs1=r1.get();
        boolean rs2=r2.get();
        boolean rs3=r3.get();
        System.out.println(rs1); System.out.println(rs2); System.out.println(rs3);
        //关闭服务
        ser.shutdown();
    }
}
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("下载异常");
        }
    }
}

龟兔赛跑案例:

//案例龟兔赛跑
public class Race implements Runnable{
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //模拟兔子睡觉
            if(Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean falg=gameover(i);
            //如果比赛结束则停止
            if(falg){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
        }
    }
    public boolean gameover(int j){
        if(winner!=null){
            return true;
        }else{
            if(j==100){
                winner=Thread.currentThread().getName();
                System.out.println("胜者"+winner);
                return true;
            }
        }
        return false;
    }
    public static void main(String[] args) {
        Race race=new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

在这里插入图片描述
龟兔赛跑案例因为兔子的偷懒导致乌龟最终胜利,我们通过定制相同赛道,使用sleep方法模拟兔子睡觉,然后乌龟取得胜利。

线程状态:

线程的状态可以分为:
1、创建:创建线程对象开始表示线程已经创建。
2、就绪:调用start()方法进入到就绪状态,但并没有立即调度。
3、阻塞:当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。
4:运行:进入运行状态,线程才真正执行线程体的代码块。
5、死亡:线程中断或者结束,一旦进入死亡状态,就不能再次启动。

线程中有几个常用方法:
在这里插入图片描述
这里就不再一一演示。

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待…
public class TestDaemon {
    public static void main(String[] args) {
        God god=new God();
        You you=new You();

        Thread thread=new Thread(god);
        thread.setDaemon(true);//默认为false表示为用户线程
        thread.start();//上帝线程启动

        new Thread(you).start();//用户线程启动
    }
}
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你!");
        }
    }
}
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你快乐的生活着!");
        }
        System.out.println("goodbye world!");//嗝屁了
    }
}

线程同步

多个线程操作一个资源
并发:多个线程同时操作同一个资源

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象﹒这时候我们就需要线程同步﹒线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station=new BuyTicket();
        new Thread(station,"我").start();
        new Thread(station,"你").start();
        new Thread(station,"黄牛").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;
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums-- +"张票");
    }
}

多线程并发就会引发线程安全问题,上面这个例子就是线程不安全的,会造成数据库脏读等问题;java也提供了解决线程不安全问题提供了synchronized关键字

synchronized 实现原理:队列和锁
两种实现形式:同步方法和同步块

每个对象对应一把锁
弊端:方法里面需要修改的内容才需要锁,锁的过多,浪费资源
锁的对象即变量,需要增删改的对象
对于普通同步方法,锁是当前实例对象。 如果有多个实例 那么锁对象必然不同无法实现同步。
对于静态同步方法,锁是当前类的Class对象。有多个实例 但是锁对象是相同的 可以完成同步。
对于同步方法块,锁是Synchonized括号里配置的对象。对象最好是只有一个的 如当前类的 class 是只有一个的 锁对象相同 也能实现同步。

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station=new BuyTicket();
        new Thread(station,"我").start();
        new Thread(station,"你").start();
        new Thread(station,"黄牛").start();
    }
}
class BuyTicket implements Runnable{
    private int ticketNums=10;
    boolean flag=true;//外部停止方式
    @Override
    public void run() {
        //多人买票
        while(flag){
            buy();
        }
    }
    private synchronized void buy(){
        //判断是否有票
        if(ticketNums<=0){
            flag=false;
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums-- +"张票");
    }
}
public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(1000,"金婚基金");
        Drawing you=new Drawing(account,50,"你");
        Drawing girlFriend=new Drawing(account,100,"girlFriend");
        you.start();
        girlFriend.start();
    }
}
//账户
class Account{
    int money;
    String name;
    public Account(int money,String name){
        this.money=money;
        this.name=name;
    }
}
//银行:模拟取款
class Drawing extends Thread{
    Account account;
    int drawingMoney;
    int nowMoney;
    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account=account;
        this.drawingMoney=drawingMoney;
    }

    @Override
    public void run(){
        //synchronized 因为有两个银行对象,这里锁的是银行类
        synchronized(account){
            //判断是否有钱
            if(account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"余额不足");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额
            account.money=account.money-drawingMoney;
            //手里的金额
            nowMoney=nowMoney+drawingMoney;
            System.out.println(account.name+"余额为"+account.money);
            System.out.println(this.getName()+"手里的钱"+nowMoney);
        }
    }
}

:(互斥锁)synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的。到了Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。

Lock锁

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

ReentrantLoc(可重入锁)类实现了lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁.
synchronized与Lock的对比:
1、Lock是显示锁(手动开启和关闭锁),synchronized是阴式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有同步代码块和同步方法
3、使用lock锁,jvm将花费较少的时间来调度线程,性能更好
4、优先使用顺序:Lock>同步代码块>同步方法

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2=new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
class TestLock2 implements Runnable{

    int ticketNums=10;
    private final ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try{
                lock.lock();//加锁
                if(ticketNums>0){
                    System.out.println(ticketNums--);
                }else{
                    break;
                }
            }finally {
                lock.unlock();//解锁
            }
        }
    }
}

死锁问题

既然有锁,那肯定会出现死锁问题;死锁就是多个对象争夺同同一资源。
死锁有四个必要条件:

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

只要破坏其中一个就可以解决死锁问题

public class DeadLock {
    public static void main(String[] args) {
        Makeup g1=new Makeup(0,"小红帽");
        Makeup g2=new Makeup(1,"大红帽");
        g1.start();
        g2.start();
    }
}
class Lipstick{}//口红
class Mirror{}//镜子
class Makeup extends Thread{
    static Lipstick lipstick=new Lipstick();
    static Mirror mirror=new Mirror();
    int choice;//选择
    String girlName;//使用者
    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }

    @Override
    public void run() {
        if(choice==0){
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }synchronized (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
            }
        }else{
            synchronized (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
            }
        }
    }
}

在这里插入图片描述

线程协作

生产者消费者问题

解决方式一:
并发协作模型“生产者/消费者模式”—>管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer=new SynContainer();
        new Productor(synContainer).start();
        new Consumer(synContainer).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++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+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.pop().id+"只鸡");
        }
    }
}
//产品
class Chicken{
    int id;
    public Chicken(int id){
        this.id=id;
    }
}
class SynContainer{
    //需要一个容器大小
    Chicken[] chickens=new Chicken[10];
    //容器计数器
    int count=0;
    public synchronized void push(Chicken chicken){
        //判断容器是否满了,如果满了通知消费者消费
        if(count==chickens.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们需要丢入产品
        chickens[count]=chicken;
        count++;
        this.notifyAll();
    }
    public synchronized Chicken pop(){
        //判断能否消费
        if(count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken=chickens[count];
        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

方式二:
并发协作模型“生产者/消费者模式”—>信号灯法

public class TestPc2 {
    public static void main(String[] args) {
        TV tv=new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
class Player extends Thread{
 TV tv;
 public Player(TV tv){
     this.tv=tv;
 }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i%2==0){
                this.tv.play("动画片");
            }else{
                this.tv.play("新闻");
            }
        }
    }
}
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){
        this.tv=tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}
class TV{
    String voice;
    boolean flag=true;
    public synchronized void play(String voice){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了"+voice);
        this.notifyAll();
        this.voice=voice;
        this.flag=!this.flag;
    }
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了"+voice);
        this.notifyAll();
        this.flag=!this.flag;
    }
}

线程池

思路:提前创建好多个线程,放入线程池中,使用时,直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。
好处:
1、提高了响应速度(减少了创建新线程的时间)
2、降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3、便于线程管理

在这里插入图片描述

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

完毕~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值