多线程(三)

1、如何在java中创键线程

1、继承Thread类,重写run方法
2、实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3、通过Callable和Future Task创键线程
4、通过线程池创键线程

通过实现Callable接口实现多线程
1、创键Callable接口的实现类,并实现Call方法
2、创键Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值
3、使用FutureTask对象作为Thread对象的target创键并启动线程
4、调用FutureTask对象的get() 来获取子线程执行结束的返回值

public class ThreadDemo03 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Callable<Object> oneCallable = new Tickets<Object>();
        FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);

        Thread t = new Thread(oneTask);

        System.out.println(Thread.currentThread().getName());

        t.start();

    }

}

class Tickets<Object> implements Callable<Object>{

    //重写call方法
    @Override
    public Object call() throws Exception {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
        return null;
    }   
}

通过线程池来实现多线程
Executors 是concurrent 并发包下的一个工具类,可以提供方法创建一个线程池

public class MyClass {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
	
        ExecutorService service = Executors.newFixedThreadPool(2);
        Callable<Integer> callable = new People();
        Callable<Integer> c2 = new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                int count = 0;
                for (int i = 51; i <= 100; i++) {
                    count = count+i;
                }
                return count;
            }
        };
        //Future 接口,获得线程返回的结果  Future Task 类是他的一个实现类
        Future<Integer> s1 = service.submit(callable);
        Future<Integer> s2 = service.submit(c2);
        Integer integer = s1.get();
        Integer integer1 = s2.get();
        //这里打印结果为5050,结果正确,
        有一种特殊情况,就是在子线程还没有执行完毕的时候,主线程就执行完毕,导致其它线程无法执行完毕,
        而这里就没有这种情况,是因为Future 接口中的get方法,执行此方法时会一直等待子线程执行完毕,获得返回值,
        此时的主线程处于一种waiting状态,线程状态下面会讲
        System.out.println(integer + integer1);
    }


}

问题:怎么统计异步运算结果

对于线程池
在java中有java.util.concurrent 包
concurrent中有接口 execute方法,
在这里插入图片描述
可以看出该方法是没有返回值的,
其次还有一个接口 ExecutorService 继承Executor
在这里插入图片描述
该接口中也有一一个方法可以实现异步运算,但是这个含有返回值

2、线程的几种状态
在java.lang包下有一个枚举类,Thread.State ,定义了线程的几种状态
在这里插入图片描述

阻塞状态就是:线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
等待状态:处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。比如使用join等方法时,
超时等待:处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。比如遇到sleep方法等,时间一过就自动唤醒
在这里插入图片描述

3、Java中Runnable和Callable接口的区别

  • Runnable 接口实现run方法,Callable接口实现call方法
  • 实现Callable接口的任务线程能返回执行结果,实现Runnable接口的任务线程不能返回执行结果
  • Callable几口的call()方法允许抛出异常,而Runnable接口的run方法的异常只能内部消化

4、shutdown方法和isTerminated方法的使用

public class MyClassTwo {
    static  int  i = 0;
    public static void main(String[] args) throws Exception{
    //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(2);

        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                for (int i1 = 0; i1 < 100; i1++) {
                    i++;
                }
            }
        };
        es.submit(r1);
        System.out.println("执行结果是==========》" + i);
    }

}

上面的代码执行的结果是:0
原因是主线程率先完成了
解决方案,加shutdown和isTerminated方法

public class MyClassTwo {
    static  int  i = 0;
    public static void main(String[] args) throws Exception{
        ExecutorService es = Executors.newFixedThreadPool(2);

        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                for (int i1 = 0; i1 < 100; i1++) {
                    i++;
                }
            }
        };
        es.submit(r1);
        //启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务
        es.shutdown();
        //isTerminated  如果所有任务在关闭后完成,则返回true
        while(!es.isTerminated()){

        }
        System.out.println("执行结果是==========》" + i);
    }

}

执行结果为:100
什么是ThreadLocal,它的实现原理是什么
多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其他线程无法访问,那么可以使用ThreadLocal

class TestClass implements Callable<String> {

    @Override
    public String call() throws Exception {
        ThreadLocal<String> t1 = new ThreadLocal<>();
        t1.set("天王盖地虎");
        for (int i = 0; i < 10; i++) {
            System.out.println("线程遍历了10次");
        }
        ThreadLocal<String> t2 = new ThreadLocal<>();
        t2.set("宝塔镇河妖");

        System.out.println(t1.get());
        System.out.println(t2.get());
        return null;
    }
}

