JUC 多线程基础

一、内存可见性

如果一个线程对共享变量的修改,能够被其它线程看到,那么就能说明共享变量在线程之间是可见的。如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

线程对共享变量的所有操作都必须在自己的工作内存中进行,不能从主内存中读写;而且不同线程之间无法直接访问其它线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成,所以不加任何修饰的,线程之间内存是不可见的。

二、volatile 关键字

详细介绍看此处 万能青年,公众号:JavaArtisanJUC 多线程之 volatile 关键字

三、原子变量

因为volatile关键字不能保证原子性操作,所以需要java.util.concurrent.atomic包下的原子变量来进行修改,底层使用CAS算法保证数据的原子性。

四、CAS算法

  1. CAS算法是硬件对于并发操作共享数据的支持,用于管理对共享数据的并发访问。
  2. 包含三个操作数:V 内存值、 A 预估值、 B 更新值
  3. 当且仅当V == A时,才执行 V = B,否则不作任何操作
  4. CAS算法比锁的效率要高,是一种无锁,非阻塞算法实现

五、同步容器类ConcurrentHashMap

在map集合中,HashMap效率高,但是不能保证线程安全,HashTable可以保证线程安全,但是效率非常低,在需要保证线程安全的情况下,也不使用HashTable,因为效率实在是太低,所以使用ConcurrentHashMap。

六、闭锁CountDownLatch

在完成某些运算时,只有当所有线程的运算全都完成后,才继续运行的这种情况下,需要用到闭锁。

七、创建线程的四种方式

1、继承Thread类

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread类");
    }
}

public class Test {
    public static void main(String args[]) {
        //启动继承Thread类创建的线程对象
        MyThread t1 = new MyThread();
        t1.start(); 
    }

2、实现Runnable接口

class RunnableDemo implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(i);
        }
    }
}
public class TestRunable {
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        new Thread(runnableDemo).start();
    }
}

3、实现Callable接口

  1. 需要实现Callable接口
  2. 相较于Runnable,方法有返回值,并且可以抛出异常。
  3. 需要FutureTask实现类接收运算结果
public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        //执行callable方式,需要FuturnTask实现类的支持,用于接收运算结果
        FutureTask<Integer> futureTask = new FutureTask<>(threadDemo);
        //启动线程
        new Thread(futureTask).start();
        //接收运算结果
        Integer sum = futureTask.get();
        System.out.println(sum);
    }

}
class ThreadDemo implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++){
            sum += i;
        }
        return sum;
    }
}

4、线程池创建线程

八、同步锁的使用

同步锁是一个显示锁,需要lock()方法上锁,unlock()释放锁

public class TestLock {
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();

        new Thread(lockDemo, "1号").start();
        new Thread(lockDemo, "2号").start();
        new Thread(lockDemo, "3号").start();
    }
}

class LockDemo implements Runnable{
    private  int ticket = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //上锁
            lock.lock();
            try {
                if (ticket > 0){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "完成售票,余票为" + --ticket);
                }
            }finally {
                //释放锁
                lock.unlock();
            }

        }
    }
}

九、生产者消费者问题解决

package com.artisan.juc;

/**
 * @author wannengqingnian
 */
public class ProAndCus {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(productor, "生产者A1").start();
        new Thread(consumer, "消费者B1").start();
        new Thread(productor, "生产者A2").start();
        new Thread(consumer, "消费者B2").start();
    }

}

/**
 * 店员
 */
class Clerk{

    private int product = 0;
    

    public synchronized void get() {
        while (product >= 144){
            System.out.println("产品已满!");

            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        //进货
        System.out.println(Thread.currentThread().getName() + " : " + ++product);
        this.notifyAll();

    }

    public synchronized void sales(){
        while (product <= 0){
            System.out.println("产品缺货!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //卖货
        System.out.println(Thread.currentThread().getName() + " : " + --product);
        this.notifyAll();
    }
}


/**
 * 生产者
 */
class Productor implements Runnable{

    private Clerk clerk;

    public Productor(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.sales();
        }
    }
}

十、使用Lock完成生产者消费者问题

package com.artisan.juc;
import java.util.concurrent.locks.Condition;

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

/**
 * @author wannengqingnian
 */
public class ProAndCus {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(productor, "生产者A1").start();
        new Thread(consumer, "消费者B1").start();
        new Thread(productor, "生产者A2").start();
        new Thread(consumer, "消费者B2").start();
    }

}

/**
 * 店员
 */
class Clerk{

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private int product = 0;

