JUC简介
售票案例
线程类问题解决套路 “线程-控制-资源类”
接口可以以内部匿名类的方式直接new,前提是必须是函数式接口
lambda表达式口诀 “复制小括号,写死右箭头,粘贴大括号” (复制的小括号是run方法的)
()->{}
wait和notify是object类的方法,不是Thread类的;线程Thread是lang包下面的,而Concurrent是util包下面的。
左边是变量的引用,右边是变量的声明。
同步方法和同步代码块
int转换为String 可以是 int i; i+“” 建议使用String.valueof(i)
使用匿名内部类创建线程的时候,各个线程都是在main线程中按顺序start的,
concurrent类
java.util.concurrent.locks.ReentrantLock 可重入锁 替换 Synchronized (能更好解决多把钥匙的demo…)
售票demo()
进阶一 ——用匿名内部类创建线程,省去写runnable接口的实现类了。
进阶二 ——用lambda表达式,使代码更精简。
蛋糕,做一个,吃一个demo(2个线程)
进阶三——为了解决线程安全问题,同步线程,上锁synchornized进阶为lock,wait和notify进阶为condition中的await和signal。
蛋糕demo(4个线程)
进阶四 ——为了避免多线程的虚假唤醒,资源类方法中的判断应是while不能是if,需要有二次判断。
JUC解决售票问题
题目:三个售票员 卖出 30张票
-
笔记:如何编写企业级的多线程代码
-
固定的编程套路+模板是什么?
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author 黄佳豪 * @create 2019-08-12-17:41 */ public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); Ticket ticket2 = new Ticket();//number为初始值 new Thread(() -> { for (int i = 0; i <= ticket2.getNumber(); i++) { ticket.sale(); } }, "A").start(); new Thread(() -> { for (int i = 0; i <= ticket2.getNumber(); i++) { ticket.sale(); } }, "B").start(); new Thread(() -> { for (int i = 0; i <= ticket2.getNumber(); i++) { ticket.sale(); } }, "C").start(); } } class Ticket {//创建资源类 public int getNumber() { return number; } private int number = 30; Lock lock = new ReentrantLock();//重进入锁 public void sale() { lock.lock(); try { if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出第:" + (number--) + "张票,余票为:" + number); } } finally { lock.unlock(); } } }
Lambda表达式
口诀:拷贝小括号,写死右箭头,落地大括号
public class HwLambdaExpress {
public static void main(String[] args) {
Foo foo= (int x,int y) -> {
System.out.println("this is Lambda Express");
return x+y;
};
System.out.println(foo.add(3,5));
System.out.println(foo.mul(3,5));
System.out.println(Foo.div(3,5));
}
}
interface Foo {
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;
}
}
JUC解决线程安全问题
-
1 故障现象
-
java.util.ConcurrentModificationException
-
并发修正异常
-
2 导致原因
-
3 解决方法
-
3.1 new Vector<>()
-
3.2 Collections.synchronizedList(new ArrayList<>());
-
3.3 new CopyOnWriteArrayList()
-
4 优化建议(同样的错误不犯第2次)
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; /** * @author 黄佳豪 * @create 2019-08-12-18:55 */ public class HwNotSafe { public static void main(String[] args) { mapNotSafe(); setNotSafe(); listNotSafe(); } private static void mapNotSafe() { Map<Object, Object> map = new ConcurrentHashMap<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 4)); System.out.println(map); }).start(); } } private static void setNotSafe() { Set<String > set = new CopyOnWriteArraySet<>(); for (int i = 0; i <10; i++) { new Thread(()->{set.add(UUID.randomUUID().toString().substring(0,3)); set.forEach(System.out::println);}).start(); } } private static void listNotSafe() { List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i <= 10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 3)); list.forEach(System.out::println); },String.valueOf(i)).start(); } } }
依次测试代码,线程安全。
mapNotSafe()
setNotSafe()、listNotSafe()
JUC八锁演示
8锁问题
- 1 标准访问的时候,请问先打印邮件还是短信?
- 2 sendEmail方法暂停4秒钟,请问先打印邮件还是短信?
- 3 新增Hello普通方法,请问先打印邮件还是Hello?
- 4 两部手机,请问先打印邮件还是短信?
- 5 两个静态同步方法,同1部手机 ,请问先打印邮件还是短信?
- 6 两个静态同步方法,有2部手机 ,请问先打印邮件还是短信?
- 7 1个静态同步方法,1个普通同步方法,有1部手机 ,请问先打印邮件还是短信?
- 8 1个静态同步方法,1个普通同步方法,有2部手机 ,请问先打印邮件还是短信?
1.1、标准访问的时候,请问先打印邮件还是短信?
答案是 不知道,因为线程谁抢到了谁执行,一般情况下是A执行
//--------------------------------------------------------
//资源类
class Phone{
public synchronized void sendEmail() {
System.out.println("sendEmail----------");
}
public synchronized void getSMS() {
System.out.println("----------getSMS");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) {
Phone p=new Phone();
new Thread(() ->{
p.sendEmail();
},"A").start();
new Thread(() ->{
p.getSMS();;
},"B").start();
}
}
1.2 在两个线程之间增加sleep,强制让线程A执行。
结果是sendEmail先执行
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
//--------------------------------------------------------
//资源类
class Phone{
public synchronized void sendEmail() {
System.out.println("sendEmail----------");
}
public synchronized void getSMS() {
System.out.println("----------getSMS");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p=new Phone();
new Thread(() ->{
p.sendEmail();
},"A").start();
//强制让线程A先执行
Thread.sleep(200);
new Thread(() ->{
p.getSMS();;
},"B").start();
}
}
2、sendEmail方法暂停4秒钟,请问先打印邮件还是短信?
4秒后A先执行,紧接着B执行。
因为休眠main线程之前A已经启动了,时间足够A运行,A会锁住资源类的入口(也就是对象)。所以A先执行。
//--------------------------------------------------------
//资源类
class Phone{
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail----------");
}
public synchronized void getSMS() {
System.out.println("----------getSMS");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p=new Phone();
new Thread(() ->{
try {
p.sendEmail();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
//强制让线程A先执行
Thread.sleep(200);
new Thread(() ->{
p.getSMS();;
},"B").start();
}
}
3
先打印hello,虽然sendEmail锁住了资源类的入口(对象),因为sayHello未上锁,所以sayHello方法可以进入资源类。但是Thread.sleep()锁住了main线程,也就是hello打印出来的延迟时间就是休眠的设定时间。
加个普通方法后发现和同步锁无关
//--------------------------------------------------------
//资源类
class Phone{
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail----------");
}
public synchronized void getSMS() {
System.out.println("----------getSMS");
}
public void sayHello() {
System.out.println("---hello---");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p=new Phone();
new Thread(() ->{
try {
p.sendEmail();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
//强制让线程A先执行
Thread.sleep(2000);
new Thread(() ->{
p.sayHello();
},"B").start();
}
}
4、
肯定是SMS啊,锁的是资源类的入口,也就是对象,既然不是同一个对象,那肯定锁不住。并且线程B的执行和main线程的休眠时间相关。
换成两个对象后,不是同一把锁了,情况立刻变化。
//--------------------------------------------------------
//资源类
class Phone{
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail----------");
}
public synchronized void getSMS() {
System.out.println("----------getSMS");
}
public void sayHello() {
System.out.println("---hello---");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p1=new Phone();
Phone p2=new Phone();
new Thread(() ->{
try {
p1.sendEmail();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
//强制让线程A先执行
Thread.sleep(2000);
new Thread(() ->{
p2.getSMS();
},"B").start();
}
}
5、
先打印sendEmali,然后打印getSMS,因为静态同步锁,锁的是Phone.Class,所以B线程在A线程执行完之前进不去。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
//--------------------------------------------------------
//资源类
class Phone{
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail----------");
}
public static synchronized void getSMS() {
System.out.println("----------getSMS");
}
public void sayHello() {
System.out.println("---hello---");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p=new Phone();
new Thread(() ->{
try {
p.sendEmail();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
//强制让线程A先执行
Thread.sleep(5000);
new Thread(() ->{
p.getSMS();
},"B").start();
}
}
6、两个静态同步方法,有2部手机 ,请问先打印邮件还是短信?
看似和5不一样,其实是一回事,虽然两个对象,因为强制A先执行,所以A会锁住资源类,因此先打印sendEmail。
//--------------------------------------------------------
//资源类
class Phone{
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail----------");
}
public static synchronized void getSMS() {
System.out.println("----------getSMS");
}
public void sayHello() {
System.out.println("---hello---");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p1=new Phone();
Phone p2=new Phone();
new Thread(() ->{
try {
p1.sendEmail();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
//强制让线程A先执行
Thread.sleep(5000);
new Thread(() ->{
p2.getSMS();
},"B").start();
}
}
7 1个静态同步方法,1个普通同步方法,有1部手机 ,请问先打印邮件还是短信?
我认为是先是静态同步方法,锁住资源类,4秒之后打印email 然后再过1秒打印SMS 这条想法是错误的
锁的不是资源类,锁的是类对象,也就是说,不管new了几个,静态同步方法的类对象都是一个;而普通同步方法锁住的是new出来的对象。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象。
所有的静态同步方法用的也是同一把锁——类对象本身,
这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,
而不管是否是同一个实例对象。
//--------------------------------------------------------
//资源类
class Phone{
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail----------");
}
public synchronized void getSMS() {
System.out.println("----------getSMS");
}
public void sayHello() {
System.out.println("---hello---");
}
}
//主方法
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone p=new Phone();
new Thread(() ->{
try {
p.sendEmail();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"A").start();
//强制让线程A先执行
Thread.sleep(5000);
new Thread(() ->{
p.getSMS();
},"B").start();
}
}
-
1个静态同步方法,1个普通同步方法,有2部手机 ,请问先打印邮件还是短信?
我认为是和第7种情况是一样的,p1会先锁住资源类,静态同步方法和对象无关,所以p2进不去。这条想法也不对,P1锁住的不是资源类,而是类对象。线程1锁的是类对象,线程2锁的是实例对象,虽然看似是一个对象,然而两者不具备竞态条件,因此修改main休眠时间之后就会发现getSMS先执行。//-------------------------------------------------------- //资源类 class Phone{ public static synchronized void sendEmail() throws Exception { TimeUnit.SECONDS.sleep(4); System.out.println("sendEmail----------"); } public synchronized void getSMS() { System.out.println("----------getSMS"); } public void sayHello() { System.out.println("---hello---"); } } //主方法 public class Lock_8 { public static void main(String[] args) throws Exception { Phone p1=new Phone(); Phone p2=new Phone(); new Thread(() ->{ try { p1.sendEmail(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } },"A").start(); //强制让线程A先执行 Thread.sleep(5000); new Thread(() ->{ p2.getSMS(); },"B").start(); } }