每一个线程的内部,都有一个hashmap集合,在这个map中的key值是ThreadLocal对象,value值是我们传入的值

什么是线程不安全的:
当多个线程并发的访问同一个临界资源时
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读/写完,其他线程才可使用。不会出现数据不一致或者数据污染。

… … … … … … … … … … … … … . … … … … … … … … …
对于ReentrantLock 和 ReentrantReadWriteLock 有什么区别
对于ReentrantLock ,只是给读或者写加上了锁,不过注意的是解锁应该放到finally中,依然是读写等关系相互互斥,比如20个线程,2个写,18个读,每个操作1s ,则总计耗时20s+;
对于读写锁,读读不互斥,读写互斥,写写互斥,总计耗时为 3s+;

        ReentrantReadWriteLock lock =  new ReentrantReadWriteLock();
        Lock rl = lock.readLock();
        Lock wl = lock.writeLock();

4.1什么是线程池,为什么要用线程池
线程池的作用就是限制系统中执行线程的作用
为什么使用:
1、减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2、可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下
java里面线程池的顶级借口是Executor,但是严格意义上将Executor并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService

5、如何获取一个线程安全的集合
线程安全的集合:vector
Conllections.synchronizedList 还有其他类型的,如set 、map等相似集合
CopyOnWriteArrayList

static List<String> strings = Collections.synchronizedList(new ArrayList<String>());
对于这种方式获得的线程安全的集合,我们可以翻看源代码
        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
可以发现,它在添加、修改、查询等方法的外面都添加了同步,其实和vector集合没有什么区别,性能较低

对于CopyOnWriteArrayList集合

 static CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
查看源代码
    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();
        }
    }
对于读方法
    public E get(int index) {
        return get(getArray(), index);
    }
发现读方法没有上锁,读的只是原来的数组,不会和写操作互斥
最后发现,CopyOnWriteArrayList集合中读操作没有加锁,添加、修改等加上了锁,因此
读读不互斥、读写不互斥、写写互斥,效率更高

总结:CopyOnWriteArrayList
线程安全的ArrayList ,加强版读写分离
写有锁,读无锁,读写之间不阻塞,优于读写锁
写入时,先Copy一个容器副本,再添加新元素,最后替换引用
使用方式和ArrayList无异

同样还有CopyOnWriteArraySet

 CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
 查看源代码
    /**
     * Creates an empty set.
     */
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
发现CopyOnWriteArraySet集合中使用的是CopyOnWriteArrayList集合,也就是说set集合的底层原理和list集合一样,
这是set集合的添加方法
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
    发现,在添加的时候,比list集合中多了一步判断,就是判断集合中是否含有重复数据,如果有,就不添加到数组中
        private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


底层是一个数组
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }


6、ConcurrentHashMap
线程安全的map集合,最大可以支持16个线程并发操作
hashtable同时只能有一个线程执行
在java7和java8中,hashmap和ConcurrentHashMap 发生了很大的变化,对于7版本中,
在这里插入图片描述
Hashmap的的存储结构是哈希表,哈希表就是数组加链表
其实就如图中一样,一共分为16个断,map中添加数据首先计算hash值,判断在哪个断中,每个断都设置了锁,如果多个线程并发访问断并不相同,那么他们就不会相互影响,可以并发操作
7、ConcurrentLinkedQueue 先进先出

1、线程安全,可高效读写的队列,高并发下性能最好的队列
2、无锁,cas比较交换算法,修改的方法包含三个核心参数(V,E,N)
3、V:要更新的变量,E:预期值   N:新值
4、只有当V==E 时,V=N;否则表示已被更新过,否则取消当前操作

Java中提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型列子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列
8、在concurrent包下有一个包atomic,包中有一些类是支持原子操作的,比如AtomicInteger
其实他的内部使用的和上面队列中的安全操作一样

package com.qf.com.qf.day01;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author dongbo
 * @date 2018/11/22 16:46
 */
public class MyClassSeven {

    static ExecutorService es = Executors.newFixedThreadPool(2);
    static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000000; i++) {
                    count.addAndGet(1);
                }
            }
        };
        es.submit(runnable);
        es.submit(runnable);
        es.shutdown();

        while(!es.isTerminated()){

        }
        System.out.println(count);
    }



}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值