对于多线程的学习总结

最近刚学完多线程,趁着自己还没忘记,现总结总结,大家凑合着看看吧

1.多线程的实现方法

1.1并行与并发
    并行:多个对象同一时刻执行多个任务
    并发:单个对象在同一时间段交替执行多个任务
1.2进程和线程
    进程:指正在运行的一个程序(cpu分配资源的基本单位)
    线程:正在运行的程序中的一个执行任务(执行单元)(进程中分配资源的基本单位)
1.3多线程的三种实现方法
    1.3.1继承Thread类
        一个类通过继承Thread类,重写父类的run()方法,来实现多线程,通过这种方法来实现多线程,直接创建对象调用start()方法来开始线程;
        start()方法实际上是开辟新的栈内存,在新的栈内存中执行线程任务
         所以要想开始新的线程,必须调用start()方法
    1.3.2实现Runnable接口
         一个类可以通过实现Runnable接口来实现多线程,重写接口中的run方法,然后将实现了接口的类的对象作为参数传递给Thread对象,再通过调用start()方法来开启线程:new Thread(Runnable对象)
    1.3.3实现Callable接口
         一个类可以通过实现Callable接口来实现多线程,重写接口的call方法,然后创建类的对象作为参数传递给FutureTask对象,然后将FutureTask对象作为参数传递给Thread对象:
           

 FutureTask ft = new FutrueTask(Call对象);
           Thread t = new Thread(ft);
           t.start();


        call方法是有一个返回值的,Callable是一个泛型接口,泛型的类型必须和call方法的返回值类型相同
            在线程运行结束后,可以通过FutureTask对象调用get方法来获取返回值
    1.3.4三种方式的比较
            继承Thread相比较起来编写代码少,但是该类无法去继承其他类,降低了可扩展性
            其他两种实现接口的方式提高了可扩展性,在实现多线程时还可以去继承其他类(以后用实现接口较多)

2.多线程的成员方法

3.1设置获取名字
   

String getName();//获取线程的名字
    Thread(String name);//通过构造方法给线程定义一个名字
    setName(String name);//设置线程的名字


3.2获取当前线程对象
   

 public static Thread currentThread();//获取当前调用方法的线程对象


3.3睡眠方法
   

 public static void sleep(long time);//设置当前线程休眠time时间


3.4线程的优先级
    声明:线程的优先级的高低只是说明其抢占CPU时间片的概率的大小,并不能保证在CPU空闲时一定能抢到执行权


    int getPriority();//获取当前线程的优先级
    void setPriority(int priority)//给线程设置优先级
    注:优先级默认为5,范围为MIN_PRIORITY(1)-MAX_PRIORITY(10)


3.5守护线程
   

void setDaemon(boolean false);//设置一个线程为守护线程


    守护线程时为了给其他线程做辅助工作,当普通线程结束时不管守护线程是否结束,它都会随之结束

3.多线程的安全问题

3.1产生安全问题原因
      当多个线程共享资源时,并且多个线程都对资源数据进行增删改操作时,会出现安全问题。

3.2解决方法
    3.2.1同步代码块
        同步代码块的格式:
        

synchronized(锁对象){//多线程对共享资源操作的代码 }


        注意:①锁对象可以是任意对象
              ②多线程必须用同一把锁
        同步代码块解决多线程安全问题的原理:使一个线程在对共享数据进行操作时,其他线程无法对其进行操作
    3.2.2同步方法
         同步方法的格式:
         

public [static] synchronized 返回值类型 方法名(){}


        static同步方法 锁对象是 this
         非static同步方法 锁对象是 类名.class
    3.2.3创建锁对象
         可以自己实例化一个锁对象:Lock lock = new ReenTrantLock
         在需要加锁的代码位置lock();需要解开锁的位置unlock();

相关代码:

这里是同步代码块写了一个经典的卖票的线程安全问题,同步方法不做演示


public class MySynchronizedTest1 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket implements Runnable{
    //定义票的数量
    private int ticket = 100;
    private static Object obj = new Object();
    @Override
    public void run() {
        while(true){
            //设置同步代码块,将操作共享数据的代码放到synchronized(Object obj){}的大括号中
            synchronized (obj){
                //就是当前的线程对象.
                if(ticket <= 0){
                    break;//说明票已经卖完了
                }else{
                    //卖每一张票需要时间,所以这里让它睡眠一下再继续卖下一张
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            }
        }
    }
}

3.3死锁问题
    死锁:由于锁的嵌套导致线程A等待线程B的锁,线程B等待线程A的锁,所以导致程序无法正常执行

代码演示:

//死锁
public class MySynchronizedTest3 {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    synchronized (objB){
                        System.out.println("我在走路");
                    }
                }
            }
        }).start();
        new Thread(()->{
            while(true){
                synchronized (objB){
                    synchronized (objA){
                        System.out.println("你在走路");
                    }
                }
            }
        }).start();
    }
}

3.4生产者-消费者
    这里举例说明可以更好的理解:
        ①顾客来到餐厅发现没有吃的,就会等待wait()
        ②顾客来到餐厅发现有吃的,那么就会吃掉吃的,然后通知厨师去做notify()
        ③厨师发现现在有吃的,那就等待wait()
        ④厨师发现没有吃的,那么就会做食物,然后通知顾客notify()
    生产者-消费者主要解决了让多个线程按照一定的次序来协作完成某个功能
    代码实现主要使用三个方法:wait() notify() notifyAll();
    注意:这三个方法的调用者都是锁对象

附上代码如下:

//桌子类,存放共享资源
public class Desk {
    //定义一个flag表示桌子上是否有食物
    private boolean flag;
    //定义食物数量
    private int FoodCount;
    //定义共用的锁
    private final Object Lock = new Object();

    public Desk() {
        this(false,10);
    }

