目录
目录
传统生产者消费真问题(synchronized wait notify)
JUC主要是java8下的三个包
提取首字母
jdk1.8文档下载链接:https://pan.baidu.com/s/1VW8D9eBXcuTpVnwLv5PJYQ?pwd=afw8
提取码:afw8
1.进程和线程
进程:一个独立功能的程序
线程:一个进程包含若干个线程
java默认有两个线程:main gc
java是开不了线程的,调用底层的c++调用的方法
并发编程:并发,并行
并发:多线程操作同一个资源,单核cpu,通过快速交换进行使用
并行:多个人一起行走,多核cpu,多个线程同时执行
查看cpu资源System.out.println(Runtime.getRuntime().availableProcessors());
线程的状态 Thread.state
2.wait和sleep区别(4点)
2.1类的不同
wait-->object sleep-->Thread
2.2关于锁的释放
wait会释放锁,sleep不会释放锁
3.范围不同
wait:必须在同步代码块使用
sleep:可以用在任何地方
4.捕获异常
wait:不需要捕获异常,需要唤醒
sleep:需要捕获异常,不需要唤醒
Lock锁
传统的线程synchronized
//线程就是一个单独的资源类
public class ThreadTest {
public static void main(String[] args) {
// com.qing.demo01.MyTest myTest = new com.qing.demo01.MyTest();
// new Thread(myTest).start();
// 获取cpu参数
// cpu密集型,IO密集型
// System.out.println(Runtime.getRuntime().availableProcessors());
// 对于单接口的函数式编程,尽量使用lambda表达式
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sell();
}
},"线程1").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sell();
}
},"线程2").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sell();
}
},"线程3").start();
}
}
//oop编程降低耦合度,尽量少继承
class Ticket{
int num=50;
public synchronized void sell(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"售卖第"+num--+"张票");
}else {
System.out.println("卖完了");
}
}
}
锁
步骤:加锁 ->写业务->解锁
Lock lock=new ReentrantLock();底层源码
公平锁:十分公平可以先到先得
非公平锁:十分不公平,可以插队(默认)
public class ThreadTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sell();
}
}, "线程1").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sell();
}
}, "线程2").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sell();
}
}, "线程3").start();
}
}
//oop编程降低耦合度,尽量少继承
class Ticket {
int num = 50;
// 锁头
Lock lock = new ReentrantLock();
public void sell() {
// 加锁
lock.lock();
try {
// 业务代码
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "售卖第" + num-- + "张票");
}
} catch (Exception e) {
e.printStackTrace();
}
// 解锁
lock.unlock();
}
}
实现结果一样
synchronized和lock的区别
1.synchronized是指关键字,lock是一个java类
2.synchronzized无法判断锁的状态,lock可以判断是否取到锁
3,synchronzized会自动释放锁,如果不释放锁会造成死锁
4.synchronized 如果线程阻塞的话,就会一直等待,而lock锁就不会一直等
5.synchronized可重入锁,不可以中断,非公平,lock可重入锁有,可以判断,非公平(可自己设置)
6.synchronized适合少量的同步代码块,Lock适合大量同步代码块
传统生产者消费真问题(synchronized wait notify)
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.incrpc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrpc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//等待 业务 通知
class Data {
private int nums = 0;
//增加产品
public synchronized void incrpc() throws InterruptedException {
if (nums != 0) {
this.wait();
}
nums++;
System.out.println(Thread.currentThread().getName() + "生产了产品" + nums);
this.notifyAll();
}
//减少产品
public synchronized void decrpc() throws InterruptedException {
if (nums == 0) {
this.wait();
}
System.out.println(Thread.currentThread().getName() + "消费了" + nums);
nums--;
this.notifyAll();
}
}
结果:
注意:(虚假唤醒 if)这是只有两个线程,如果多个线程则会不稳定,
多了c,d两个线程
则会导致:产生错误
产生错误的原因:
把if换成while
结果正常
JUC版的生产者消费者问题
老版升级
相交于Synchronized中线程交互wait和notify的问题,Lock中出现了相同的await(等待)和signal (唤醒)。
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.incrpc();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrpc();
}
}, "B").start();
new Thread(() -> {
data.incrpc();
}, "C").start();
new Thread(() -> {
data.decrpc();
}, "D").start();
}
}
//等待 业务 通知
class Data2 {
private int nums = 0;
// 创建锁,
Lock lock = new ReentrantLock();
// 创建监视器
Condition condition = lock.newCondition();
// condition.await(); 等待
// condition.signalAll(); 唤醒
//增加产品
public void incrpc() {
lock.lock();
try {
while (nums != 0) {
condition.await();
}
nums++;
System.out.println(Thread.currentThread().getName() + "生产了产品" + nums);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//减少产品
public synchronized void decrpc() {
lock.lock();
try {
while (nums == 0) {
condition.await();
}
System.out.println(Thread.currentThread().getName() + "消费了" + nums);
nums--;
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果:
可以实现
有序线程(配对)
问题。使三个线程按照顺序进行输出
补充:在唤醒的时候会重新进入一次锁,重新调用while进行判断
public class C {
public static void main(String[] args) {
JUCPC jucpc = new JUCPC();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
jucpc.P1();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
jucpc.P2();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
jucpc.P3();
}
}, "C").start();
}
}
class JUCPC {
int nums = 1;
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void P1() {
lock.lock();
try {
while (nums != 1) {
c1.await();
}
System.out.println(Thread.currentThread().getName() + "---->" + nums);
nums = 2;
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void P2() {
lock.lock();
try {
while (nums != 2) {
c2.await();
}
System.out.println(Thread.currentThread().getName() + "---->" + nums);
nums = 3;
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void P3() {
lock.lock();
try {
while (nums != 3) {
c3.await();
}
System.out.println(Thread.currentThread().getName() + "---->" + nums);
nums = 1;
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果
八锁现象
深刻理解锁的机制,关于锁的八个问题
补充:import java.util.concurrent.TimeUnit;包下的
TimeUnit工具类,点进去可以看到里面的方法,可以让线程休眠
情况1:一个类,两个synchronized的方法,一个对象,两个线程。
多个线程使用同一把锁头-顺序执行
情况2:在情况1中的方法1中加入延迟,TimeUtile观察情况
多个线程使用同一把锁头+线程阻塞-随即执行
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendemail() {
System.out.println("发邮件");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果: 程序结果不是顺序执行,synchronized 锁住的对象中的方法,两个方法谁先拿到谁先调用
情况3:多个线程有锁和无锁的-随机执行
是类普通(内未加锁的)方法和加锁方法进行比较(未必先调用,就先执行)
如果对锁中的方法未延时,主要看cpu心情,多数,锁内方法先执行,如果对锁内方法进行延迟,且时间足够长时,类内无锁的方法先执行
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone {
public synchronized void sendemail() {
try {
// 时间必须过大,才可以类中未加载的方法先执行,这里设置了2秒和4秒
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("绝顶聪明");
}
}
结果:类内普通方法没有锁,不是同步方法,不受锁的影响
情况4:多个线程 多锁头-随机执行
public class Lock8 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(() -> {
p1.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
p2.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendemail() {
try {
// 时间必须过大,才可以类中未加载的方法先执行,这里设置了2秒和4秒
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发邮件");
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
System.out.println("绝顶聪明");
}
}
结果:B线程,没有延迟的先调用,同是获取方法
情况5:增加静态方法 多线程使用同一个对象 class锁头 -顺序执行
public class Lock8 {
public static void main(String[] args) {
Phone p1 = new Phone();
new Thread(() -> {
p1.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
p1.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendemail() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发邮件");
}
public static synchronized void call() {
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
System.out.println("绝顶聪明");
}
}
结论:加入static,synchronized锁的是calss,而不是方法的调用者 -顺序执行
情况6:多线程,多对象
public class Lock8 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(() -> {
p1.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
p2.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendemail() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发邮件");
}
public static synchronized void call() {
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
System.out.println("绝顶聪明");
}
}
结果:A线程调用在前面,加入static之后synchronized锁的就是类,谁先调用谁先执行
情况7:多线程 单对象-随机执行
举例:一个静态的同步方法,一个普通的同步方法,一个对象
public class Lock8 {
public static void main(String[] args) {
Phone p1 = new Phone();
new Thread(() -> {
p1.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
p1.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendemail() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发邮件");
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
System.out.println("绝顶聪明");
}
}
结果:打电话优先
情况8:多线程,多个对象-随机执行
public class Lock8 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(() -> {
p1.sendemail();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
p2.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendemail() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发邮件");
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
System.out.println("绝顶聪明");
}
}
结果:打电话优先
总结:
synchronized锁的一个是具体的对象,多线程调用方法内的,按照顺序,先到先得
static synchronized 锁的是具体的一个类,创建多个对象是时候用的是类模板里面的锁,相当于一个锁。在相同的锁内,按照执行顺序执行。不同锁内按照,执行时间。
集合类不安全-CopyOnWriteArrayList
List不安全
举例:创建以ArrayList用10个线程进行跑
public class ArrayListTest {
public static void main(String[] args) {
List<String> arr = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
arr.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arr);
},String.valueOf(i)).start();
}
}
}
结果:报错,并发下list是不安全的
java.util.ConcurrentModificationException 并发修改异常
修改:
方式1:把ArrayList修改成Vectory、
方式2:Collection.synchronizedList(new ArrayList<>())
把ArrayList改成安全的List
// 把不安全的线程变得安全
List<String> arr = Collections.synchronizedList(new ArrayList<>());
方式三:在java.util.concurrent.CopyOnWriteArrayList;包下有有CopyOnWriteArrayList类
但是CopyOnWriteArrayList读写分离,底层的set和add方法底层都复制了一个新的容器。和synchronized的区别在于,对于新增的数据会进行上锁,然后复制原来的数据再进行修改,修改完之后在进行对原来的数据进行覆盖。但占用内存
补充:CopyOnWriteArrayList和Vectory区别:
Vectory底层试用seychronized进行绑定的,而CopyOnWriteLsit底层是又lock锁实现的,synchronized比lock效率低
public class ArrayListTest {
public static void main(String[] args) {
CopyOnWriteArrayList<String> arr = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
arr.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arr);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArraset
Set<String> objects = new HashSet<>();在多线程也是不安全的,但它和ArrayList大致一样
创建方法也是
创建方法:
//不安全
Set<String> set1 = new HashSet<>();
//安全效率慢
Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
//juc下的创建
CopyOnWriteArraySet<String> set3 = new CopyOnWriteArraySet<>();
ConcurrentHashMap
创建方法:
HashMap<String,String> hashMap1 = new HashMap<String,String>();//不安全
Map<String, String> hashMap2 = Collections.synchronizedMap(new HashMap<String, String>());//安全效率低
Map hashMap3 = new ConcurrentHashMap();//juc下创建anquanMap
Callable
特点:有返回值,可以抛出异常,支持泛型,重写call方法
new Threa()无法直接调用Callable,但是runable下的实现类FutureTask可以调用
代码测试:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Thread无法直接调用Callable的实现类,
// 但是它的子类FutureTask可以调用
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask, "A").start();
// 只执行一次 结果会被缓存,效率高
new Thread(futureTask, "B").start();
// get方法可能会阻塞,所以放到最后执行
Object o = futureTask.get();
System.out.println(String.valueOf(o));
}
}
//有返回值,随着泛型的变化而变化
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("call()");
return "1234";
}
}
常用的辅助类
CountDownLatch:
主要为两个方法:1.countDown()数量减1 2.awite等待计数器归零向下执行
countSownLatch里面的为线程数,必须等待线程数完全执行完之后才可以执行await后面的方法
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
cdl.countDown();
},String.valueOf(i)).start();
}
System.out.println("close Door");
cdl.await();
System.out.println("open");
}
}
截图:
CyclicBarrier:
官方:允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
加法计数器:
public class CyclicBarrieDemo {
// 累加器,必须执行到才可以执行
public static void main(String[] args)
{
// 第二个参数传的是Runnable接口
// 必须执行m个线程之后才可以调用预定的线程方法
CyclicBarrier cyclicBarrier = new CyclicBarrier(4,()->{
System.out.println("执行预定的线程");
});
for (int i = 0; i < 4; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName());
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore:
一个计数的信号量,在概念上,信号量维持一组许可证。
主要两个方法 acquire()得到 release()释放
举例:4个信号量,如果满了就释放,在循环冲重新获取,比如停车位,只有别的车走了,后面的车才可以进来
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(4);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获取锁");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(Thread.currentThread().getName()+"释放锁");
}
},String.valueOf(i)).start();
}
}
}
结果:总共获取了6次,每次最多获取不超过4
读写锁-ReadWriteLock
主要方法:
Lock lock=(Lock) new ReentrantReadWriteLock();
//写锁lock.writeLock().lock(); lock.writeLock().unlock();
//读锁 lock.readLock().lock(); lock.readLock().lock();
public class ReadWriteLock {
public static void main(String[] args) {
MyLock myLock = new MyLock();
MyLock1 myLock1 = new MyLock1();
// 写入
for (int i = 0; i < 5; i++) {
int f1 = i;
int f2 = i;
new Thread(() -> {
// myLock.put(String.valueOf(f1), f2);
myLock1.put(String.valueOf(f1), f2);
}).start();
}
// 读取
for (int i = 0; i < 5; i++) {
int f1 = i;
int f2 = i;
new Thread(() -> {
// myLock.pop(String.valueOf(f1));
myLock1.pop(String.valueOf(f1));
}).start();
}
}
}
class MyLock {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
}
public void pop(String key) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
}
}
//加锁的
class MyLock1 {
private volatile Map<String, Object> map = new HashMap<>();
// Lock lock=(Lock) new ReentrantReadWriteLock();
// 读写锁
// 写的时候只希望一个线程可以写
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
// 读锁
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.writeLock().unlock();
}
}
// 读的时候希望所有线程都可以读
public void pop(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
结果:
阻塞队列-BlockingQueue
ArrayBllockingQueue 有容量
add:添加 添加成功返回true,超过抛异常
remove:按照进队列的顺序进行移除,并返回,取多了抛异常
offer():和add一样超出返回false
poll():和remove一样,超出返回false
element():返回队首的元素,没有抛出异常
peek():返回队首元素,不抛出异常
put();放进去,如果超出一直等待
take():取出,如果没有一直等待
offer("放进去元素","多少","单位"):添加元素,等待超过退出
poll("放进去元素","多少","单位"):去除元素,等待超过退出
public static void main(String[] args) {
// 创建一个数组类型的消息队列
ArrayBlockingQueue<String> bq = new ArrayBlockingQueue<>(3);//队列的大小
System.out.println(bq.add("a"));
System.out.println(bq.add("b"));
System.out.println(bq.add("c"));
System.out.println(bq.add("d"));//超多大小会报错
}
}
结果:
同步队列-SynchronousQueue
没有容量,不存储元素,put一个元素,必须先取出来,否则不能放进去
不需要加锁,源码里面有锁
put:放入元素
take:取出元素
public class SynchronousQueueTest {
public static void main(String[] args) {
// 没有容量
SynchronousQueue<String> sq = new SynchronousQueue();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"放入1");
sq.put("1");
System.out.println(Thread.currentThread().getName()+"放入2");
sq.put("2");
System.out.println(Thread.currentThread().getName()+"放入3");
sq.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+sq.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+sq.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+sq.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
结果: