Java教程

自用 Java 学习(多线程高级)

本文主要是介绍自用 Java 学习(多线程高级),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

线程池【了解】

线程的状态

NEW
    至今尚未启动的线程处于这种状态。 
RUNNABLE
    正在 Java 虚拟机中执行的线程处于这种状态。 
BLOCKED
    受阻塞并等待某个监视器锁的线程处于这种状态。 
WAITING
    无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。 
TIMED_WAITING
    等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。 
TERMINATED
    已退出的线程处于这种状态。 

 

我们自己频繁的去创建和销毁线程比较消耗系统资源,同时也比较浪费时间。所以Java语言的API给我们提供了线程池的技术,帮我们解决这个问题。

当创建一个线程池,其实就是创建了一个能够存储线程的容器,需要执行线程任务是,就从线程池中拿一个线程出来用,用完之后再还给线程池。

创建默认的线程池【掌握】

API提供了一个工具类叫Executors,可以用它的方法生成不同特点的线程池。

public static  ExecutorService newCachedThreadPool() 
    创建一个可以根据需要创建新线程的线程池。最多可以创建int最大值的线程数量
public static ExecutorService newFixedThreadPool(int nThreads)  
    创建一个固定长度的线程池
public static ExecutorService newSingleThreadExecutor() 
    创建单个线程的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
     创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 

  • 以newCachedThreadPool方法为例,演示向线程池提交任务

//创建线程池
//1.根据需要可以,新建线程的线程池
ExecutorService executorService = Executors.newCachedThreadPool();

//2.创建固定长度的线程池
//ExecutorService service = Executors.newFixedThreadPool(5);
//3.创建单一线程的线程池
//ExecutorService serive = Executors.newSingleThreadExecutor();

//提交线程任务
executorService.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行了");
    }
});

//提交线程任务
executorService.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行了");
    }
});
//关闭线程池
executorService.shutdown(); 

按周期执行某一个线程任务

ScheduledExecutorService 
     executorService = Executors.newScheduledThreadPool(3);
        //按照周期性执行某一个任务
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了");
            }
        },3,5, TimeUnit.SECONDS);

ThreadPoolExecutor创建线程池

线程池可以理解为一个火锅店,店内可以6张桌子是常开的,如果客人比较多时,可以临时再开3张桌子,客人不多时就把这临时的3张桌子收起来。当客人继续增加时常开的桌子和临时的桌子都用完了,就会启用排队机制,其他任务就在阻塞队列中排队

常开的6张桌子    --       核心线程数
临时的3张桌子    --         临时线程数
店内最大桌子数 --        最大线程数
排队通道      --      阻塞队列

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
参数解释:
    corePoolSize - 核心线程数。
    maximumPoolSize - 最大线程数。
    keepAliveTime - 临时线程存活的时间。
    unit - keepAliveTime 时间单位。
    workQueue - 阻塞队列。
    threadFactory -  创建线程的工厂。
    handler -  如果超过最大线程数,的拒绝方案。 

创建线程池的演示

ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(
    6, //核心线程数
    9, //最大线程数
    3, //空闲时间
    TimeUnit.SECONDS, //时间单位:秒
    new ArrayBlockingQueue<>(10),
    Executors.defaultThreadFactory(), //默认的工厂对象,由工厂对象帮我们生产线程
    new ThreadPoolExecutor.AbortPolicy() //拒绝的方式
); 

//提交任务
poolExecutor.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行了");
    }
}); 

拒绝策略

当提交的线程任务超过了最大线程数 + 阻塞队列长度,就会触发拒绝策略。

static class ThreadPoolExecutor.AbortPolicy 
          用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException. 
static class ThreadPoolExecutor.CallerRunsPolicy 
          用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。 (让main线程执行多余的任务)
static class ThreadPoolExecutor.DiscardOldestPolicy 
          用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。 
static class ThreadPoolExecutor.DiscardPolicy 
          用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。 

线程安全细节【了解】

volatile 关键字

当多个线程在访问共享数据时,不是直接访问的主内存的数据,而是在线程本地内存创建了一个和主内存一样的变量副本,对副本进行赋值,然后在重新赋值给主内存。

可能会出现多个线程临时存储的变量副本的值不一样,使用volatile修饰这个变量,就可以解决问题,强制让线程每次使用变量时,都从主内存中获取最新的值。

原子性

原子性指意思是多个操作是不可分割的原子项,他们要么同时成功,要么同时失败。 例如:当多线程在执行自增操作时,就不是原子性的。【本地内存】

自增操作,count++为例,其实底层是要完成三个步骤(这三个步骤是不可分割的)
    1.从主内存拷贝一个变量副本到线程本地内存
    2.对变量副本进行操作
    3.把变量副本的值写回主内存

但是由于CPU的随机性,可能一个线程没有完成这个三个步骤,执行权被其他线程抢走了,就破坏了原子性。

