目录
一、什么是JUC
1、JUC(java.util.concurrent)是在并发编程中使用的工具类
2、进程、线程是什么
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
3、进程、线程的例子
-
使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。
-
大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。
-
word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查
4、线程的状态(粗分5种,细分6种)
Thread.State
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,(新建)
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,(准备就绪)
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,(阻塞)
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,(不见不散)
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,(过时不候)
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;(终结)
}
5、wait和sleep的区别
功能都是当前线程暂停,wait放开手去睡 ,放开手里的锁,sleep握紧手去睡,醒了手里还有锁
6、什么是并发?什么是并行?
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:小米9今天上午10点,限量抢购
春运抢票
电商秒杀...
并行:多项工作一起执行,之后再汇总
例子:泡方便面,电水壶烧水,一边撕调料倒入桶中
二、Lock接口
1、多线程编程模板(上)
在高内聚低耦合的前提下,线程 操作 资源类
2、实现步骤
1)创建资源类
2)资源类里创建同步方法、同步代码块
3、例子卖票程序
class Ticket{
private int number = 30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出"+(number--)+"\t 还剩"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class SaleTicketDemo01 {
public static void main(String[] args) {
//Thread thread = new Thread();
Ticket ticket = new Ticket();
//Runnable是接口,接口是特殊的类,所以接口可以new,new出来的叫匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {开发不用while(true)
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();
}
}
},"C").start();
//以上代码可用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();
}
}
三、lambda表达式
1、写法:
1)拷贝小括号(),写死右箭头->,落地大括号{...}
2)@FunctionalInterface:声明函数式接口
3)default
4)static
注意:lambda表达式,如果一个接口只有一个方法,可以把方法名省略
//不写默认加@FunctionalInterface,此注解规定接口中只能有一个方法
interface Foo{
//void say();
int add(int x,int y);
//JDK1.8允许在接口中声明默认方法,加上此方法编译也是能通过的
public default int mul(int x,int y){
return x*y;
}
//并且默认方法可以有多个
default int mul2(int x,int y){
return x*y;
}
//1.8允许在接口中声明静态方法,也可以声明多个
static int div(int x,int y){
return x/y;
}
static int div2(int x,int y){
return x/y;
}
}
public class LambdaExpressDemo02 {
public static void main(String[] args) {
Foo foo = (int x,int y) -> {
System.out.println("add方法");
return x+y;
};
System.out.println(foo.add(3,4));
System.out.println(foo.mul(3, 5));
//用接口名调用
System.out.println(Foo.div(5, 2));
}
}
四、线程不安全
1、分析线程不安全从哪几个方面入手?
1)故障原因
java.util.ConcurrentModificationException
2)导致原因
3)解决方法
4)优化建议(同样的错误不放第2次)
2、ArrayList线程不安全代码
public class NotSafeDemo03 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// list.forEach(System.out::println);
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();
}
}
}
解决方法:
List<String> list = new ArrayList<>();
//修改为
List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList();
重点——写时复制
/*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();
}
}
3、HastSet线程不安全代码
public static void main(String[] args) {
Set<String> set = new HashSet<>();
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();
}
}
解决方法:
Set<String> set = new CopyOnWriteArraySet<>();//new HashSet<>();
1、HastSet底层是HashMap
//HashSet源码
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
4、HastSet线程不安全代码
public static void main(String[] args) {
// setNotSafe();
Map<String,String> map = new HashMap<>();
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();
}
}
解决方法:
Map<String,String> map = new ConcurrentHashMap<>();//new HashMap<>();
五、八锁
import java.util.concurrent.TimeUnit;
class Phone{
public synchronized void sendSMS() throws Exception{
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception{
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
/**
*
* @Description: 8锁
*
1 标准访问,先打印短信还是邮件
2 停4秒在短信方法内,先打印短信还是邮件
3 新增普通的hello方法,是先打短信还是hello
4 现在有两部手机,先打印短信还是邮件
5 两个静态同步方法,1部手机,先打印短信还是邮件
6 两个静态同步方法,2部手机,先打印短信还是邮件
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
* ---------------------------------
*
*/
public class Lock_8{
public static void main(String[] args) throws Exception{
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
//phone.getHello();
//phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
// synchronized锁的是当前对象——对象锁,锁this
// static synchronize锁的是整个类——全局锁,锁Class,static修饰的同步方法
* *一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访间这些synchronized方法,
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
*加个普通方法后发现和同步锁无关
*换成两个对象后,不是同一把锁了,情况立刻变化。
*都换成静态同步方法后, 情况又变化
*所有的非静态同步方法用的都是同一把锁——实例对象本身,
synchronized实现同步的基础: Java中的每一个对象都可以作为锁。
*具体表现为以下3种形式。
*对于普通同步方法,锁是当前实例对象。
*对于同步方法块,锁是Synchonized括号里配置的对象。
*对于静态同步方法,锁是当前类的Class对象。
当一个线程试图访间同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。|
也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,
所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
所有的静态同步方法用的也是同一一把锁——类对象本身,
这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,
而不管是同一个实例对象的静态同步方法之间,
还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
六、生产者消费者(虚假唤醒问题)
1、多线程编程模板(中)
1)判断(while)
2)干活
3)通知
4)防止虚假唤醒
2、传统方法:
//资源类
class AirCondition{
private int number = 0;
public synchronized void increment()throws Exception{
//判断
if(number !=0)
this.wait();
//干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//通知
this.notifyAll();
}
public synchronized void decrement()throws Exception{
//判断
if(number ==0)
this.wait();
//干活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//通知
this.notifyAll();
}
}
public class ProdConsumerDemo04 {
public static void main(String[] args) {
AirCondition airCondition = new AirCondition();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(200);
airCondition.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(300);
airCondition.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(400);
airCondition.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(500);
airCondition.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"D").start();
}
}
运行结果:
虚假唤醒的原因:判断是用if
3、Lock方法
//资源类
class AirCondition{
private int number = 0;
Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment()throws Exception{
lock.lock();
try {
//判断
while (number !=0)
//this.wait();
condition.await();
//干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//通知
//this.notifyAll();
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement()throws Exception{
lock.lock();
try {
//判断
if(number ==0)
//this.wait();
condition.await();
//干活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//通知
//this.notifyAll();
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProdConsumerDemo04 {
public static void main(String[] args) {
AirCondition airCondition = new AirCondition();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(200);
airCondition.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(300);
airCondition.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(400);
airCondition.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(500);
airCondition.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"D").start();
}
}
4、案例
题目:多线程之间按顺序调用,实现A->B->C
三个线程启动,要求如下
A打印5次,B打印10次,C打印15次
接着,A打印5次,B打印10次,C打印15次
打印10轮
package cn.wyu.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData{
private int number = 1;
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 {
//判断,如果是number是2或3就让A等待
while (number != 1)
//wait...
c1.await();
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
//判断,让其它线程阻塞
while (number != 2)
//wait...
c2.await();
//干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
//判断,让其它线程阻塞
while (number != 3)
//wait...
c3.await();
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
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();
}
}
七、Callable接口
package cn.wyu.juc;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class Mythread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("********");
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 result = futureTask.get();
System.out.println(result);
}
}