    public Desk(boolean flag, int foodCount) {
        this.flag = flag;
        FoodCount = foodCount;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getFoodCount() {
        return FoodCount;
    }

    public void setFoodCount(int foodCount) {
        FoodCount = foodCount;
    }

    public Object getLock() {
        return Lock;
    }

    @Override
    public String toString() {
        return "Desk{" +
                "flag=" + flag +
                ", FoodCount=" + FoodCount +
                ", Lock=" + Lock +
                '}';
    }
}
//消费者类(顾客)
public class Foodie extends Thread{
    private Desk desk;
    public Foodie(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while(true){
            synchronized (desk.getLock()){
                if(desk.getFoodCount() == 0){
                    break;
                }else{
                    if(desk.isFlag()){//代表桌子上有食物
                        System.out.println("吃货正在吃倒数第"+desk.getFoodCount()+"个食物");
                        desk.setFlag( false);//吃完之后将flag变为flase表示桌子上没有食物了

                        //Desk.FoodCount--;//食物数量减一
                        desk.setFoodCount(desk.getFoodCount()-1);

                        desk.getLock().notifyAll();//唤醒等待的线程
                    }else{
                        try {
                            desk.getLock().wait();//如果为false,说明没有食物,那么进入等待状态,释放掉锁
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
//生产者类(厨师)
public class Cooker extends Thread{
    private Desk desk;
    public Cooker(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while(true){
            synchronized (desk.getLock()){
                if(desk.getFoodCount() == 0){
                    break;
                }else{
                    if(!desk.isFlag()){
                        System.out.println("厨师正在做倒数第"+desk.getFoodCount()+"个食物,还剩"+(desk.getFoodCount()-1)+"个食物");
                        desk.setFlag(true);
                        desk.getLock().notifyAll();
                    }else{
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Desk desk = new Desk();
        Foodie f = new Foodie(desk);
        Cooker c = new Cooker(desk);
        f.start();
        c.start();
    }
}

3.5阻塞队列

     3.5.1阻塞队列的使用
        阻塞出现的原因:程序去完成一个功能时,由于某种原因,现在无法完成,程序会停住(后面的代码就不能再执行了),直到该功能完成为止。
        阻塞队列结合生产者-消费者使用可以大大提高代码效率,并且不需要加锁,因为阻塞队列的底层就是 锁+wait+notify
     3.5.2阻塞队列的创建和方法
        BlockingQueue:
            ArrayBlockingQueue(int capacity)
             LinkedBlockingQueue();
        方法:
            往阻塞队列中放元素:put
            从阻塞队列中取元素:take
     3.5.3分析
            在消费者-生产者机制中使用阻塞队列,相当于给生产者提供了一个可以存放多个产品的队列,它生产了产品就放到阻塞队列中,不必等待消费者消费了产品之后再唤醒它再去生产下一个产品;而消费者也同理,它可以直接从阻塞队列中取产品,不用等生产者一个一个生产。

附上代码如下:

import java.util.concurrent.ArrayBlockingQueue;
//生产者类
public class Cooker extends Thread{
    private ArrayBlockingQueue<String> arrayBlockingQueue;
    public Cooker(ArrayBlockingQueue<String> arrayBlockingQueue) {
        this.arrayBlockingQueue = arrayBlockingQueue;
    }

    @Override
    public void run() {
        while (true){
            try {
                arrayBlockingQueue.put("汉堡包");
                System.out.println("往阻塞队列中放一个汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//消费者类
import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread{
    private ArrayBlockingQueue<String> arrayBlockingQueue;
    public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue) {
        this.arrayBlockingQueue = arrayBlockingQueue;
    }

    @Override
    public void run() {
        while (true){
            try {
                String food = arrayBlockingQueue.take();
                System.out.println("从阻塞队列拿到了一个"+food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//测试类
import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

        Cooker c = new Cooker(arrayBlockingQueue);
        Foodie f = new Foodie(arrayBlockingQueue);

        c.start();
        f.start();
    }
}

3.6线程的状态

        线程的状态有6种,分别是:
              new:Thread对象new好之后的状态
              runnable:Thread对象调用.start方法后
              blocked:阻塞
              waiting:等待
              timed_waiting:计时等待
             terminated:死亡

3.7线程池

    3.7.1概念
        线程池指存放线程的容器,大大提高了效率,不需要每需要执行一个任务就创建一个线程,执行完成后销毁线程;线程池的存在可以使线程任务结束后回到线程池,下次任务执行再直接使用
    3.7.2Executors默认线程池
      

 Executors:
        static ExecutorService newCachedThreadPool();
        这是一个静态方法,可以通过Executors这个类直接调用,返回一个ExecutorService对象
         ExecutorService提供了这些方法:
            submit(Runable r);//接收一个Runable接口的实现类,可以使用Lambda表达式
            submit(Callable c);//接收一个Callable接口的实现类,可以使用Lambda表达式
            shutdown();//摧毁线程池


    3.7.3Executors创建指定上限的线程池
        要想创建指定线程池容量的方法:
            

ExecutorService newFixedThreadPool(int nTheads);
            ScheduledExecutorService newScheduledThreadPool(int corePoolSize)  
            ScheduledExecutorService newSingleThreadScheduledExecutor()
            ExecutorService newSingleThreadExecutor()

3.7.4自己直接创建线程池
        上面的创建都是先创建Service,我们也可以直接创建线程池对象:
          

 public ThreadPoolExecutor(    
                int corePoolSize,//核心线程数量 2
                int maximumPoolSize,//最大线程数量 5
                long keepAliveTime,//空闲线程最大的空闲时间
                TimeUnit unit,//空闲时间单位
                BlockingQueue<Runnable> workQueue, //阻塞队列,存放等待执行的任务
                ThreadFactory threadFactory,//造线程的工厂  
                RejectedExecutionHandler handler//任务超过最大线程数+阻塞队列容量,拒绝的策略)


        3.7.4.1拒绝策略
                当任务超过最大线程数+阻塞队列容量会被拒绝,默认的拒绝策略是丢弃任务,并抛出异常
         3.7.4.2非默认任务拒绝策略
               

ThreadPoolExecutor.DiscardPolicy:
                *丢弃任务;
                ThreadPoolExecutor.DiscardOldestPolicy:
                *丢弃任务队列中等待时间最长的任务,再把最新的任务添加到队列中
                ThreadPoolExecutor.CallerRunsPolicy:
                *谁提交的这个任务,谁来执行这个任务;

附上线程池相关代码:

public class MyThreadPoolDemo1 {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService executorService = Executors.newCachedThreadPool();
        //使用Lambda表达式来创建函数式接口的对象
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在运行");
        });


        //使用Lambda表达式来创建函数式接口的对象
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在运行");
        });

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在运行");
        });

        executorService.shutdown();


    }
}

上边是通过Executors默认创建线程池,下边是自己new的线程池:

public class MyThreadPoolDemo3 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                5,//总线程数量
                2,//临时线程的空闲时间
                TimeUnit.SECONDS,//空闲时间单位
                new ArrayBlockingQueue<>(10),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的线程工厂
                new ThreadPoolExecutor.AbortPolicy());//当等待的线程超过总量+阻塞队列时的拒绝策略

        pool.submit(()->{
            System.out.println(Thread.currentThread().getName()+"RUNNING....");
        });
        pool.shutdown();
    }
}

3.8volatile-问题
        volatile关键字主要解决了内存不可见问题,就是当多个线程同时操作共享数据时,如果其中一个修改了共享数据,另外的线程无法及时获取到新的数据。
        使用volatile关键字修饰变量后,那么有关该变量操作的代码,就不会被机器指令替换
         内存不可见问题:jvm在执行java代码时,针对反复执行的代码,就会把这些代码标记为热点代码,这些热点代码在执行指定量次(10000),jvm会使用jit及时编译,把之前这些class指令替换为机器指令,反复执行的时候就再也不会读取新的数据了
        也可以使用synchronized解决内存不可见问题,因为synchronized中的代码会强制查看共享内存中的数据;
3.9原子性
        原子性指不可分割,举例:count++
            count++虽然看起来是一行代码,但是执行的时候对应3个指令:
                1.读取共享内存数据到变量副本;
                2.对变量副本中的数据进行自增1;
                3.把变量副本中的数据写入到共享内存;
                所以当多个线程执行时,count++可能造成线程安全问题,
                可以使用synchronized解决原子性导致的线程安全问题,将count++变成不可分的,但是synchronized是重量级锁,性能较低,也可以使用volatile+cas算法解决线程安全问题。

    3.9.1AtomicInteger
        jdk在并发包中提供了大量的Atomicxxx类,专门用于在多线程环境下编程,AtomicInteger是比较常用的一种:
      

AtomicIneger();
        int get(); //获取值
        int getAndIncrement();//以原子方式将当前值加1,这里返回的是自增前的值
        int incrementAndGet();// 以原子方式将当前值加1,这里返回的是自增后的值
        int addAndGet(int delta);//以原子方式将参数与对象中的值相加,并返回结果
        int getAndSet(int delta);//以原子方式设置为newValue的值,并返回旧值。


    3.9.2AtomicInteger-内存解析
        volatile关键字加CAS算法可以解决内存不可见问题
        CAS算法原理:在对共享数据进行操作时,把原来的旧值记录下来,在自己的栈内对数据进行操作之后要写入内存之前,比较现在内存中的值跟原来的旧值一样,那么说明没有其他线程对共享数据进行操作,那么对其进行修改(将修改的值写入内存);如果旧值不一样了,那么说明有其他线程对共享数据进行过其他操作了,那么需要再次获取内存的新值,再次进行操作,这个重新获取就是自旋。
   

 伪代码:
        while(true){
            ①读取内存值,赋值给旧值;
            ②给旧值加1,得到新值;
            ③if(旧值==内存值){
                内存值=新值;
                break;
            }
        }

3.10并发工具类

    3.10.1HashTable
        HashTable是线程安全的,因为它的底层方法都加了synchronized关键字,在操作共享数据时,将整个hash表都锁
    3.10.2并发工具类-ConcurrentHashMap
        ConcurrentHashMap继承了HashMap,所以父类的方法都是可以使用的
        ConcurrentHashMap在JDK1.7中,有一个长度为16的不可变的数组,在每个数组位置上有一个可扩容小数组,该位置存储的数据超过小数组的加载因子,则自动扩容为原长度的2倍
        ConcurrentHashMap在JDK1.8中,采用了 CAS + synchronized 来保证并发安全性
        put方法:
        1.计算key的hash值
        2.如果当前table还没有初始化先调用initTable方法将tab进行初始化
        3.tab中索引为i的位置的元素为null,则直接使用CAS将值插入即可
        4.当前正在扩容
        6.若当前为红黑树,将新的键值对插入到红黑树中
        7.插入完键值对后再根据实际大小看是否需要转换成红黑树
        8.对当前容量大小进行检查,如果超过了临界值(实际大小*加载因子)就需要扩容
    3.10.3ConcurrentHashMap的高性能
        在操作数据时只锁住索引处的链表,不像HashTable锁住一整张表
    3.10.4并发工具类-CountDownLatch
        如果一个线程需要等待其他多个线程执行完毕以后才能执行,可以使用CountDownLatch
        public CountDownLatch(int count)://让当前线程等待,计数器为0时唤醒
        public void await():
        public void countDown():

       附上完整代码:

//这里写了一个妈妈要等待孩子都吃完饺子才收拾碗筷的案例
import java.util.concurrent.CountDownLatch;
//孩子线程
public class ChildrenRunnable implements Runnable{
    private CountDownLatch countDownLatch;
    private int number;

    public ChildrenRunnable(CountDownLatch countDownLatch, int number) {
        this.countDownLatch = countDownLatch;
        this.number = number;
    }

    @Override
    public void run() {
        for (int i = 1; i <= number; i++) {
            System.out.println(Thread.currentThread().getName()+"正在吃第"+i+"个饺子");
        }
        countDownLatch.countDown();
    }
}
import java.util.concurrent.CountDownLatch;
//妈妈线程
public class MotherRunnable implements Runnable{
    private CountDownLatch countDownLatch;
    public MotherRunnable(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("妈妈去收拾碗筷了");
    }
}
import java.util.concurrent.CountDownLatch;

public class MyCountDownLatchDemo {
    public static void main(String[] args) {

        CountDownLatch countDownLatch = new CountDownLatch(3);//这里表示有三个线程要等待
        MotherRunnable mr = new MotherRunnable(countDownLatch);

        new Thread(mr).start();

        new Thread(new ChildrenRunnable(countDownLatch,7),"小黑").start();
        new Thread(new ChildrenRunnable(countDownLatch,9),"小红花").start();
        new Thread(new ChildrenRunnable(countDownLatch,11),"小狗").start();
    }
}

    3.10.5并发工具类-Semaphore

         限制资源的执行量时 使用Semaphore
         public Semaphore(int permits)
         public void acquire()//获取信号量----阻塞方法
         public void release()//释放信号量

完整代码:

//这里模拟一个路口同时只允许三辆车通过,必须拿到通行证才能通过,通过后归还通行证,然后别的车才能拿到通行证
public class MySemaphoreRunnable implements Runnable{
    //获取管理员对象
    private Semaphore semaphore = new Semaphore(3);
    @Override
    public void run() {
        try {
            semaphore.acquire();//相当于获取通行证
            System.out.println("拿到通行证,可以通行");
            Thread.sleep(2000);
            System.out.println("归还通行证");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MySemaphoreDemo {
    public static void main(String[] args) {
        MySemaphoreRunnable msr = new MySemaphoreRunnable();
        for (int i = 0; i < 100; i++) {
            new Thread(msr).start();
        }
    }
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值