Java多线程笔记

多线程笔记

说明:主要是记录自己在学习多线程时候产生的笔记。已完结

1 继承多线程

方式一:继承Thread类
创建线程方式一:继承Thread类,重写run方法,调用start开启线程
idea快捷键:
20.fori等同于for(int i=1;i<=20;i++)
    
方式二:实现Runnable接口
  1. 启动线程:传入目标对象+Thread对象.start()
  2. 推荐使用该方法,因为可以避免单继承局限性,方便同一个对象对多个线程使用

案例(龟兔赛跑)

package com.young.demo;

public class Race implements Runnable{
    private static  String winner;
    @Override
    public void run() {
        boolean flag = false;
        for (int i = 0; i <= 100; i++) {
            //模拟兔子睡觉,每10步睡一觉
            if(i%10==0 && Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            flag = gameOver(i);
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->跑了"+
                    i+"步");
            //模拟两者的速度,跑一步乌龟需要的时间是兔子的10倍
            if(Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps){
        if(winner!=null){
            return true;
        }else{
            if(steps>=100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is"+winner);
                return true;
            }
        }
        return false;
    }
    //主线程
    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

兔子-->跑了85步
兔子-->跑了86步
兔子-->跑了87步
兔子-->跑了88步
兔子-->跑了89步
乌龟-->跑了99步
winner is乌龟

2 Lambda表达式

​ 函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

​ 格式:(参数表)->{函数体};

​ 参数表中的参数类型可以被忽略

​ 简化版:单个参数名->单条语句

​ 例子

(int a)->{System.out.print(a);};
a->System.out.print("test");

3 线程状态

3.1 线程中止

​ 核心:使用标志位让线程自行终止

​ 步骤:

  1. 建议线程正常停止–>利用次数,不建议死循环
  2. 建议使用标志位–>设置一个标志位
  3. 不要使用stop或destory等过时或者JDK不建议使用的方法
3.2 线程休眠

两个功能:模拟网络延时和模拟倒计时

每一个对象都有一个锁,sleep不会释放锁

3.3 线程礼让

关键字:yield

package com.young.state;

public class TestYield {
    public static void main(String[] args) {
        //使用lambda表达式
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"线程开始");
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"线程结束");
        },"a").start();
        //使用匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程开始");
                Thread.yield();
                System.out.println(Thread.currentThread().getName()+"线程结束");
            }
        },"b").start();
    }
}

3.4 线程强制执行

线程插队,保证该线程完全执行

关键字:join

package com.young.state;

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 testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            if(i==300){
                thread.join(); //插队
            }
            System.out.println("main"+i);
        }
    }
}

3.5 线程状态

在这里插入图片描述

可以使用Thread.State state = thread.getState()去获取当前线程的状态

3.6 线程优先级

线程的优先级用数字表示,范围从1~10,数字越大,线程的优先级越高

获取优先级 getPriority,设置优先级setPriority(int xxx)

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了。这都是看CPU的调度。

package com.young.state;

public class TestPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        TestPriority testPriority = new TestPriority();
        Thread t1 = new Thread(testPriority);
        Thread t2 = new Thread(testPriority);
        Thread t3 = new Thread(testPriority);
        //不设置优先级
        t1.start();
        //设置优先级
        t2.setPriority(Thread.MIN_PRIORITY);
        t2.start();
        t3.setPriority(Thread.MAX_PRIORITY);
        t3.start();
    }
}
3.7 守护线程

线程分用户线程和守护线程;虚拟机必须确保用户线程执行完毕,但不需要等待守护线程执行完毕。

应用:后台记录操作日志、内存监控、垃圾回收等待……

使用:setDaemon(true)

4 线程同步

对方法添加synchronized关键字可以让方法变成同步方法。但是会影响效率。

在同步方法中,synchronized作用的是该类的对象,即this,这样还是会出现不安全,假设在银行家问题中,有账户account类,以及取款drawing类,取款操作放在drawing类中,如果对该类的run方法加synchronized关键字其实是无用的,这锁的是drawing类,但我们需要锁的是account类。

所以方法二:同步块,结构synchronized(obj){代码块}
在这里插入图片描述

总结:同步方法适合与共享变量(或需要锁的对象)与操作该变量的方法是同一个对象,比如说火车站取票问题,而同步块适合与共享对象与操作方法不在同一个类中的情况,比如银行取钱问题

扩充资料:

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

5 死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。

产生死锁的四个必要条件:

1、互斥条件:一个资源每次只能被一个进程使用。

2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

3、不剥夺条件:进程已获得的资源,在未完全使用完之前,不能强行剥夺

4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

破坏上面的一个必要条件就可以避免死锁。

案例:女生化妆产生死锁

package com.young.syn;
/*
*案例:女生化妆产生死锁 
* Author:小羊
*/
//女生化妆引起的死锁问题
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(1,"小红");
        Makeup g2 = new Makeup(0,"小蓝");
        g1.start();
        g2.start();
    }
}
//化妆必备品:口红
class Lipstick{

}
//化妆必备品:镜子
class Mirror{

}
//化妆类
class Makeup extends Thread{
    private static Lipstick lipstick = new Lipstick();
    private static  Mirror mirror = new Mirror();
    private int chioce;
    private String name;
    public Makeup(int chioce, String name) {
        this.chioce = chioce;
        this.name = name;
    }
    @Override
    public void run(){
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void makeup() throws InterruptedException {
        if(this.chioce==1){
            //先拿起口红再拿起镜子
            synchronized (lipstick){
                System.out.println(this.name+"拿起了口红。");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.name+"拿起了镜子。");
                }
            }
        }{
            //先拿起镜子再拿起口红
            synchronized (mirror){
                System.out.println(this.name+"拿起了镜子。");
                Thread.sleep(1000);
                synchronized (lipstick){
                    System.out.println(this.name+"拿起了口红。");
                }
            }
        }
    }

}

分析:上述情况会产生死锁,是因为一个锁包含在另外一个锁里,他们彼此占用资源但又不释放,从而导致死锁

解决方法:只要将两个锁分开放,即去掉锁中锁的情况即可。

6 Lock锁

在这里插入图片描述

7 生产者与消费者

生产者和消费者共享同一个资源,并且生产者与消费者之间相互依赖,互为条件

进程通信需要两个函数:wait和notify

生产者将产品放入缓冲区,消费者从缓冲区中拿数据

7.1 管程法

其实也就是利用缓冲区

package com.young.senior;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.concurrent.locks.ReentrantLock;

//利用缓冲区解决:管程法
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        Productor p = new Productor(synContainer);
        Consumer c = new Consumer(synContainer);
        p.start();
        c.start();

    }
}

//生产者
class Productor extends Thread{
    private final ReentrantLock lock = new ReentrantLock();
    //专心生产
    SynContainer container;
    public Productor(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            lock.lock();
            try{
                container.push(new Chicken(i));
                System.out.println("生产第-->"+i+"只鸡");
            }
            finally {
                lock.unlock();
            }
        }
    }
}
//消费者
class Consumer extends Thread{
    private final ReentrantLock lock = new ReentrantLock();
    //专心消费
    SynContainer container;
    public Consumer(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            lock.lock();
            try{
                Chicken chicken = container.pop();
                System.out.println("消费第-->"+chicken.id+"只鸡");
            }finally {
                lock.unlock();
            }

        }
    }
}
//产品
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;
        this.notifyAll();
    }
    //消费者从缓冲区中取产品
    public synchronized Chicken pop(){
        //首先判断是否为空
        if(count==0){
            //缓冲区为空,则阻塞该进程
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //不为空就取数据
        this.notifyAll();
        return chickens[--count];
    }
}


8 使用线程池

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

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。类似与共享单车之!

package com.young.senior;

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

public class TestPool {

    public static void main(String[] args) {
        //1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //2.执行服务
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //3.关闭服务
        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、付费专栏及课程。

余额充值