一、JUC
1、SaleTicketDemo.java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author yht
* @create 20:46
*
* 题目:三个售票员 卖出 30张票
* 笔记:如何编写企业级的多线程代码
* 固定的编程套路+模板是什么?
* 1.在高内聚,低耦合的前提下,线程 操作 资源类
* 1.1一言不合,先创建一个资源类
*/
class Ticket{ //资源类=实例变量+实力方法
private int num=30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try {
if (num>0){
num--;
System.out.println(Thread.currentThread().getName()+"在卖票,还剩:"+num);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class SaleTicketDemo {
public static void main(String[] args){
Ticket ticket = new Ticket();
//Lambda表达式写法
new Thread(()->{for (int i=0;i<40;i++) ticket.sale();},"A").start();
new Thread(()->{for (int i=0;i<40;i++) ticket.sale();},"B").start();
new Thread(()->{for (int i=0;i<40;i++) ticket.sale();},"C").start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i=0;i<40;i++){
// ticket.sale();
// }
// }
// },"A").start();
//
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i=0;i<40;i++){
//
// ticket.sale();
// }
// }
// },"B").start();
//
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i=0;i<40;i++){
// ticket.sale();
// }
// }
// },"B").start();
}
}
2、
*函数式接口内只能有一个抽象方法
*Lambda表达式写法:
拷贝小括号, 写死右箭头, 落地大括号
LambdaExpressDemo.java
/**
* @author yht
* @create 21:16
* <p>
* 1 拷贝小括号, 写死右箭头, 落地大括号
* 2 @FunctionalInterface
* 3 default
* 4 static
*/
@FunctionalInterface
interface Foo {
// public void sayHello();
int add(int x, int y);
default int mul(int x, int y) {
return x * y;
}
static int div(int x, int y) {
return x / y;
}
}
public class LambdaExpressDemo {
public static void main(String[] args) {
// Foo foo = new Foo() {
// @Override
// public void sayHello() {
// System.out.println("*************eat*****");
// }
//
// @Override
// public int add(int x, int y) {
// return x + y;
// }
// };
// foo.sayHello();
// System.out.println(foo.add(1, 2));
Foo foo = (int x, int y) -> {
return x + y;
};
System.out.println(foo.add(1, 2));
System.out.println(foo.mul(2, 3));
System.out.println(Foo.div(4, 2));
}
}
3、请写代码说明ArrayList、HashMap、ArraySet是线程不安全的
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
*
* 1 故障现象
* java.util.ConcurrentModificationException
*
* 2 导致原因
* 多线程并发争抢同一个资源类且没加锁//多个线程对同一个资源类集合类读写同时进行,拉扯了导致数据不一致
* 3 解决方法
* 3.1 new Vector<>()
* 3.2 Collections.synchronizedList(new ArrayList<>());
* 3.3 new CopyOnWriteArrayList()
*
*
* 4 优化建议(同样的错误不犯第2次)
* 考虑使用JUC的工具类
*
*/
public class NotSafeDemo03
{
public static void main(String[] args)
{
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,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
public static void setNotSafe()
{
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <=30; i++)
{
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
public static void listNotSafe()
{
List<String> list = new CopyOnWriteArrayList();//Collections.synchronizedList(new ArrayList<>());//new Vector<>();//new ArrayList<>();
for (int i = 1; i <=30; i++)
{
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
/**笔记
* 写时复制
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,
复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,
再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读,
而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
public boolean add(E e)
{
final ReentrantLock lock = this.lock;
lock.lock();
try
{
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
finally {
lock.unlock();
}
}
*/
4、八锁理论及解释
import java.util.concurrent.TimeUnit;
class Phone//Phone.java ---> Phone.class Class.forName();
{
public static synchronized void sendEmail()throws Exception
{
TimeUnit.SECONDS.sleep(3);
System.out.println("*****sendEmail");
}
public synchronized void sendSMS()throws Exception
{
System.out.println("*****sendSMS");
}
public void sayHello()throws Exception
{
System.out.println("*****sayHello");
}
}
/**
* @auther yht
* 8 lock
*1 标准访问,请问先打印邮件还是短信 //Email
*2 暂停4秒钟在邮件方法,请问先打印邮件还是短信 //Email
*3 新增普通sayHello方法,请问先打印邮件还是hello //hello
*4 两部手机,请问先打印邮件还是短信 //SMS
*5 两个静态同步方法,同一部手机,请问先打印邮件还是短信 //Email
*6 两个静态同步方法,2部手机,请问先打印邮件还是短信 Email //static 整个类都锁了,全局锁
*7 1个静态同步方法,1个普通同步方法,同一部手机,请问先打印邮件还是短信 锁的对象不是一个 //SMS
*8 1个静态同步方法,1个普通同步方法,2部手机,请问先打印邮件还是短信 //SMS
*
* * 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
* * 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
* *
* * 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
*
* * 加个普通方法后发现和同步锁无关
*
* * 换成两个对象后,不是同一把锁了,情况立刻变化。
*
* * synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
* * 具体表现为以下3种形式。
* * 对于普通同步方法,锁是当前实例对象,锁的是当前对象this,
* * 对于同步方法块,锁是Synchonized括号里配置的对象。
*
* * 对于静态同步方法,锁是当前类的Class对象。
*/
public class Lock8Demo05
{
public static void main(String[] args) throws InterruptedException
{
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try
{
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(() -> {
try
{
//phone.sendSMS();
//phone.sayHello();
phone2.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
/**
* 笔记
* * 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
* * 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
* * 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
* 加个普通方法后发现和同步锁无关
* 换成两个对象后,不是同一把锁了,情况立刻变化。
* 都换成静态同步方法后,情况又变化
* 所有的非静态同步方法用的都是同一把锁——实例对象本身,
*
* synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
* 具体表现为以下3种形式。
* 对于普通同步方法,锁是当前实例对象。
* 对于同步方法块,锁是Synchonized括号里配置的对象。
* 对于静态同步方法,锁是当前类的Class对象。
* 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
* 也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
* 可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,
* 所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
*
* 所有的静态同步方法用的也是同一把锁——类对象Class本身,
* 这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
* 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,
* 而不管是同一个实例对象的静态同步方法之间,
* 还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
**/
5、生产者消费者Demo1
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量减1,
* 实现交替,来10轮,变量初始值为零。
方法:
* 1 高聚低合前提下,线程操作资源类
* 2 判断/干活/通知
/**
* @author yht
* @create 14:28
* <p>
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量减1,
* 实现交替,来10轮,变量初始值为零。
* <p>
* 1 高聚低合前提下,线程操作资源类
* 2 判断/干活/通知
*/
class AirCondition {
private int num = 0;
public synchronized void increment() throws Exception{
//1判断
if (num != 0) {
this.wait();
}
//2干活
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
//3通知
this.notifyAll();
}
public synchronized void decrement() throws Exception {
//1判断
if (num==0){
this.wait();
}
//2干活
num--;
System.out.println(Thread.currentThread().getName()+":"+num);
//3通知
this.notifyAll();
}
}
public class ProdConsumerDemo {
public static void main(String[] args){
AirCondition airCondition = new AirCondition();
new Thread(()->{
for (int i=0;i<10;i++){
try {
airCondition.increment();
} catch (Exception e) {
}
}
},"A").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
airCondition.decrement();
} catch (Exception e) {
}
}
},"B").start();
}
}
6、生产者消费者Demo2
* 题目:现在四个线程,可以操作初始值为零的一个变量,
* 实现两个线程对该变量加1,两个线程对该变量减1,
* 实现交替,来10轮,变量初始值为零。
方法:
1 高聚低合前提下,线程操作资源类
2 判断/干活/通知
3 防止虚假唤醒
知识点:
//多线程的调度交互判断绝对不准用if 必须用while(while的本质是循环+判断),使用之后程序再也不犯2了
也就是说:多线程的横向通信调用必须用while
//wait(),notifyAll()方法是Object的
package com.dongda.java;
/**
* @author yht
* @create 2020-09-09 14:28
* <p>
* 题目:现在四个线程,可以操作初始值为零的一个变量,
* 实现两个线程对该变量加1,两个线程对该变量减1,
* 实现交替,来10轮,变量初始值为零。
* <p>
* 1 高聚低合前提下,线程操作资源类
* 2 判断/干活/通知
* 3 防止虚假唤醒
*/
class AirCondition {
private int num = 0;
public synchronized void increment() throws Exception{
//1判断
while (num != 0) { //多线程的调度交互判断绝对不准用if 必须用while(while的本质是循环+判断),使用之后
//程序再也不犯2了
//AAA CCC
this.wait(); //wait(),notifyAll()方法是Object的
}
//2干活
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
//3通知
this.notifyAll();
}
public synchronized void decrement() throws Exception {
//1判断
while (num==0){
this.wait();
}
//2干活
num--;
System.out.println(Thread.currentThread().getName()+":"+num);
//3通知
this.notifyAll();
}
}
public class ProdConsumerDemo {
public static void main(String[] args){
AirCondition airCondition = new AirCondition();
new Thread(()->{
for (int i=0;i<10;i++){
try {
airCondition.increment();
} catch (Exception e) {
}
}
},"A").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
airCondition.decrement();
} catch (Exception e) {
}
}
},"B").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
airCondition.increment();
} catch (Exception e) {
}
}
},"C").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
airCondition.decrement();
} catch (Exception e) {
}
}
},"D").start();
}
}
为什么两个线程正确,而四个线程就错了呢?(为什么犯2?)
***7、生产者消费者Demo3
知识点:多线程编程套路+while判断+新版写法
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author yht
* @create 2020-09-09 14:28
* <p>
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量减1,
* 实现交替,来10轮,变量初始值为零。
* <p>
* 1 高聚低合前提下,线程操作资源类
* 2 判断/干活/通知
* 3 新版写法
*/
class AirCondition {
private int num = 0;
Lock lock = new ReentrantLock(); //可重复递归的非公平锁
Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock();
try {
//1判断
while (num != 0) {
condition.await();
}
//2干活
num++;
System.out.println(Thread.currentThread().getName() + ":" + num);
//3通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception {
lock.lock();
try {
//1判断
while (num == 0) {
condition.await();
}
//2干活
num--;
System.out.println(Thread.currentThread().getName() + ":" + num);
//3通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProdConsumerDemo {
public static void main(String[] args) {
AirCondition airCondition = new AirCondition();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airCondition.increment();
} catch (Exception e) {
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airCondition.decrement();
} catch (Exception e) {
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airCondition.increment();
} catch (Exception e) {
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
airCondition.decrement();
} catch (Exception e) {
}
}
}, "D").start();
}
}
8、多线程之间按照顺序调用
* 备注:多线程之间按顺序调用,实现A->B->C
* 三个线程启动,要求如下:
*
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* 来10轮
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData
{
private int number = 1;//A : 1 B:2 C:3
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition(); //一把锁配多把备用钥匙
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5()
{
lock.lock();
try
{
//1 判断
while(number != 1)
{
//wait.....
c1.await();
}
//2 干活
for (int i = 1; i <=5; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3 通知
number = 2; //先改标志位
// 如何通知第2个
c2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print10()
{
lock.lock();
try
{
//1 判断
while(number != 2)
{
//wait.....
c2.await();
}
//2 干活
for (int i = 1; i <=10; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3 通知
number = 3;
// 如何通知第2个
c3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print15()
{
lock.lock();
try
{
//1 判断
while(number != 3)
{
//wait.....
c3.await();
}
//2 干活
for (int i = 1; i <=15; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3 通知
number = 1;
// 如何通知第2个
c1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
/**
* 备注:多线程之间按顺序调用,实现A->B->C
* 三个线程启动,要求如下:
*
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* 来10轮
*/
public class ConditionDemo
{
public static void main(String[] args)
{
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 1; i <=10; i++) {
shareData.print5();
}
},"A").start();
new Thread(() -> {
for (int i = 1; i <=10; i++) {
shareData.print10();
}
},"B").start();
new Thread(() -> {
for (int i = 1; i <=10; i++) {
shareData.print15();
}
},"C").start();
}
}
9、多线程实现Runnable接口和实现Callable接口有哪些维度的区别和不同?
①接口实现的方法不一样,一个是run()方法,一个是call()方法
②一个没有异常,一个有异常
③有无返回值
详情代码见下:
class Mythread implements Runnable {
@Override
public void run() {
}
}
class Mythread2 implements Callable<Integer> {//如果泛型里面写的String,那么
@Override
public Integer call() throws Exception {
return 1024;//这里返回就是String类型的
}
}
注意:Callable<V>是函数式接口
10、Callable接口的使用
思想:找一个能搭上线的类
实例代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author yht
* @create 2020-09-09 23:13
*/
class Mythread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("**********come in call method");
return 1024;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new Mythread());
new Thread(futureTask,"A").start();
Integer res = futureTask.get();
System.out.println(res);
}
}