大数据基础之JUC:JUC简介、concurrent类、JUC解决售票问题、Lambda表达式、JUC线程安全问题、JUC八锁演示及说明

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();
            }
        }
    }
    

大数据基础之JUC:JUC简介、concurrent类、JUC解决售票问题、Lambda表达式、JUC线程安全问题、JUC八锁演示及说明

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()
大数据基础之JUC:JUC简介、concurrent类、JUC解决售票问题、Lambda表达式、JUC线程安全问题、JUC八锁演示及说明
setNotSafe()、listNotSafe()
大数据基础之JUC:JUC简介、concurrent类、JUC解决售票问题、Lambda表达式、JUC线程安全问题、JUC八锁演示及说明

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个静态同步方法,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();
     	}
     }
    
上一篇:JUC-FutureTask


下一篇:Windows下面startup.bat启动Tomcat偶发死锁问题