1.线程和进程
- 进程:一个程序,QQ.exe Music.exe 程序的集合;
- 一个进程往往可以包含多个线程,至少包含一个!
- Java默认有几个线程? 2 个 mian、GC
- 线程:开了一个进程 Typora,写字,自动保存(线程负责的)
- 对于Java而言:Thread、Runnable、Callable
1.1 Java可以开启线程么?不可以
Java把线程映射到内核上去,让操作系统自己实现
是通过调用本地方法实现的
//进入new Thread().start();里面查看源码 public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //本地方法底层的C++,Java无法直接操作硬件 private native void start0();查看CPU的核数
public class TestThread { public static void main(String[] args) { //获取CPU的核数 //CPU 密集型,IO 密集型 System.out.println(Runtime.getRuntime().availableProcessors()); } }并发编程的本质:充分利用CPU的资源
1.2 线程的状态
public enum State {
// 新生
NEW,wait/sleep 区别
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
3、使用的范围是不同的
wait
.
sleep 可以再任何地方睡
4、是否需要捕获异常
wait 不需要捕获异常
sleep 必须要捕获异常
3、Lock锁(重点)
传统 Synchronized
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
} 2 Lock锁
2.1 使用传统的synchronized
package com.snowdong;
/**
* 真正的多线程开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1、属性,方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
final Ticket ticket=new Ticket();
//@functionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i=1;i<40;i++){
ticket.sale();
}
},"Thread A").start();
new Thread(()->{
for (int i=1;i<40;i++){
ticket.sale();
}
},"Thread B").start();
new Thread(()->{
for (int i=1;i<40;i++){
ticket.sale();
}
},"Thread C").start();
}
}
//资源类,面向对象的编程OOP
class Ticket{
//属性、方法
private int ticketNum=30;
//卖票的方式
//synchronized 本质:队列,锁
public synchronized void sale(){
if(ticketNum>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum+"票,剩余:"+(--ticketNum));
}
}
} 2.2 使用Lock接口
- 公平锁:十分公平:可以先来后到
- 非公平锁:十分不公平:可以插队 (默认)
还是买票的代码,使用Lock锁
package com.snowdong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 真正的多线程开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1、属性,方法
*/
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket=new Ticket2();
//@functionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码}
new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread A").start();
new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread B").start();
new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread C").start();
}
}
//Lock三部曲
//1. new ReentrantLock()
//2. lock.lock();//加锁
//3. finally=>lock.unlock();//解锁
class Ticket2{
//属性、方法
private int ticketNum=30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();//加锁
try{
//业务代码
if(ticketNum>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum+"票,剩余:"+(--ticketNum));
}
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
} 2.3 synchronized 和 Lock 的区别
- Synchronized 内置的Java关键字,Lock是一个Java类
- Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized无法得知是否获取锁成功,Lock则可以通过tryLock得知加锁是否成功
- Synchronized 会自动释放锁,Lock必须手动释放锁。如果不释放,死锁
- Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁就不一定会等待下去
- Lock可以通过tryLock得知加锁是否成功,然后判断是否等待
- Synchronized 可重入锁,不可以中断的,非公平;Lock锁,可重入锁,可以判断锁,公平和非公平自己设置
- Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
可重入锁:可以多次获取同一个锁,释放也要多次释放
锁是同步监视器,保证线程安全,是实现同步互斥的一种机制你
2.4 生产者和消费者问题
2.4.1 生产者和消费者问题的Synchronized版
package com.snowdong;
/**
* 线程之间的通讯问题:生产者和消费者,等待唤醒和通知唤醒
* 线程之间交替执行:线程A 线程B操作同一个变量,num=0
* A执行num+1
* B执行num-1
*/
public class ProducerAndConsumer {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for(int i=0;i<20;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread A").start();
new Thread(()->{
for(int i=1;i<20;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread B").start();
new Thread(()->{
for(int i=1;i<20;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread C").start();
new Thread(()->{
for(int i=1;i<20;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread D").start();
}
}
//1. 判断等待,2.进行业务,3.通知唤醒
class Data{
private int number=0;//数字,资源类
//+1
public synchronized void increment() throws InterruptedException {
if(number !=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"===>"+number);
//通知其他线程我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if(number ==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"===>"+number);
//通知其他线程我-1完毕了
this.notifyAll();
}
}问题存在,A B C D四个线程的时候会出现错误,出现了num=2,3...的情况!虚假唤醒
解释:
- 拿两个加法线程A、B来说,比如A先执行(num=1),执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕(num=0)并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行(num=2)。
- 如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行
2.4.2 虚假唤醒
- 一般而言线程调用wait()方法后,需要其他线程调用notify,notifyAll方法后,线程才会从wait方法中返回, 而虚假唤醒(spurious wakeup)是指线程通过其他方式,从wait方法中返回
- 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
- 1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁
* A thread can also wake up without being notified, interrupted, or * timing out, a so-called <i>spurious wakeup</i>. While this will rarely * occur in practice, applications must guard against it by testing for * the condition that should have caused the thread to be awakened, and * continuing to wait if the condition is not satisfied. In other words, * waits should always occur in loops, like this one: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(timeout); * ... // Perform action appropriate to condition * } * </pre> * (For more information on this topic, see Section 3.2.3 in Doug Lea's * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming * Language Guide" (Addison-Wesley, 2001). * * <p>If the current thread is {@linkplain java.lang.Thread#interrupt() * interrupted} by any thread before or while it is waiting, then an * {@code InterruptedException} is thrown. This exception is not * thrown until the lock status of this object has been restored as * described above.
if 改为 while 判断,就可以避免
wait方法可以分为三个操作:
(1)释放锁并阻塞
(2)等待条件cond发生
(3)获取通知后,竞争获取锁
wait方法一定是要获取到锁后,才会返回
2.4.3 JUC版的生产者和消费者问题
官方实现代码:
package com.snowdong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通讯问题:生产者和消费者,等待唤醒和通知唤醒
* 线程之间交替执行:线程A 线程B操作同一个变量,num=0
* A执行num+1
* B执行num-1
*/
public class ProducerAndConsumer {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for(int i=0;i<200;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread A").start();
new Thread(()->{
for(int i=1;i<200;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread B").start();
new Thread(()->{
for(int i=1;i<200;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread C").start();
new Thread(()->{
for(int i=1;i<200;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread D").start();
}
}
//1. 判断等待,2.进行业务,3.通知唤醒
class Data{//数字,资源类
private int number=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
//condition.await();//等待
//condition.signalAll();//唤醒全部
public void increment() throws InterruptedException {
lock.lock();//加锁
try{
//业务代码
while(number !=0){
condition.await();//等待
}
number++;
System.out.println(Thread.currentThread().getName()+"===>"+number);
//通知其他线程我+1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();//加锁
try{
//业务
while(number ==0){
condition.await();//等待
}
number--;
System.out.println(Thread.currentThread().getName()+"===>"+number);
//通知其他线程我-1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
} 得到的运行结果是线程随机的同步执行,即随机的状态。
Condition:精准的通知和唤醒线程
package com.snowdong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLockCondition {
public static void main(String[] args) {
Data03 data03=new Data03();
new Thread(()-> {
for(int i=1;i<10;i++)
data03.printA();
},"Thread A").start();
new Thread(()-> {
for(int i=1;i<10;i++)
data03.printB();
},"Thread B").start();
new Thread(()-> {
for(int i=1;i<10;i++)
data03.printC();
},"Thread B").start();
}
}
class Data03{//资源类
private Lock lock=new ReentrantLock();
private Condition condition1=lock.newCondition();
private Condition condition2=lock.newCondition();
private Condition condition3=lock.newCondition();
private int number=1;//1A 2B 3C
public void printA(){
lock.lock();
try{
//业务代码:先判断-》执行-》通知
while(number!=1)
condition1.await();//等待条件condition1这个条件被唤醒
System.out.println(Thread.currentThread().getName()+"=>AAAAAAAA");
//唤醒,唤醒指定的人,B
number=2;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
//业务代码:先判断-》执行-》通知
while(number!=2)
condition2.await();//等待条件condition2这个条件被唤醒
System.out.println(Thread.currentThread().getName()+"=>BBBBBBB");
//唤醒,唤醒指定的人,C
number=3;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
//业务代码:先判断-》执行-》通知
while(number!=3)
condition3.await();//等待条件condition3这个条件被唤醒
System.out.println(Thread.currentThread().getName()+"=>CCCCCCCC");
//唤醒,唤醒指定的人,A
number=1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
} 3. 8锁现象
3.1 同一个对象两个synchronized方法
代码如下:
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 8锁就是关于锁的8个问题,
* 1、标准情况下,两个线程那个先打印,发信息 or 打电话
* 2、sendSms延迟了4秒,谁先打印呢
*/
public class TestLock01 {
public static void main(String[] args) {
Phone phone=new Phone();
//锁的存在
new Thread(()->{
phone.sendSms();
},"Thread A").start();
new Thread(()->{
phone.call();
},"Thread B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!
//两个方法用的同一个锁,谁先拿到谁就执行
public synchronized void sendSms() {
/*try {
//发送信息的方法,先给它睡眠4秒
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
} 运行结果:(在sendSms方法中加不加延迟4秒,都是同一个结果)
发信息 打电话
原因解释:synchronized 锁的对象是方法的调用者。
- 当main主线程中依次执行时,线程A先拿到sendSms方法的调用者phone对象的锁,然后进入延迟;同时线程B去执行phone.call()方法时,发现对象phone已上锁,则进入phone的等待队列中,等待别的线程解锁。
3.2 同一个对象操作synchronized方法和普通方法
代码如下
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 3、增加一个普通方法后!先执行发短信还是Hello?
*
*/
public class TestLock02 {
public static void main(String[] args) {
Phone2 phone=new Phone2();
//锁的存在
new Thread(()->{
phone.sendSms();
},"Thread A").start();
try {
//发送信息的方法,先给它睡眠2秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"Thread " +
"Thread C").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者!
//两个方法用的同一个锁,谁先拿到谁就执行
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
} 运行结果:(在sendSms方法中加不加延迟4秒,都测试)
发现synchronized方法和同步方法没有锁的联系,随机打印(需要加延迟才能出现所有情况)
原因解释:synchronized同步方***锁调用对象,普通方法不需要同步,直接主线程运行就行。
3.3 两个对象操作synchronized方法
代码如下:
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 4、两个对象,两个同步方法,发短信还是打电话?
*
*/
public class TestLock02 {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁!
Phone2 phone1=new Phone2();
Phone2 phone2=new Phone2();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"Thread A").start();
new Thread(()->{
phone2.call();
},"Thread B").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者!
//两个方法用的同一个锁,谁先拿到谁就执行
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
try {
//发送信息的方法,先给它睡眠4秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打电话");
}
//这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
} 运行结果:(在两个方法中都加延迟,测出所有情况)
随机打印
原因解释:synchronized同步方***锁调用对象,两个线程中两个对象调用同步方法时会获取各自调用对象的锁,两把锁之间不关联。
3.4 同一个类的两个静态的同步方法,只有一个对象
代码如下:
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 5、增加两个静态的同步方法,只有一个对象,先打印那个呢
*/
public class TestLock03 {
public static void main(String[] args) {
Phone phone1=new Phone();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"Thread A").start();
new Thread(()->{
phone1.call();
},"Thread B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!
//static 静态方法,类已加载就有,锁的是Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public static synchronized void call(){
System.out.println("打电话");
}
} 运行结果:(在sendSms方法中加上延迟)
发信息 打电话
原因解释:静态的同步方***锁住调用对象的Class
- 所以当下线程A通过对象访问类的静态同步方法时,会拿到Phone类的Class的锁,则线程B也是同样调用静态的同步方法因拿不到Class锁陷入等待。
3.5 同一个类的两个对象,类有两个静态的同步方法
代码如下:
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 6、两个对象,增加两个静态的同步方法对比
*/
public class TestLock03 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone phone1=new Phone();
Phone phone2=new Phone();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"Thread A").start();
new Thread(()->{
phone2.call();
},"Thread B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!
//static 静态方法,类已加载就有,锁的是Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public static synchronized void call(){
System.out.println("打电话");
}
} 运行结果:同上
发信息 打电话
原因解释:静态的同步方***锁住调用对象的Class
- 所以当下线程A通过对象访问类的静态同步方法时,会拿到Phone1类的Class的锁,因为phone1和phone2都是类Phone的实例,所以其对应的Class类模板是同一个,则当线程B通过对象phone2调用类的静态的同步方法,因拿不到Class锁陷入等待。
- 推知:可能跟类的静态方法加载(在类实例化之前就加载了),存储位置(类静态方法跟对象实例的普通方法,不知道存储位置是不是都在方法区)
3.6 一个对象,一个静态的同步方法,一个普通同步方法
代码如下:
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 7、两个对象,增加两个静态的同步方法对比
*/
public class TestLock03 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone phone1=new Phone();
//线程A 通过phone1调用类的静态同步方法
new Thread(()->{
phone1.sendSms();
},"Thread A").start();
/* //捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//线程B 通过phone1调用对象的普通同步方法
new Thread(()->{
phone1.call();
},"Thread B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!
//static 静态方法,类已加载就有,锁的是Class
public static synchronized void sendSms() {
try {
//发送信息的方法,先给它睡眠4秒
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
} 运行结果:是随机的
解释:普通同步方法锁的是调用者对象,静态同步方锁的是Class对象。两个是锁的是不同的对象,两把锁。
3.7 两个对象,一个静态的同步方法,一个普通同步方法
代码如下:
package com.snowdong;
import java.util.concurrent.TimeUnit;
/**
* 8、两个对象,一个静态的同步方法,一个普通的同步方法
*/
public class TestLock03 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone phone1=new Phone();
Phone phone2=new Phone();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"Thread A").start();
/* //捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(()->{
phone2.call();
},"Thread B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!
//static 静态方法,类已加载就有,锁的是Class
public static synchronized void sendSms() {
try {
//发送信息的方法,先给它睡眠4秒
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
} 运行结果:随机结果
解释:原理同上。
4. 集合类不安全
4.1 List不安全
package com.snowdong;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//线程不安全指的是集合内部的多个方法是不能保证原子性操作的,(同步操作?)
public class TestList {
public static void main(String[] args) {
//ArrayList集合在多线程下会出现java.util.ConcurrentModificationException 并发修改异常
//List<String> list=new ArrayList<>();
/**解决方案
* List是可重复有序集合
* 1、List<String> list=new Vector<>();
* 2、List<String> list= Collections.synchronizedList(new ArrayList<>());
* 3、List<String> list=new CopyOnWriteArrayList<>();
*/
//写入时复制,COW计算机程序设计领域的一种优化策略
//在多个线程调用时,list读取无误,写入时有可能会覆盖以前新add的值,导致错
//读写分离,即先复制一份,写的时候判断现在值跟复制值是否一致,一致则写入。
//源码中CopyOnWriteArrayList().add()使用了Lock锁
List<String> list=new CopyOnWriteArrayList<>();
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}).start();
}
}
} 4.2 Set不安全
Set是不可重复,无序集合
package com.snowdong;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 同理可证 : ConcurrentModificationException
* //1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
*/
public class TestSet {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();//不安全
//使用Collections工具类
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <=30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
} hashSet的底层实现:
public HashSet() {
map = new HashMap<>();
}
//add set 本质就是map key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//固定值 4.3 Map不安全
Map基本要素:初始容量,最大容量,加载因子
package com.snowdong;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class TestMap {
public static void main(String[] args) {
/* //出现ConcurrentModificationException,默认new HashMap<>(16,0.75);
Map<String,String> map=new HashMap<>();*/
//使用Collections工具类
Map<String,String> map= Collections.synchronizedMap(new HashMap<>());
//使用ConcurrentHashMap
//Map<String,String> map=new ConcurrentHashMap<>();
for(int i=1;i<=30;i++){
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
} 待定专题补充ConcurrentHashMap的原理
4.4 Callable
- 可以有返回值
- 可以抛出异常
- 方法不同,run()/call()
具体实现方式代码如下:
package com.snowdong;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启的方式
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();//其中futureTask是Runnable接口的实现类
// new Thread(new FutureTask<V>(new Callable())).start();
MyThread myThread=new MyThread();
//FutureTask构造函数接受Callable接口
FutureTask futureTask=new FutureTask(myThread);//适配类
new Thread(futureTask,"Thread A").start();
new Thread(futureTask,"Thread B").start();//结果会被缓存,效率高
//这个get方法可能会产生阻塞,把它放到最后
//或者使用异步通信来处理
Integer integer= (Integer) futureTask.get();
System.out.println(integer);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+" 调用了Call");
return 9527;
}
} 具体来说:
- 线程中任务执行,同一个任务执行会缓存结果
- 结果可能需要等待,会阻塞
5 常用的辅助类
5.1 CountDownLatch减法器
package com.snowdong;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
//设置总数为6的计数器,必须要执行任务的时候,在使用
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" go out");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
//注释或者不注释下行,观看运行结果
countDownLatch.await();//等待计数器归零,然后再向下执行
System.out.println("Close door");
}
} 说明:
- countDownLatch.countDown();//数量-1
- countDownLatch.await();//等待计数器归零,然后再向下执行
- 每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续
执行!
5.2 CyclicBarrier加法器
可以简单理解为:加法计算器
package com.snowdong;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.w3c.dom.ls.LSOutput;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class TestCyclicBarrier {
public static void main(String[] args) {
/**
* 一个案例说明:集齐7颗龙珠召唤神龙
*/
//召唤神龙的线程
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 0; i <7 ; i++) {
final int temp=i;
//lambda能操作到i么?
// 不能,i是for循环类的局部变量,lambda里是局部内部内了,需传参数
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
} 说明:CyclicBarrier 的构造函数,在指定数目线程await发生后,执行Runnable接口实例
/**
* Creates a new {@code CyclicBarrier} that will trip when the
* given number of parties (threads) are waiting upon it, and which
* will execute the given barrier action when the barrier is tripped,
* performed by the last thread entering the barrier.
*
* @param parties the number of threads that must invoke {@link #await}
* before the barrier is tripped
* @param barrierAction the command to execute when the barrier is
* tripped, or {@code null} if there is no action
* @throws IllegalArgumentException if {@code parties} is less than 1
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
} 5.3 Semaphore信号量
抢车位的案例代码:
package com.snowdong;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class TestSemaphore {
public static void main(String[] args) {
//线程数量:停车位,限流.定义了一个3组信号量(许可证)
Semaphore semaphore=new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
//获取许可证
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到了车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}).start();
}
}
} 运行结果:
Thread-0抢到了车位 Thread-1抢到了车位 Thread-2抢到了车位 Thread-2离开了车位 Thread-1离开了车位 Thread-0离开了车位 Thread-4抢到了车位 Thread-3抢到了车位 Thread-5抢到了车位 Thread-3离开了车位 Thread-5离开了车位 Thread-4离开了车位
说明:
- semaphore.acquire();请求获得许可证。假设如果已满,则等待,直到被释放
- semaphore.release();释放许可证。会将当前的许可证(信号量)+1,然后唤醒等待的线程
- 主要用于,多个共享资源互斥的使用!并发限流,控制最大的线程数



京公网安备 11010502036488号