JAVA线程进阶2

目录

一、Callable接口

二、创建线程的方法

三、ReentrantLock(可重入锁)

四、信号量(Semaphore)

五、CountDownLatch

六、线程不安全的集合类解决措施

一、Callable接口

1、作用

也是创建线程的接口,Runnable接口不注重返回结果,该接口关注线程的结果。

2、代码应用

eg:计算1+2+3......+1000

public class test {
    public static int sum=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 1000; i++) {
                    sum+=i;
                }
            }
        });
        t.start();
        t.join();
        System.out.println(sum);
    }
}

线程t没有返回结果时,只能等线程t执行完后主线程才能输出结果。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum=0;
                for (int i = 0; i <= 1000 ; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();
        System.out.println(futureTask.get()); //t线程还没执行完时get方法会被阻塞
    }
}

Callable接口的核心方法是call,有返回结果。FutureTask可用于异步获取执行结果,传入Callable的任务给FutureTask,将该任务可以放入线程中执行,调用FutureTask的get方法可以异步获取执行结果。

二、创建线程的方法

1、继承Thread类(创建单独类/匿名内部类)

2、实现Runnable接口(创建单独类/匿名内部类)

3、使用lambda表达式

4、实现Callable接口

5、线程池

6、ThreadFactory 线程工厂创建线程

三、ReentrantLock(可重入锁)

1、伪代码

lock()加锁,unlock()解锁(一定不能忘记)。

2、ReentrantLock锁的特点

①加锁方式有两种:(1)lock(),和synchronied一样,如果获取不到锁就死等;(2)trylock(),加锁,如果获取不到锁,则等待一段时间就放弃加锁;

②解锁需调用unlock(),且加锁了就必须解锁;

③提供了更强大的等待通知机制,搭配Condition类,实现等待通知,可以精准唤醒某个指定的线程;

④ReentrantLock默认是非公平锁,通过构造方法可以实现公平锁。

四、信号量(Semaphore)

1、概念

用来表示“可用资源的个数”,本质上是一个计数器。每次申请一个可用资源,就需要计数器-1(P操作,acquire);释放一个可用资源,就需要计数器+1(V操作,release)。

2、锁与信号量

锁是一种特殊的信号量,可用资源为1,加锁操作(P操作),解锁操作(V操作)。 

3、信号量例子

​
import java.util.concurrent.Semaphore;

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(4);
        semaphore.acquire();
        System.out.println("第一个p操作");
        semaphore.acquire();
        System.out.println("第二个p操作");
        semaphore.acquire();
        System.out.println("第三个p操作");
        semaphore.acquire();
        System.out.println("第四个p操作");
        semaphore.acquire();
        System.out.println("第五个p操作");
    }
}

​

import java.util.concurrent.Semaphore;

public class test2 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(4);
        semaphore.acquire();
        System.out.println("第一个p操作");
        semaphore.acquire();
        System.out.println("第二个p操作");
        semaphore.acquire();
        System.out.println("第三个p操作");
        semaphore.acquire();
        System.out.println("第四个p操作");
        semaphore.release();
        System.out.println("释放一个操作");
        semaphore.acquire();
        System.out.println("第五个p操作");
    }
}

4、应用场景

如果遇到了申请资源的场景,可以使用信号量实现。

五、CountDownLatch

1、应用场景

多个线程完成同一个任务,监督该任务是否已经完成。

2、主要的两个方法

①await:调用的时候被阻塞,等所有的线程执行完成后才会执行;

②CountDown:告诉CountDownLatch,该线程负责的任务已执行完。

import java.util.concurrent.CountDownLatch;

public class test3 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(10);
        for (int i = 0; i <= 9; i++) {
            int id = i;
            Thread t = new Thread(() -> {
                System.out.println("thread " + id);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 通知说当前的任务执行完毕了.
                countDownLatch.countDown();
            });
            t.start();
        }
        countDownLatch.await();
        System.out.println("所有任务已执行完毕");
    }
}

若上面的线程个数不够10个,则await一直被阻塞等待。

六、线程不安全的集合类解决措施

1、线程安全的集合类

Vector、Stack、Hashtable

2、多线程环境使用ArrayList

①加锁,synchronized或ReentrantLock

Collections.synchronizedList(new ArrayList);

synchronizedList是标准库提供的一个基于synchronized进行线程同步的List,关键操作上都带有synchronized

③CopyOnWriterArrayList(写时拷贝)

如果某个线程要修改ArrayList,则把原来的ArrayList拷贝一个副本,修改线程就修改这个副本,与此同时,读操作还是在原来的ArrayList上读,当线程修改好时,就用副本替代原来的ArrayList。上述过程修改就不需要加锁。

局限性:ArrayList不能太大(拷贝成本高);适用于多个线程读,一个线程修改。

3、多线程环境使用Queue

ArrayBlackingQueue、LinkedBlackingQueue、PriorityBlackingQueue

4、多线程环境使用HashMap

Hashtable是线程安全的,但是锁是加在整个方法上,只要两个线程调用同一个Hashtable就会出现锁冲突,效率低,只要两个线程操作同一个链表时才会出现锁冲突。

ConcurrentHashMap:

①最核心的改进:把每个链表的头结点作为锁对象,把一个大锁改为每个链表的独立小锁,降低了锁冲突的概率。

②充分利用了CAS特性,把一些不必要加锁的环节省略加锁了。

③针对读操作没有加锁操作。在底层实现时,把一些修改操作写为原子,读的时候要么是旧值,要么是修改后的值,不会读修改一半的值。

④针对扩容操作做出了单独的优化

在需要扩容的时候,需要把所有的数据重新拷贝一遍,ConcurrentHashMap不是一次性拷贝完,而是分很多次来拷贝,需要读取数据时,在新旧里都会找。

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ambition…

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值