    public void get() {
        lock.lock();
        try {
            while (product >= 1){
                System.out.println("产品已满!");

                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //进货
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void sales(){
        lock.lock();
        try {
            while (product <= 0){
                System.out.println("产品缺货!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //卖货
            System.out.println(Thread.currentThread().getName() + " : " + --product);
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }
}


/**
 * 生产者
 */
class Productor implements Runnable{

    private Clerk clerk;

    public Productor(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.get();
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            clerk.sales();
        }
    }
}

十一、读写锁

为什么需要读写锁?

读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制。

Synchronized存在明显的一个性能问题就是读与读之间互斥,读写锁可以解决这个问题。

代码演示:

package com.artisan.juc;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁
 *
 * 创建一个写线程
 * 20个读线程
 * @author wannengqingnian
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();



        new Thread(new Runnable() {

            @Override
            public void run() {
                readWriteLockDemo.set((int) (Math.random() * 101));
            }
        }, "写线程").start();

        for (int i = 1; i <= 20; i++){
            new Thread(new Runnable() {

                @Override
                public void run() {
                    readWriteLockDemo.get();
                }
            }, "读线程 " + i).start();
        }

    }
}

class ReadWriteLockDemo {

    private int number = 0;

    /**
     * 创建读写锁
     */
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void get(){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " : " + number);
        }finally {
            lock.readLock().unlock();
        }
    }

    public void set(int number){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName());
            this.number = number;
        }finally {
            lock.writeLock().unlock();
        }
    }
}

十二、线程八锁

代码演示:

/*
 * 题目:判断打印的 "one" or "two" ?
 * 
 * 1. 两个普通同步方法,两个线程,标准打印, 打印? //one  two
 * 2. 新增 Thread.sleep() 给 getOne() ,打印? //one  two
 * 3. 新增普通方法 getThree() , 打印? //three  one   two
 * 4. 两个普通同步方法,两个 Number 对象,打印?  //two  one
 * 5. 修改 getOne() 为静态同步方法,打印?  //two   one
 * 6. 修改两个方法均为静态同步方法,一个 Number 对象?  //one   two
 * 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象?  //two  one
 * 8. 两个静态同步方法,两个 Number 对象?   //one  two
 * 
 * 线程八锁的关键:
 * ①非静态方法的锁默认为  this,  静态方法的锁为 对应的 Class 实例
 * ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            } 
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
//              number.getTwo();
                number2.getTwo();
            }
        }).start();

        /*new Thread(new Runnable() {
            @Override
            public void run() {
                number.getThree();
            }
        }).start();*/

    }

}

class Number{

    public static synchronized void getOne(){//Number.class
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }

        System.out.println("one");
    }

    public synchronized void getTwo(){//this
        System.out.println("two");
    }

    public void getThree(){
        System.out.println("three");
    }
}

总结:

1、一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法。

2、所有的非静态同步方法用的都是同一把锁 -- 实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已经取锁的非静态同步方法释放锁就可以获取他们自己的锁。

3、所有的静态同步方法用的也是同一把锁 -- 类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争条件。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个实例对象。

原文链接:https://blog.csdn.net/kfengqingyangk/article/details/69485858

十三、线程池

1、为什么使用线程池?

需要并发线程数量很多,可以避免新创建与销毁线程产生额外的开销,提高响应速度,使线程可以复用。线程池提供了一个线程队列,队列中保存着所有等待状态的线程。

2、线程池的体系结构

Java.util.concurrent.Executor : 负责线程的使用与调度的根接口

|-- ExecutorService 子接口:线程池的主要接口

|--ThreadPoolExecutor :线程池的实现类

|--ScheduledExecutorService 子接口 :负责线程的调度

|--ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现ScheduledExecutorService,具备两者的功能

3、工具类 : Executor

ExecutorService new FixedThreadPool() : 创建固定大小的线程池。

ExecutorService newCachedThreadPool() : 缓存线程池,线程池的线程数量不固定,可以根据需求自动更改数量。

ExecutorService newSingleThreadPool() : 创建单个线程的线程池。

ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程池,可以延时或定时执行任务。

4、使用线程池

runnable方法:

package com.artisan.juc;
import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

/**
 * @author wannengqingnian
 */
