java基础——多线程总结

谈起java多线程,我们首先想到的肯定是如何实现java多线程。下面我们就来聊一聊java多线程的实现方式。
一、java多线程的实现方式
1⃣️继承Thread类,重写run方法;
2⃣️实现Runnable接口,实现run方法;
以上两种方法,大家都比较熟悉了,这里不再过多介绍。
3⃣️实现Callable接口,实现call()方法;
具体用法如下:
第一步:创建Callable接口的实例化子对象;
第二步:将Callable对象传入FutureTask构造函数中,创建FutureTask对象;
第三步:将FutureTask对象传入Thread的构造函数中,创建Thread对象;
第四部:调用start()方法,启动线程;
启动线程后,通过调用FutureTask对象的get()方法可以获取线程的返回值。
通过以上的步骤我们不难看出这种创建线程的优缺点:
优点就是:可以获取线程的返回值。
缺点就是:创建线程的过程稍微复杂了一些。

package com.ysy.多线程;

import java.util.concurrent.Callable;

/**
 * @author shanyangyang
 * @date 2019/12/1
 * 实现Callable接口,实现call方法,其中泛型代表的就是返回值的类型。
 */
public class MehtodCall implements Callable<Integer> {
    @Override public Integer call() throws Exception {
        return 100;
    }
}
public class ThreadTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MehtodCall());
        Thread t = new Thread(futureTask);
        t.start();
        System.out.println(futureTask.get());
    }
}

4⃣️通过线程池的方式实现多线程,这也是在java开发项目中最常用的方法。
按道理说,我们前面已经学习了三种创建多线程的方法,为什么在项目中还要使用线程池的方法来创建线程呢?主要是因为这种方式有如下优点:
线程池:顾名思义就是存放线程的池子,系统已经把线程创建好了,使用的时候直接拿就好了。其实多线程的创建与销毁是比较消耗资源的,使用线程池减小了线程创建与销毁的开销。
线程池既是存放线程的地方,也对线程的生命周期进行管理。

既然谈到了线程池,我们不得不说常用的四种线程池类型了。
缓存类型的线程池(newCachedThredPool):线程池可以根据任务的多少,自动控制线程的数量,还是挺爽的用法。

//创建缓存类型的线程池:
        //线程池会根据任务的多少自动控制线程数量
        ExecutorService executorService = Executors.newCachedThreadPool();

固定数量的线程池(newFixedThreadPool):创建固定数量的线程,放在池子里面,当任务数量多余线程数量时,需要排队;少于的时候就会造成资源浪费。

//创建100个线程
ExecutorService executorService1 = Executors.newFixedThreadPool(100);

创建可以定时或者延时执行的线程池(newScheduledThreadPool):

 //long initialDelay:表示延迟时间                                                                
 //long period:表示运行次数                                                                      
 //TimeUnit unit:表示时间单位   
 //创建的时候需要传入创建线程的数量                                                                               
 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(100);
 //表示延迟11s,每11s执行一次,输出"实现了多线程"                                                             
 scheduledExecutorService.scheduleAtFixedRate(new Runnable() {                             
     @Override public void run() {                                                         
         System.out.println("实现了多线程");                                                     
                                                                                           
     }                                                                                     
 }, 1, 11, TimeUnit.SECONDS);     

创建单例线程池(newSingleThreadExecutor):它只会创建一个线程池,用这个线程池来完成任务。

ExecutorService executorService2 = Executors.newSingleThreadExecutor(); 

这里只是轻描淡写的提及了一下线程池创建多线程的过程。
个人觉得多线程这边还是可以再写一篇博客,仔细说明一下。这里不再赘述。

二、讲完多线程的创建方式,我们需要讲讲多线程的状态转换过程
这里我们不面面俱到的讲解,只讲解一些自己感兴趣的东西。
1、线程停止:不要用stop方法,让线程自然死亡;
2、线程阻塞:
join:由运行态到阻塞态,释放锁,合并的意思,比如在main方法中,A线程调用了join方法,那么就会先执行A线程,等待A线程执行完毕了,再执行main线程。

yield:由运行态到就绪态,不会释放锁;

sleep:休眠方法,不会释放锁;

三、下面和大家聊一聊线程的优先级——priority
优先级不严格代表执行顺序,只是代表执行线程的概率大小。

四、下面讲到了这篇博客的重点,也是多线程发面的重点知识——线程同步
记得三个月前参加新华三笔试题时,有道题目就是考什么叫做原子性?
什么叫做可见性?
原子性:在java中的原子性指一个操作不可分割,最常见的问题就是volatile遇到非原子操作时,就会存在线程同步问题。
可见性:指的是在一个线程内的值的改变,对另一线程是可见的。volatile关键字可以保证线程的可见性。
volatile关键字不允许线程内部缓存和指令重排。

synchronized关键字
可以用在方法上(StringBuffer线程安全的原理),同步代码块上;是通过加锁的形式实现同步的,资源开销比较大;

Lock接口
需要手动的加锁和释放锁,使用Lock接口使代码更加灵活;
和Condition一起用,可以通过条件获取锁和释放锁;

lock():获取锁,获取不到锁就一直阻塞等待;
tryLock():尝试去获取锁,获取到就返回true,否则返回false,不会发生阻塞;
tryLock(longtimeout,TimeUnit unit):在规定时间内去获取锁,获取到了就返回true,否则返回false;
ReentrantLock()。创建一个ReentrantLock实例;
unLock():释放锁

死锁形成的原因:
1、资源的互斥:甲拥有了A资源后,乙无法在拥有A资源;
2、不可剥夺性:甲拥有了A资源后,乙无法从甲那里争夺到A资源;
3、请求保持:甲拥有了A资源之后,又去请求B资源;在获取不到B资源的情况下,依然保持对A资源的拥有;
4、循环等待:甲获得了A资源,还缺B资源;乙获得了B资源,还缺A资源;

发布了16 篇原创文章 · 获赞 1 · 访问量 285

猜你喜欢

转载自blog.csdn.net/lovesman/article/details/103327515
今日推荐