java学习之多线程编程4

今日目标:

|-- 原子性相关类(java.util.concurrent.atomic包)
	|-- AtomicInteger
	|-- AtomicLong
	……

|-- ThreadLocal对象
	|-- 使用
	|-- 底层实现
	|-- 注意事项
	|-- 使用场景
|-- 同步问题
	|--(生产者和消费者问题)
|-- 唤醒机制
|-- 线程池
|-- 高并发下的几种常见容器的使用
	|-- ArrayList和CopyOnWriteArrayList
	|-- HashSet和CopyOnWriteArraySet
	|-- HashMap和ConrrentHashMap

高并发三元素

|-- 可见性:
	多线程情况下,多个线程修改共享变量时,
	是不可见的(修改数据,CPU缓存或者寄存器)
	volatile关键字修改后,每一次值被其他线程修改后,
	会立刻刷新内存,保证每一个线程都知道值的变化

|-- 有序性
	内存指令的重排序(效率)
	多线程情况下,禁止指令重排序
|-- 原子性
	最小的,不具备分割的,是一个整体的
	volatile关键字不具备原子性,它修饰的变量,不是最小!!

原子性相关类(java.util.concurrent.atomic包)

java.util.concurrent.atomic这个包下的类,都是具有原子性的!!!

运算符:
	自加		// i++ ==> t = i+1;  i = t; 
	自减
	+=		// i += 3 ==> t = i + 3; i = t;
	-=
不具备原子性,在多线程情况下,会出现线程安全问题
AtomicInteger这些类,所有方法都是使用自旋锁实现的具备原子性的方法
java.util.concurrent.atomic包下的基于原子性的类
	AtomicInteger
	AtomicLong
	AtomicBoolean
package com.openlab.day25.atomic;

import java.util.concurrent.atomic.AtomicInteger;

public class TestAtomic {

    public static void main(String[] args) {
        MyThread01 mt = new MyThread01();
        new Thread(mt).start();
        new Thread(mt).start();
    }

}

class MyThread01 implements Runnable {

    // 一旦使用volatile修饰,虽然是可见,但是因为volatile不具备原子性
    // 因此还是会出现线程安全问题
//    private volatile int count ;

    private AtomicInteger count = new AtomicInteger(0);


    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
//            count++;
            // 这个操作是使用自旋锁,具备原子性
            count.incrementAndGet(); // ++count
//            count.getAndIncrement(); // count++

//            count.getAndAdd(10); // += 10

        }

        System.out.println(count);
    }
}

ThreadLocal对象:

线程对象提供了副本对象,特点是,每一个线程都独立拥有ThreadLocal对象
多线程情况下,一般比较喜欢使用ThreadLocal,多线程情况下,将值保存到
ThreadLocal中,多线程之间都是各自拥有各自的值,不会打架的

set()		// 在自己线程中添加值
get()		// 获取自己线程中存储在ThreadLocal中的值
remove()	// 最后一定要移除值!!!,否则很容易出现内存溢出
package com.openlab.day25.atomic;

public class TestThreadLocal {

//    public static String name = "子线程";

    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 该线程的threadLocal
                threadLocal.set("第一个线程的名字:张三");

                printMsg(threadLocal);

                // threadLocal添加的值,最后一定要移除掉,否则容易出现内存溢出
                threadLocal.remove();
            }
        }).start();

        // 使用lambda表达式完成
        new Thread(() -> {
            // 该线程的threadLocal
            threadLocal.set("另外一个线程的名字:李四");
            printMsg(threadLocal);
            // threadLocal添加的值,最后一定要移除掉,否则容易出现内存溢出
            threadLocal.remove();
        }).start();


    }

    private static void printMsg(ThreadLocal<String> threadLocal) {
        System.out.println(Thread.currentThread().getName() + "---->"+ threadLocal.get());
    }
}

java中存在着四种对象引用:

强引用:
	Object o = new Object();
	o就是一个强引用,不会被gc回收,即便是调用System.gc();
	// o = null;
|-- java的对象引用
	|-- 强引用
	|-- 软引用
	|-- 弱引用
	|-- 虚引用

同步(协同步调)

多个线程开始运行时,受到操作系统、硬件等等一系列的因素的制约,运行时顺序是不可控的!!!

通过程序控制多线程的运行顺序或者步骤,就是同步。
|-- 加锁	// 加锁是一种实现同步的手段,但是这种效率不高
|-- 唤醒机制
	wait()		// 让线程进入等待状态
	notify()	// 唤醒线程
	notifyAll()	// 唤醒所有线程

生产者和消费者模型:

|-- 生产者 消费者两种对象
|-- 生产者生成的商品和消费者消费商品的平衡
|-- 供不应求
|-- 供大于求
唤醒机制:
	一般是使用在两个线程之间,运行时顺序是不可控的!!!
	使用唤醒机制实现同步可控
		wait()		// 让线程进入等待状态
		notify()	// 唤醒其他线程

	需要注意的是:
		1、使用共有的对象做为唤醒的对象存在
		2、必须要加同步锁(synchronized)
		3、同步锁的钥匙必须是共有的对象

	目的就是为了实现线程之间的通信

上代码:
生产者代码:

package com.openlab.day25.syc;

import java.util.Random;

public class Productor implements  Runnable{

    private String[] foods;
    private Disk disk;
    private Random random;

    public String[] getFoods() {
        return foods;
    }

    public void setFoods(String[] foods) {
        this.foods = foods;
    }

    public Disk getDisk() {
        return disk;
    }