public class TestThreadPool {

    public static void main(String[] args) {
        TestRunnableDemo demo = new TestRunnableDemo();

        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        //2.为线程池中的线程分配任务
        for (int i = 0; i < 10; i++){
            pool.submit(demo);
        }

        //3.关闭线程
        pool.shutdown();
    }
}

class TestRunnableDemo implements Runnable{
    private int i = 0;
    @Override
    public void run() {
        while (i <= 100){
            System.out.println(Thread.currentThread().getName() + " : " + i++);
        }
    }
}

Callable方式:

package com.artisan.juc;
import java.util.ArrayList;
import  java.util.List;
import java.util.concurrent.*;
/**
 * @author wannengqingnian
 */
public class TestThreadPool {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallableDemo cdemo = new TestCallableDemo();
        List < Future < Integer > > futures = new ArrayList<>();

        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        //2.为线程池中的线程分配任务 Callable方式
        for (int i = 0 ; i < 10; i++){
            Future<Integer> future = pool.submit(cdemo);
            futures.add(future);
        }

        for (Future future : futures){
            System.out.println(future.get());
        }
        //3.关闭线程
        pool.shutdown();
    }
}
class TestCallableDemo implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++){
            sum += i;
        }
        return sum;
    }
}

十四、面试题

三个线程,三个线程ID分别是ABC,打印20遍,显示结果按顺序显示?

package com.artisan.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 三个线程,三个线程ID分别是ABC,打印20遍,显示结果按顺序显示
 * 如 ABCABC...
 * @author wannengqingnian
 */
public class PrintingInSequence {

    public static void main(String[] args) {
        PrintDemo printDemo = new PrintDemo();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1; i <= 20; i++){
                    printDemo.loopA(i);
                }
            }
        }, "A").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1; i <= 20; i++){
                    printDemo.loopB(i);
                }
            }
        }, "B").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1; i <= 20; i++){
                    printDemo.loopC(i);
                }
            }
        }, "C").start();
    }

}

class PrintDemo{
    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void loopA(int total){
        lock.lock();
        try {

            //1.判断是不是到该自己执行,不到则等待
            if (number != 1){
                condition1.await();
            }

            //2.打印自己编号
            for (int i = 1 ; i <= 1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
            }

            //3.自己结束后,唤醒下一个线程
            number = 2;
            condition2.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    public void loopB(int total){
        lock.lock();
        try {

            //1.判断是不是到该自己执行,不到则等待
            if (number != 2){
                condition2.await();
            }

            //2.打印自己编号
            for (int i = 1 ; i <= 1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
            }

            //3.自己结束后,唤醒下一个线程
            number = 3;
            condition3.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
    public void loopC(int total){
        lock.lock();
        try {

            //1.判断是不是到该自己执行,不到则等待
            if (number != 3){
                condition3.await();
            }

            //2.打印自己编号
            for (int i = 1 ; i <= 1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + total);
                System.out.println("==========================");
            }

            //3.自己结束后,唤醒下一个线程
            number = 1;
            condition1.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

本文分享自微信公众号 - JavaArtisan(gh_f521f5243781),作者:万能青年

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JUC 多线程知识杂集

    synchronized是关键字,属于JVM层面,monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于mon...

    万能青年
  • LeetCode算法题(一)

    请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

    万能青年
  • MySQL Explain关键字

    使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。

    万能青年
  • 线程同步

    mathor
  • java 线程之对象的同步和异步(实例讲解)

    下面小编就为大家带来一篇java 线程之对象的同步和异步(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    挨踢小子部落阁
  • 如何创建多线程

    Wait: 等待状态,没有通过notify 或者 notifyAll 唤醒,就会一直进行等待。

    chaplinthink
  • 编写高性能的Java代码需要注意的4个问题

    ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个Th...

    宜信技术学院
  • Java多线程JUC

    1. volatile 关键字 多线程访问的时候,一个比较严重的问题就是内存不可见,其实在内存访问的时候每一个线程都有一个自己的缓冲区,每次在做修改的时候都是从...

    lwen
  • C++-面向对象(六)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    cwl_java
  • java应用CAS

      CAS(Compare and Swap),即比较并替换。jdk里的大量源码通过CAS来提供线程安全操作,比如AtomicInteger类。下面我们来分析一...

    良辰美景TT

扫码关注云+社区

领取腾讯云代金券