JUC入门(一)

进程与线程的区别

  进程:系统正在运行的一个应用程序,程序一旦运行就是进程,进程是资源分配的最小单位

  线程:线程是程序执行的最小单位

 

线程的基本状态

  1)new 新建

  2)runnable  准备就绪

  3)blocked  阻塞

  4)waiting  不见不散的等待

  5)timed_waiting  过时不候

  6)terminated   终结

 

wait和sleep的区别

  1)sleep是Thread的静态方法,wait是Object的方法,任何对象都能够调用

  2)sleep不会释放锁,也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁

  3)他们都可以被interrupted中断

 

并发和并行

  并发:同一时刻多个线程在访问同一个资源

  并行:多项工作一起同时执行

 

管程  (Monitor) 

  是一种同步机制,保证同一时间内,只有一个线程访问被保护数据或者代码

  jvm的同步是基于进入和退出的时候,使用管程对象实现的

 

用户线程和守护线程

  用户线程:自定义线程,如果用户线程还存活,jvm就不会关闭

public static void main(String[] args){
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
            while(true){

            }
        }, "aa");
        aa.start();
    }

可以看到下图中一直在死循环,没有return 0;

 

 

 

守护线程:比如垃圾回收,如果没有用户线程在执行了,即使守护线程还在,jvm也会结束

public static void main(String[] args){
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
            while(true){

            }
        }, "aa");
        aa.setDaemon(true);  //改为守护线程
        aa.start();
    }

可以看到,即使守护线程运行还没结束,主程序已经return 

 

 

 

 

Synchronized    

  修饰的对象有以下几种

  1)修饰一个代码块,被修饰的代码块称为同步代码快

  2)修饰一个方法,被修饰的方法称为同步方法

  3)修饰一个静态的方法,作用的范围是整个静态方法,作用的对象是这个类的所有对象

  4)修饰一个类,作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象

 

一个关于Synchronized的一个买票小例子

class Ticket{
    private int number=30;
    public synchronized void sale(){
        if(number>0){
            System.out.println(Thread.currentThread().getName()+"卖出"+(number--)+"剩下"+number);
        }
    }
}

public class Sale {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"AA").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"BB").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"CC").start();
    }
}

 

Lock 

  除了synchronized锁,在JDK 1.5,Java提供了更强大的线程同步机制,比起 synchronized 隐式的方式,虽然可以看到它锁的代码片段,但看不到更具体的;不同synchronized锁,Lock锁可以看到它的开始和结束。(用法也是如此,手动创建Lock锁,需要手动关闭Lock锁)

  Lock是显性的方式来实现同步

Lock与Synchronized的区别

  1)Lock不是java语言内置的,synchronized是java的关键字,因此是内置特性,Lock是类,通过这个类可以实现同步访问

  2)Lock和Synchronized有一点非常不同,采用synchronized不需要用户手动加锁解锁,当synchronized方法或者synchronized代码块执行完或者发生异常之后,系统会自动的让线程释放对锁的占用,而Lock则必须手动去释放锁,如果没有释放,则会造成死锁现象

  3)Lock可以让等待的线程响应中断,而synchronized却不行,使用synchronized时,线程会一直执行下去,不能够响应中断

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到

  5)Lock可以提高多个线程进行读操作的效率

 

使用lock锁来实现买票问题

class LTicket{
    private int number=30;
    private final ReentrantLock lock = new ReentrantLock();
    public void sale(){
        try{
            lock.lock();
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出"+(number--)+"剩下"+number);
            }
        }finally {
            lock.unlock();
        }
    }
}

public class SaleByLock {
    public static void main(String[] args) {
        LTicket ticket = new LTicket();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"AA").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"BB").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"CC").start();
    }
}

 

 

使用Synchronized关键字 一个线程通信的例子(线程A当number==0时执行++,线程B当number==1时,执行--,要实现,一个加一个减   一个加一个减)

class share{
    private int number=0;
    public synchronized void incr() throws InterruptedException {
        if(number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+":number"+number);
        notifyAll();
    }
    public synchronized void desc() throws InterruptedException {
        if(number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+":number"+number);
        notifyAll();
    }

}
public class XCTX {
    public static void main(String[] args){
        share share = new share();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    share.desc();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }

}

执行结果如下:

 