    public void setDisk(Disk disk) {
        this.disk = disk;
    }

    public Productor(Disk disk) {
        this.foods = new String[]{"牛肉面", "油泼面", "拉面", "刀削面", "烩面", "蛋炒饭", "拍黄瓜", "红烧肉"};
        this.disk = disk;
        this.random = new Random();
    }

    @Override
    public void run() {
        cookie();
    }

    private void cookie(){
        for (int i = 0; i < 10; i++) {
            synchronized (this.disk) {
                if (!this.disk.isFull()) {
                    // 随机做一种食物
                    int index = this.random.nextInt(this.foods.length);
                    String food = this.foods[index];
                    System.out.println(Thread.currentThread().getName() +"制作了一种食物:"+ food);
                    // 将食物放上去
                    this.disk.setFoodName(food);
                    // 表示盘子上存在了食物
                    this.disk.setFull(true);

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

消费者代码:

package com.openlab.day25.syc;

public class Customer implements Runnable{

    private Disk disk;

    public Disk getDisk() {
        return disk;
    }

    public void setDisk(Disk disk) {
        this.disk = disk;
    }

    public Customer(Disk disk) {
        this.disk = disk;
    }

    public Customer() {
    }

    @Override
    public void run() {
        eat();
    }

    private void eat() {
        while (true) {
            synchronized (this.disk) {
                // 只要盘子上有食物,我们就直接吃食物
                if (this.disk.isFull()) {
                    String food = this.disk.getFoodName();
                    System.out.println(Thread.currentThread().getName() +"开始吃食物:"+ food);
                    this.disk.setFull(false);

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

盘子代码:

package com.openlab.day25.syc;

public class Disk {

    // 盘子上面的食物
    private String foodName;
    private boolean full;

    public String getFoodName() {
        return foodName;
    }

    public void setFoodName(String foodName) {
        this.foodName = foodName;
    }

    public boolean isFull() {
        return full;
    }

    public void setFull(boolean full) {
        this.full = full;
    }

    public Disk() {
    }

    public Disk(String foodName, boolean full) {
        this.foodName = foodName;
        this.full = full;
    }
}

测试代码:

package com.openlab.day25.syc;

public class TestCP {
    public static void main(String[] args) {
        Disk disk = new Disk();
        Productor productor = new Productor(disk);
        Customer customer = new Customer(disk);

        Thread task1 = new Thread(productor);
        Thread task2 = new Thread(customer);

        // 将消费方做成守护线程,让它依赖于生产者
        task2.setDaemon(true);

        task1.start();
        task2.start();


    }
}

通过以上方式就实现了线程之间的通信。

线程池:

池化模式:将大量我们需要的对象提前创建好,放在一个池(概念),
对象提前创建完成,也不需要销毁对象,所以说使用效率比较好
	优点:使用效率比较好,避免对象的重复创建和销毁
	缺点:内存占有较高,池的数量难以把控


池的数量的把控问题才是最关键的

java线程池是 1.5提供 juc包中,底层实现其实就是Callable和Future接口
|-- 根据情况,我们创建很多种不同场景的线程池

package com.openlab.day25.threadpool;

import org.junit.Test;

import java.util.concurrent.*;

public class TestThreadPool {

    @Test
    void test01() {
        // 创建一个单例的线程池
        // 单例线程池:只有一个线程,固定不会变化点一个线程
        // 使用场景:只有一个线程时,建议使用这种线程池
//        ExecutorService executor = Executors.newSingleThreadExecutor();
        ExecutorService executor = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            executor.execute(() -> System.out.println(Thread.currentThread().getName() +"-->"));
        }
    }

    @Test
    void test02() {
        // 创建一个固定大小的线程池
        // 适用场景:并发量不会发生变化(并发量的变化非常小)
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            // 如果想要将外部的变量,传递到匿名内部类中,就需要将该值做成常量
            final int index = i;
            executorService.submit(() -> System.out.println(Thread.currentThread().getName() +"--->"+ index));
        }

    }

    @Test
    void test03() {
        // 	创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程
        // 并发量变化比较明显的,建议使用这种线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            final int index = i;
            executorService.execute(()-> {
                System.out.println(Thread.currentThread().getName() +"--->"+ index);
            });
        }
    }

    @Test
    void test04() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

        for (int i = 0; i < 20; i++) {
            final int index = i;
            // 延时线程池,创建的线程,就有延时执行的特点
            // 第一个参数:需要执行的任务
            // 第二个参数:延时多长
            // 延时的时间单位:秒、小时、毫米、微秒、纳秒、分钟
            scheduledExecutorService.schedule(()-> {
                System.out.println(Thread.currentThread().getName() +"--->"+ index);
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    },
                    5, TimeUnit.SECONDS);
        }

    }

    @Test
    void test05() {

        // 建议使用自己构建的线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(500, 2000, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>());

//        threadPoolExecutor.execute();

    }


}

在intellij idea中使用Junit单元测试

1、需要安装一个插件 Junit插件(setting——>plugins),搜索 Junit
2、根据提示一步一步的安装
3、重启就生效

高并发下的几种常见容器的使用

ArrayList
Vector
LinkedList
HashMap
HashSet
Hashtable

……

强调一个是:容器都存在着线程安全

|-- 线程安全的:
	Vector:加了锁,好处:不存在线程安全问题,坏处:效率比较差

|-- juc包:
	|-- ConrrentHashMap	
	|-- CopyOnWriteArrayList
	|-- CopyOnWriteArraySet
	|-- ConrrentHashMap

	1、线程安全
	2、读写分离
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值