今日内容
- Synchronized关键字【重点】
- 高并发下JDK提供一堆的线程安全有关类【理解】
synchronized关键字【重点】
- 之前的AtomicInteger类只能保证"变量"的原子性操作,而对多行代码进行"原子性"操作,使用AtomicInteger 类就不能达到效果了。
- 线程安全问题都是由全局变量及静态变量引起的。而每个线程操作这个变量都需要很多步骤:获取变量的值、打印变量的值、更改变量的值,而一个线程在执行某一步骤时都可能被暂停,而另一个线程会执行,这同样会导致多个线程访问同一个变量,最终导致这个变量的值不准确。
synchronized关键字概述
- synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。
- synchronized被称为“重量级的锁”方式,也是“悲观锁”——效率比较低。
- synchronized有几种使用方式:
a).同步代码块 b).同步方法 c).Lock锁
解放方法一:同步代码块
同步代码块:
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访 问。格式:
synchronized(同步锁对象){
需要同步操作的代码
}
- 使用同步锁代码演示
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
}
解决方法二: 同步方法
- 同步方法:使用
synchronized
修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。 - 格式
public synchronized void method(){
可能会产生线程安全问题的代码
}
- 同步方法代码演示
格式:
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
解决代码:
/**
* 卖票任务
*/
public class TicketTask implements Runnable{
//定义变量,表示初始有100张票
int count = 100;
@Override
public void run() {
while (true){
sellTicket();
}
}
public synchronized void sellTicket(){
if (count > 0) {
//先判断,后卖票
System.out.println("卖出第"+count+"张票!");
//票数要减少
count--;
}
}
}
注意
- 同步锁和同步代码块
1.同步代码块和同步方法原理一样,同步代码块的同步锁需要自己指定,而同步方法的同步锁,默认使用当前对象this。
2.对于非static方法,同步锁就是this。
对于static方法,默认使用当前方法所在类的字节码对象(类名.class)。
解决方法三: Lock锁
Lock是一个接口, 需要使用其实现类 ReentrantLock。
public ReentrantLock();
同步代码块/同步方法具有的功能Lock都有
Lock锁也称同步锁(悲观锁),加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
Lock锁的代码演示
/**
* 卖票任务
*/
public class TicketTask implements Runnable{
//定义变量,表示初始有100张票
int count = 100;
//创建一个Lock锁
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
lock.lock();
if (count > 0) {
//先判断,后卖票
System.out.println("卖出第"+count+"张票!");
//票数要减少
count--;
}
lock.unlock();
}
}
}
并发包
并发包概述:
在JDK的并发包里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。使用这些集合或者工具类时,能保证
CopyOnWriteArrayList
- ArrayList的线程不安全最终结果可能会抛异常,或者最终集合大小是不正确的。
ArrayList和CopyOnWriteArrayLiST安全性的代码演示
public class MyThread extends Thread {
// public static List<Integer> list = new ArrayList<>();//线程不安全的
public static List<Integer> list = new CopyOnWriteArrayList<>();//保证线程安全
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("添加完毕!");
}
}
public class TestArrayList {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("最终集合的长度:" + MyThread.list.size());
}
}
CopyOnWriteArraySet
HashSet仍然是线程不安全的:
CopyOnWriteArraySet和HashSet安全性代码演示
public class MyThread extends Thread {
// public static List<Integer> list = new ArrayList<>();//线程不安全的
public static List<Integer> list = new CopyOnWriteArrayList<>();//保证线程安全
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("添加完毕!");
}
}
ConcurrentHashMap
- HashMap是线程不安全的。
- Hashtable是线程安全的,但效率低
- ConcurrentHashMap是线程安全的,但效率高
- HashMap和ConcurrentHashMap 安全性代码演示
public class MyThread extends Thread {
//public static Set<Integer> set = new HashSet<>();//线程不安全的
public static Set<Integer> set = new CopyOnWriteArraySet<>();//线程安全的
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
set.add(i);
}
System.out.println("添加完毕!");
}
}
public class TestSet {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
//主线程也添加10000个
for (int i = 10000; i < 20000; i++) {
MyThread.set.add(i);
}
Thread.sleep(1000 * 3);
System.out.println("最终集合的长度:" + MyThread.set.size());
}
}
- ConcurrentHashMap比Hashtable效率高的原因
Hashtable对哈希表进行的是全表加锁,ConcurrentHashMap是对局部加锁(对桶或者说链表加锁)同时使用CAS机制。
ConcurrentHashMap 线程安全的map类, 16 把锁,16 个线程
线程1 锁第一个链表
线程2 锁第二个链表
Hashtable 线程安全的map类, 把整个集合加了一把锁, 16 线程, 只有一个线程能用 , 15 个得等
ConcurrentHashMap高效的原因:CAS + 局部(synchronized)锁定
CountDownLatch
CountDownLatch的作用
允许当前一个或多个线程等待其他线程完成操作。-
CountDownLatch的API
构造方法:
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象,count指等待几个线程结束
成员方法:
public void await() throws InterruptedException// 让当前线程等待
public void countDown() // 计数器进行减1
CountDownLatch的案例代码
需求:
线程1要执行打印:A和C,线程2要执行打印:B
我们需要这样的结果: 线程1 先打印A 线程2打印B 之后 线程1再打印C
A B C
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
//0.创建一个CountDownLatch
CountDownLatch latch = new CountDownLatch(1);
//1.创建两个线程
Thread t1 = new MyThread1(latch);
Thread t2 = new MyThread2(latch);
t1.start();
Thread.sleep(5000);
t2.start();
}
}
public class MyThread1 extends Thread {
private CountDownLatch latch;
public MyThread1(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
System.out.println("A....");
try {
latch.await();//让当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C....");
}
}
public class MyThread2 extends Thread {
private CountDownLatch latch;
public MyThread2(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
System.out.println("B....");
//让latch的计数器减少1
latch.countDown();
}
}
CyclicBarrier
CyclicBarrier 的作用
让一组线程到达一个屏障 (也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。-
CyclicBarrier 的API
- 构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时,优先执行。第一个参数为需要的多少个线程到达。第二个参数为都到达之后需要执行的任务
public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
- 构造方法:
CyclicBarrier 的代码演示
需求: 部门开会,假设部门有五个人,五个人都到达了才执行开会这个任务
public class TestPersonThread {
public static void main(String[] args) throws InterruptedException {
//0.创建一个CyclicBarrier
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("人都齐了,开会吧");
}
});
//1.创建五个线程
PersonThread p1 = new PersonThread(barrier);
PersonThread p2 = new PersonThread(barrier);
PersonThread p3 = new PersonThread(barrier);
PersonThread p4 = new PersonThread(barrier);
PersonThread p5 = new PersonThread(barrier);
//2.开启
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
//Thread.sleep(6000);
//System.out.println("人都到了,开会吧...");
//要求,人没到不开会,都到了立刻开会!!!
}
}
public class PersonThread extends Thread {
private CyclicBarrier barrier;
public PersonThread(CyclicBarrier barrier){
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(6)*1000);
System.out.println(Thread.currentThread().getName() + " 到了! ");
//调用 barrier的await 表示线程到了
try {
barrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
补充:
Math的静态方法
public static double random(); //获取一个0(包括)到1(不包括)的正小数
Semaphore
Semaphore的作用
用于控制并发线程的数量-
Semaphore的API
- 构造方法:
public Semaphore(int permits) //permits 表示许可线程的数量
public Semaphore(int permits, boolean fair) //fair 表示公平性,如果这个设为 true 的 话,下次执行的线程会是等待最久的线程
- 成员方法:
public void acquire() throws InterruptedException //表示获取许可
public void release() //release() 表示释放许可
- 构造方法:
Semaphore的使用案例
public class MyThread extends Thread {
private Semaphore semaphore;
public MyThread(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run(){
//从Semaphore获取线程的许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 进入 时间=" + System.currentTimeMillis());
try {
Thread.sleep(100*new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 结束 时间=" + System.currentTimeMillis());
//归还semaphore线程的许可
semaphore.release();
}
}
public class TestDemo {
public static void main(String[] args) {
//0.创建Semaphore
Semaphore semaphore = new Semaphore(3);
//最多的并发线程数量为1
for (int i = 0; i < 10; i++) {
new MyThread(semaphore).start();
}
}
}
Exchanger
- Exchanger作用
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。 - Exchanger的API
- 构造方法:
public Exchanger()
- 成员方法:
public V exchange(V x) // 参数为发给其他线程数据,返回值为其他线程返回的数据
- 构造方法:
- Exchanger的代码演示
public class TestExchanger {
public static void main(String[] args) throws InterruptedException {
//0.创建一个线程间数据交互对象
Exchanger<String> exchanger = new Exchanger<String>();
//1.创建线程A
ThreadA aThread = new ThreadA(exchanger);
aThread.start();
//休眠
Thread.sleep(5000);
ThreadB bThread = new ThreadB(exchanger);
bThread.start();
}
}
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
System.out.println("线程A,要将礼物AAA,送给线程B...");
//调用exchanger
String result = null;
try {
result = exchanger.exchange("AAA");//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A,获取到线程B的礼物:"+result);
}
}
public class ThreadB extends Thread {
private Exchanger<String> exchanger;
public ThreadB(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
System.out.println("线程B,要将礼物BBB,送给线程A...");
String result = null;
try {
result = exchanger.exchange("BBB");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B,获取到线程A的礼物:"+result);
}
}
今日小结
能够使用同步代码块解决线程安全问题【重点】
synchronized(锁对象){
需要同步的代码(需要保证原子性的代码)
}
锁对象,可以是任意对象
能够使用同步方法解决线程安全问题牌【重点】
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
能够使用Lock锁解决线程安全问题牌【重点】
Lock lock = new ReentrantLock();
lock.lock();
需要同步的代码(需要保证原子性的代码)
lock.unlock();
能够说明volatile关键字和synchronized关键字的区别
volatile 能解决有序性和可见性
原子类 能解决变量操作的原子性(有序性和可见性)
synchronized 能解决多句代码的原子性(有序性和可见性)
能够描述CopyOnWriteArrayList类的作用
代替多线程的情况,线程安全的ArrayList集合
能够描述CopyOnWriteArraySet类的作用
代替多线程的情况,线程安全的HashSet集合
能够描述ConcurrentHashMap类的作用
代替多线程的情况,线程安全的HashMap集合(比HashTable效率更好)
能够描述CountDownLatch类的作用
可以允许一个线程等待另外一个线程执行完毕后再继续执行
能够描述CyclicBarrier类的作用
让一组线程都到达某种条件后再执行某个任务
能够表述Semaphore类的作用
控制多线程并发的最大数量
能够描述Exchanger类的作用
用于线程间的通信(数据交换)