JAVA常用知识总结(九)——线程

  • sleep和wait的区别?

  1. sleep()来自Thread类,和wait()来自Object类.调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁

  2. sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU

  3. sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用

  • notify()和notifyAll()有什么区别?

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

  • java创建线程的三种方式及其对比

    1、继承Thread类创建线程类

    (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

    (2)创建Thread子类的实例,即创建了线程对象。

    (3)调用线程对象的start()方法来启动该线程。

    2、通过Runnable接口创建线程类

    (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

    (2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

    (3)调用线程对象的start()方法来启动该线程。

    3、通过Callable和Future创建线程

    (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

    public interface Callable
    {
      V call() throws Exception;
    }

    (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)

    (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

    (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

  

package com.thread;  
  
import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.FutureTask;  
  
public class CallableThreadTest implements Callable<Integer>  
{  
  
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }  
  
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
  
}

创建线程的三种方式的对比

1、采用实现Runnable、Callable接口的方式创建多线程时,

优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

2、使用继承Thread类的方式创建多线程时,

优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。

3、Runnable和Callable的区别

(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

(3) call方法可以抛出异常,run方法不可以。

(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

  • Thread.sleep(0)的作用是什么?

    由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,当前线程暂时放弃cpu,这也是平衡CPU控制权的一种操作。

  • 什么是乐观锁和悲观锁

    乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

    悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

  • 线程池的好处和分类?

线程池的好处:
1:通过重复利用已创建的线程,减少在创建和销毁线程上所花的时间以及系统资源的开销。
2:提高响应速度,当任务到达时,任务可以不需要等到线程创建就可以立即执行。
3:提高线程的可管理性,使用线程池可以对线程进行统一的分配和监控。
4:如果不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存。

线程池的注意事项:
1:线程池的大小:多线程应用并非线程越多越好。需要根据系统运行的硬件环境以及应用本身的特点决定线程池的大小。一般来说,如果代码结构合理,线程数与cpu数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小、如果有必要可采用自适应算法来动态调整线程池的大小。以提高cpu的有效利用率和系统的整体性能。
2:并发错误:多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
3:线程泄露:这是线程池应用中的一个严重的问题、当任务执行完毕而线程没能返回池中就会发生线程泄露现象。

其实四种线程池都是 ThreadPoolExecutor ,只是创建参数不同

newSingleThreadExecutor

  创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

 

newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

 

package com.king.lu.Demo;

import java.util.Calendar;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MutiThread {
    private volatile static int a=0;  //https://mp.weixin.qq.com/s/DZkGRTan2qSzJoDAx7QJag
    private final static Integer INDEX = 1000000000;
    //对于静态方法,由于此时对象还未生成,所以只能采用类锁;
    private static void add(){
        synchronized (MutiThread.class) {
            a++;
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        long startTime = System.currentTimeMillis();    //获取开始时间
//        for(int i=0;i<INDEX;i++){
////                    System.out.println(Thread.currentThread().getName());
//                    add();
//        }
        
        
        
//        for(int i=0;i<10000;i++){
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    System.out.println(Thread.currentThread().getName());
//                    add();
//                }
//            }).start();
//        }
        
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
            singleThreadExecutor.execute(new Runnable() {
                public void run() {
                    for(int i=0;i<INDEX;i++){
//                    System.out.println(Thread.currentThread().getName());
                    add();
                    }
                }
            });
        
        
//        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//        scheduledThreadPool.schedule(new Runnable() {
//        @Override
//        public void run() {
//          System.out.println(Thread.currentThread().getName()+"delay 3 seconds");
//        }
//        },3, TimeUnit.SECONDS);
//        
//        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
//        @Override
//        public void run() {
//          System.out.println(Thread.currentThread().getName()+"delay 3 seconds");
//        }
//        }, 1,1, TimeUnit.SECONDS);
        
        
//            ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 
//                cachedThreadPool.execute(new Runnable() {
//                    public void run() {
//                        for(int i=0;i<INDEX;i++){
//    //                        System.out.println(Thread.currentThread().getName());
//                            add();
//                        }
//                    }
//                });
        
        
//            ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
//                fixedThreadPool.execute(new Runnable() {
//                public void run() {
//                    for(int i=0;i<INDEX;i++){
//    //                    System.out.println(Thread.currentThread().getName());
//                            add();
//                        }
//                    
//                    }
//                });
////        
////        
        singleThreadExecutor.shutdown();
        singleThreadExecutor.awaitTermination(1, TimeUnit.HOURS);
            while(true){  
                if(singleThreadExecutor.isTerminated()){  
                    break;
                }
            }
        
         
        
//        while(Thread.activeCount()>1){
//            System.out.println(Thread.activeCount()+","+Thread.currentThread().getName());
//            Thread.yield();
//        }  //保证前面的线程都执行完
        long endTime = System.currentTimeMillis();    //获取结束时间
        System.out.println("程序运行时间:" + (endTime - startTime) + "ms");    //输出程序运行时间
        System.out.println(a);
            
    }     
    
}
  • 什么叫线程安全?servlet是线程安全吗?

线程安全就是说多线程访问同一代码,不会产生不确定的结果。

在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么一定是线程安全的。但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。

线程安全一般都涉及到synchronized, 就是一段代码同时只能有一个线程来操作 不然中间过程可能会产生不可预制的结果。

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

  • 什么是Daemon线程?它有什么意义?

Java语言自己可以创建两种进程“用户线程”和“守护线程”

用户线程:就是我们平时创建的普通线程.

守护线程:主要是用来服务用户线程.

Daemon就是守护线程,他的意义是:

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

  • SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步。而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。

所以,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,这样,即使在遍历map时,如果其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。

posted @ 2018-11-28 17:32  用心记录每一天  阅读(226)  评论(0编辑  收藏  举报