AtomicInteger类

直接使用int或者Integer对变量进行自增、修改、获取等操作,不能保证原子性,可能会出现线程安全的问题。

可使用synchronized来保证线程的安全性。这种方式解决问题的方式,是从悲观的角度出发,叫做悲观锁。

解决原子性的问题,Java提供了一系列的原子类。如AtomicInteger类就是其中的一个,它也表示整数,提供了一些方法可以对变量进行自增、获取、修改等操作,但是这些方法可以保证原子性,也能保证线程安全。

public int incrementAndGet()  
    对AtomicInteger包装的整数,先自增再获取。  //等价于 int num=10;  int c = ++num;

public int getAndincrement()
    对AtomicInteger包装的整数,先获取再自增。  //等价于 int num=10;  int c= num++;

public int getAndAdd(int delta)  
    对AtomicInteger包装的整数,先获取再增加。  //等价于 int num=10;
                                          //    int temp=num; //先获取num的值
                                          //    num+=5; //再对num值增加5
public int addAndGet(int delta)  
     对AtomicInteger包装的整数,先增加再获取      //等价于 int num=10; 
                                           //      num+=5; /对num增加5
                                           //      int temp=num; //赋值后的值
public int getAndSet(int newValue)  
      对AtomicInteger包装的整数,先获取再设置

//创建原子性的整数10
AtomicInteger ai = new AtomicInteger(10);

//先获取原来的值,再增加5
int result = ai.getAndAdd(5);
System.out.println("原来的值:" + result);//10
System.out.println("增加后的值:" + ai);//15

//先获取原来的值,再设置新的值
result = ai.getAndSet(20);
System.out.println("原来的值:" + result); //15
System.out.println("新的值:" + ai); //20

AtomicInteger类保证原子性的原理如下图所示

 

常见的线程安全的类【了解】

ArrayList和Vector
    ArrayList: 数组结构,线程不安全(效率高)
    Vector: 数组结构,线程安全的(效率低)

HashMap和Hashtable 
    HashMap: 哈希表结构(数组+链表),线程不安全的(效率高)
    Hashtable: 哈希表结构(数组+链表),线程安全的(同步代码块,效率低)
    ConcurrentHashMap: 哈希表结构(数组+链表+红黑树),线程安全的(同步代码块+CAS算法,效率高) 
   
        
StringBuilder和StringBuffer
    StringBuilder: 线程不安全的(效率高)
    StringBuffer: 线程安全的(效率低) 

 

CountDownLatch类

使用场景:当需要某一个线程在其他线程执行完毕之后才执行。

//第一个孩子
public class MyChildThread1 extends Thread {
    private CountDownLatch cdl;
    //利用构造方法,来给cdl赋值
    public MyChildThread1(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //1.孩子吃东西
        System.out.println("小刚吃完饺子了");
        //2.吃完说一声
        cdl.countDown();
    }
}


//第二个孩子
public class MyChildThread2 extends Thread {
    private CountDownLatch cdl;

    //利用构造方法,来给cdl赋值
    public MyChildThread2(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //1.孩子吃东西
        System.out.println("小黄吃完饺子了");
        //2.吃完说一声
        cdl.countDown();
    }
}


//妈妈线程,等待前面两个孩子线程执行完毕之后再执行。
public class MyMother extends Thread{
    private CountDownLatch cdl;
    //利用构造方法,来给cdl赋值
    public MyMother(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //1.先等待
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.收拾碗筷
        System.out.println("吃完饭洗完"); 
    }
}



public static void main(String){
    //创建一个CountDownLatch对象,记录执行的线程数量
    CountDownLatch cd = new CountDownLatch(2);
    //三个线程共用一个CountDownLatch对象(计数器)
    new MyMother(cd).start();
    new MyChildThread1(cd).start();
    new MyChildThread2(cd).start();
}

Semaphore类

SemaPhore类用来控制正在执行的线程数量,相当于一个管理员的角色,它可以给线程发许可证,得到许可证的线程才能执行,没有得到许可证的线程就必须等待。通过SemaPhore的构造方法,可以指定有多少个线程获得许可。

 public class MyRunnable implements Runnable{
    //创建对象,控制线程执行的数量为2
    Semaphore sp = new Semaphore(2); 
    @Override
    public void run(){
        try {
            //发同行许可
            sp.acquire(); 

            //线程执行的代码
            System.out.println(Thread.currentThread().getName()+"执行了");
            Thread.sleep(3000);

            //释放同行许可
            sp.release();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



public class Demo6 {
    public static void main(String[] args) {
        MyRunnable mr=new MyRunnable();

        for (int i = 0; i < 100; i++) {
            new Thread(mr,"线程"+i).start();
        }
    }
}

 

 

 

 

 

这篇关于自用 Java 学习(多线程高级)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!