线程通信的虚假唤醒问题

  在上面所述的例子中,如果我们多增加两个线程C、D,一个执行++,一个执行--,那么,就有可能会出现以下问题(虚假唤醒)

  

 

   那么,为什么会出现这样的问题呢?原因是出现在if语句

    我们有4个线程A、B、C、D、两个加两个减

    当我们执行B线程(进行减操作)结束的时候,就会唤醒其他线程,其他线程会根据值来判定是否wait

    其中A跟C是满足条件继续执行的,但是因为加了synchronized,所以只有一个能执行

    这里我们假设A拿到了锁,成功执行,那么C就只能进入wait

    当A结束的时候,就会唤醒其他线程

    而这个时候,因为C之前已经进入过了wait,而这是一个if语句(也就是不会再次判定),那么C就能够直接往下继续执行

    但是C在这个时候,我们是希望等待的。

    这就是虚假唤醒

  如何解决虚假唤醒?

  只需要将if语句改成while语句,进行循环判断即可

class share{
    private int number=0;
    public synchronized void incr() throws InterruptedException {
        while(number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+":number"+number);
        notifyAll();
    }
    public synchronized void desc() throws InterruptedException {
        while(number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+":number"+number);
        notifyAll();
    }

}
public class XCTX {
    public static void main(String[] args){
        share share = new share();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    share.desc();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    share.desc();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }

}
View Code

 

使用lock锁实现线程通信

class share2{
    private int number=0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void incr(){
        lock.lock();
        try{
            while(number!=0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+":number"+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decr(){
        lock.lock();
        try{
            while(number!=1){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+":number"+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}
public class XCTX2 {
    public static void main(String[] args) {

        share2 share = new share2();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                share.incr();
            }
        },"A").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                share.decr();
            }
        },"B").start();

    }
}
View Code

 

实现一个定制化通信(依次打印“A线程打印5次A,B线程打印10次B,C线程打印15次C”10次)

思路:         

  condition是用来干嘛的? 是多线程间协调通信的工具类

  1)创建一个ReentrantLock()对象,并且创建3个用来通信的condition,c1 c2 c3

  2)实现三个print方法,分别对应题目要求,每一个方法里用flag来维护是否等待,唤醒操作用condition.signal();

  3)使用3个线程取调用这三个方法,循环10次

package com.ma;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ShareResource{
    private int flag=1;
    private Lock lock = new ReentrantLock();

    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print15() throws InterruptedException {
        lock.lock();
        try{
            while(flag!=3){
                c3.await();
            }
            for(int i=1;i<=15;i++){
                System.out.println(Thread.currentThread().getName()+":C");
            }
            flag=1;
            c1.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print10() throws InterruptedException {
        lock.lock();
        try{
            while(flag!=2){
                c2.await();
            }
            for(int i=1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+":B");
            }
            flag=3;
            c3.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print5() throws InterruptedException {
        lock.lock();
        try{
            while(flag!=1){
                c1.await();
            }
            for(int i=1;i<=5;i++){
                System.out.println(Thread.currentThread().getName()+":A");
            }
            flag=2;
            c2.signal();
        }finally {
            lock.unlock();
        }
    }
}
public class XCTX3 {
    public static void main(String[] args){
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            try {
                for(int i=1;i<=10;i++) {
                    shareResource.print5();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for(int i=1;i<=10;i++) {
                    shareResource.print10();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                for(int i=1;i<=10;i++) {
                    shareResource.print15();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
    }
}
View Code

 

 

集合的线程安全问题

  集合线程不安全的一个例子

public class CollectionSafe {
    public static void main(String[] args){
        List<String> list = 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();
        }
    }
}

执行上述代码的时候,有很大概率会触发并发修改异常

 

   如何解决?

  1)因为ArrayList是不安全的,所以可以用vector(线程安全)来替换

      List<String> list = new Vector<>();

  2)使用Collections解决

      List<String> list = Collections.synchronizedList(new ArrayList<>());

  3)使用CopyOnwriteArrayList

      List<String> list = new CopyOnwriteArrayList<>();

 

那么CopyOnwriteArrayList为什么能够避免并发修改问题呢?

  1)CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器",Java并发包中类似的容器还有CopyOnWriteSet。

  2)很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

  3)优点:读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了

  4)缺点也很明显,一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;二是无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。

 

HashMap的并发修改异常

例子如下

    Map<String,String> map = new HashMap<>();
        for(int i=1;i<=30;i++){
            String key=String.valueOf(i);
            new Thread(()->{
               map.put(key,UUID.randomUUID().toString().substring(0,8));
               System.out.println(map);
            }).start();
        }

我们可以用ConcurrentHashMap<>()来避免这个问题

Map<String,String> map = new ConcurrentHashMap<>(); 
        for(int i=1;i<=30;i++){
            String key=String.valueOf(i);
            new Thread(()->{
               map.put(key,UUID.randomUUID().toString().substring(0,8));
               System.out.println(map);
            }).start();
        }

 

posted @ 2021-09-10 10:15  古比  阅读(62)  评论(0编辑  收藏  举报