java内功系列三(多线程)
1.并发和并行区别:并行指某时刻多个同时执行,并发指某时刻只能有一个执行但是由于cpu的轮换执行宏观上感觉是同时执行。
2.线程拥有自己的堆栈、程序计数器、局部变量。但是多个线程只能共享父进程的全部资源。
3.线程启动几种方式。一:继承Thread后重写run方法后就可以实例化对象用star()启动一个线程执行(多个thread之间不能共享)。二:实现Runnable(多个thread启动时传入同一个Runnable对象,可以实现对象共享),在此接口也有一个run方法,在new thread时候最为参数传递给构造函数,最后也是有star()启动。三:callable(call方法有返回值并可以声明异常抛出,它还是函数式接口所以能用lambda)接口,future接口代表callable方法返回值,其中future能对正在执行的线程进行监控。future有个实现类futuretask还实现了runnable接口,因此可以用它包装callable对象然后在作为线程的target在启动。如:
//用lambda表达式创建一个callable对象,然后用FutureTask包装作为线程target
FutureTask<Integer> task=new FutureTask<>((Callable<Integer>)()->{return 1;}) ;
new Thread(task,"有返回值").start();
//用task可以查看线程执行情况进行下一步操作,如果线程抛出异常,可以进行捕获
try {
if(task.isDone())
task.get();//线程执行返回值,如果线程还没有执行完此处或阻塞
} catch (ExecutionException e) {
e.printStackTrace();
}
采用runnable、callable接口方式实现好处是实现的是接口,因此实现类还可以继承或者实现其他接口,当处理相同任务时可以多个线程用同一个对象,实现线程间资源共享。
4.线程状态:新建、就绪、运行、阻塞、死亡。以下是线程各个阶段情况重点看阻塞如下:
5.线程工具:join(阻塞当前线程等待其他线程执行完后再执行),setDaemon(设置当前线程是否为守护线程),yield(不建议使用。当前线程为就绪状态,让调度器重新调度一次,和sleep不一样。),setPriority(设置线程优先级)。
6.线程同步:
使用同步代码块(在{}中的代码安全):
synchronized (obj) { //需要同步的代码}
同步方法,也就是用synchronized修饰方法。
7.释放同步监视器的锁:程序正常或者异常结束导致程序不会在执行。调用了同步监视器的wait方法。以下情况不会释放:Thread.sleep、Thread.yield、suspend挂起线程。
8.同步锁:Lock和ReadWriteLock是锁的2个根接口。分别提高了重入锁ReentrantLock和ReentrantReadWriteLock。java8提供StampedLock,大多数可以代替ReentrantReadWriteLock。
使用锁比同步代码块和同步代码方法好处有几点:灵活不用在固定代码块,前面方式加锁如果出现多次加锁需要按照顺序解锁
private final ReentrantLock lock=new ReentrantLock();
public void m()
{
lock.lock();//加锁
try
{
//安全代码
}
finally {
lock.unlock();//释放锁,必须释放
}
}
9.线程之间的通信:针对用synchronized进行同步的方法或者代码块使用wait(对当前线程挂起并释放锁),notify(通知单个挂起的线程可以继续了),notifyall(通知所有的线程可以继续访问),调用上述3个方法的对象是对应的锁对象。针对用lock对象实现的同步方法采用condition对象。condition(lock.newCondition方法获取)对象类似上述锁对象,提供了对应的await(),signal(),signalall()3个方法。和全面三个方法一一对应。
10.针对生产者和消费者java5提供了blockingqueue接口,对消费者和生产者进行了控制,当容量为空消费者阻塞,当容量满时生产者阻塞。提供了多个实现:
11.给线程设置异常处理类(默认情况在线程组中已经实现了线程异常处理方法,所有的线程都有所属组):
Thread d;
d.setUncaughtExceptionHandler(eh);//设置单个线程实例的异常处理类
d.setDefaultUncaughtExceptionHandler(eh);//设置默认的该类所有实例的线程异常处理类
12.线程池使用executors工厂类提供的几个静态方法创建。
//返回ExecutorService(提供submit方法,可以传递runnable或者callable,返回future对象)
Executors.newCachedThreadPool();//返回缓存线程池
Executors.newFixedThreadPool(arg0);//返回固定个线程线程池
Executors.newSingleThreadExecutor();//返回单个线程池
//返回ScheduledExecutorService(提供Schedule方法,可以传递runnable或者callable并带上延迟参数和周期调用参数,返回Scheduledfuture对象),
//线程池具有调度功能,可以延迟调用或者周期调用
Executors.newScheduledThreadPool(arg0);//
Executors.newSingleThreadScheduledExecutor();
//返回ExecutorService,他是针对硬件内核实现并行计算
Executors.newWorkStealingPool(arg0);
Executors.newWorkStealingPool();//根据内核个数返回能并行计算的线程池
例如:
ExecutorService pool=Executors.newFixedThreadPool(6);
Runnable target=()->{
for(int i=0;i<100;i++)
System.out.println(Thread.currentThread().getName());
};
pool.submit(target);
pool.submit(target);
//关闭线程池
pool.shutdown();
13.为了充分利用CPU多核因此java7提供ForkJoinPool(继承至ExecutorService,也是线程池)把大任务拆分执行。提供了2个构造方法和有一个类静态方法构建ForkJoinPool。构建完后可以传递ForkJoinTask对象给ForkJoinPool开始执行任务。ForkJoinTask是Future的子类,并且有2个子类RecursiveAction(没返回值)和RecursiveTask(有返回值)。
//继承RecursiveTask实现可分解任务(带返回值,RecursiveAction不带返回值)
class CalTask extends RecursiveTask<Integer>
{
//每个小任务最多累加到20个数
private static final int Threshold=20;
private int arr[];
private int start;
private int end;
//累加从start到end的数组
public CalTask(int[] arr,int start,int end)
{
this.arr=arr;
this.start=start;
this.end=end;
}
//继承都要重写这个
@Override
protected Integer compute() {
int sum=0;
//裁缝任务模式
if(end-start<Threshold)
{
for(int i=start;i<end;i++)
{
sum+=arr[i];
}
return sum;
}
else
{
//进一步拆分
int middle=(start+end)/2;
CalTask left=new CalTask(arr, start, middle);
CalTask right=new CalTask(arr, middle, end);
//继续执行
left.fork();
right.fork();
//因为要返回值,所有有这句,如果是RecursiveAction就不需要这句
return left.join()+right.join();
}
}
}
public class Sum
{
public static void main(String[] args) throws Exception
{
int[] arr=new int[100];
Random rand =new Random();
int total=0;
//开始初始化
for(int i = 0 , len=arr.length;i<len;i++)
{
int tmp=rand.nextInt(20);
total+=(arr[i]=tmp);
}
System.out.println(total);
ForkJoinPool pool=ForkJoinPool.commonPool();
//因为也是线程池返回一个Future对象
Future<Integer> future=pool.submit(new CalTask(arr, 0, arr.length) );
System.out.println(future.get());
//关闭线程池
pool.shutdown();
}
}
14.threadlocal可以把变量当做副本进行包装。针对以前的集合使用collections的静态方法可以把非线程安全的集合包装成线程安全的集合。
15.java针对结合重新设计了一套新的线程安全结合,以Concurrent和CopyOnWrite开头。
//ConcurrentHashMap<K, V>
//ConcurrentLinkedDeque<E>
//ConcurrentLinkedQueue<E>
//ConcurrentSkipListMap<K, V>
//ConcurrentSkipListSet<E>
//CopyOnWriteArrayList<E>
//CopyOnWriteArraySet<E>