6. 线程安全和线程池

线程安全和线程池知识点

编写多线程程序的几种实现方式(换个问法:创建多线程的方式)?

1.通过继承Thread类

2.通过实现Runnable接口(推荐使用,因为Java中是单继承,一个类只有一个父类,若继承了Thread类,就无法在继承其它类,显然实现Runnable接口更为灵活)

3.通过实现Callable接口(Java 5之后)

解决多线程安全问题的几种方式

1. 同步代码块

在代码块声明上,加上synchronized

Synchronized(锁对象){
  //  可能会产生线程安全问题的代码
}

同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全

2.同步方法

在方法声明上加上synchronized

public synchronized void method(){
    //可能会产生线程安全问题的代码
}

同步方法中的锁对象是this

静态同步方法: 在方法声明上加上static synchronized

静态同步方法中的所对象是类名.class

3.同步锁 lock

lock接口提供了与synchronized 关键字类似的同步功能,但是需要在使用时手动获取锁和释放锁

参考: https://editor.csdn.net/md/?articleId=103837074

Lock与synchronized 有一下区别 锁问题

  1. Lock是一个接口. 而synchronized 是一个关键字

  2. Synchronized 会自动释放锁(不会造成死锁),而lock必须手动释放锁(会造成死锁).

  3. Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去.

  4. 通过lock 可以知道线程有没有拿到锁, 而synchronized不能.

  5. Lock 能提高多个线程读操作的效率

  6. Synchronized 能锁住类, 方法和代码块,而lock是块范围内的

解释 : 如果获取锁的线程需要等待I/O或者调用了sleep()方法被阻塞了,但仍持有锁,其他线程只能干巴巴的等着,这样就会很影响程序效率。 因此就需要一种机制,可以不让等待的线程继续等待下去,比如值等待一段时间或响应中断,Lock锁就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。 但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。 因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。 另外,Lock可以知道线程有没有得到锁,而synchronized不能。

sleep()和wait()有什么区别?

类的不同: sleep()来自Thread; wait()来自Object

释放锁: sleep()不释放锁; wait()释放锁

用法不同: sleep()时间到会自动恢复; wait()可以使用notify()/notifyAll()直接唤醒;

Notify()和notifyAll()有什么区别?

NotifyAll()会唤醒所有的线程,Notify()之后唤醒一个线程. notifyAll()调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行, 如果不成功则留在锁池等待被释放后再次参与竞争,而notify()只会唤醒一个线程, 具体唤醒哪一个线程由虚拟机控制.

线程的run() 和start()有什么区别?

Start()方法用于启动线程, run方法用于执行线程的运行时代码,run()可以重复调用, 而start()方法只能调用一次.

启动一个线程是调用run()方法还是start()方法

在这里插入图片描述

答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,这并不意味着线程就会立即运行.

Run()方法是线程启动后要进行回调(callback(()方法))的方法

线程的生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。

新建:就是刚使用new方法,new出来的线程;

就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;完整的生命周期图如下:
在这里插入图片描述

为什么要使用线程池及常用的几种线程池

1.newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:

(1)工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程

(2)将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

(3)在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();   
for(int i = 0; i < 10; i++){     
    final int index = i;     
    try{        
        Thread.sleep(index * 1000);      
    }catch(InterruptedException e){        
        e.printStackTrace();     
    }     
        cachedThreadPool.execute(new Runnable(){       
            public void run(){         
                System.out.println(index);       
            }      
        });   
}

2.newFixedThreadPool:创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);    
for(int i = 0; i < 10; i++){      
    final int index = i;      
    fixedThreadPool.execute(new Runnable(){        
        public void run(){          
            try{            
                System.out.println(index);            
                Thread.sleep(1000);         
            }catch(InterruptedException e){            
                e.printStackTrace();          
            }        
        }      
    });    
}

因为线程池大小为3,每个任务输出index后sleep 1秒,所以每两秒打印1个数字。 定长线程池的大小最好根据系统资源进行设置如Runtime.getRuntime().availableProcessors()。

3.newSingleThreadExecutor:创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();    
for(int i = 0; i < 10; i++){      
    final int index = i;      
    singleThreadPool.execute(new Runnable(){        
        public void run(){          
            try{            
                System.out.println(index);            
                Thread.sleep(1000);          
            }catch(InterruptedException e){            
                e.printStackTrace();          
            }        
        }      
    });    
}

4.newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。延迟3秒执行,延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);    
scheduledThreadPool.schedule(new Runnable(){      
    public void run(){        
        System.out.println("延迟3秒");      
    }    
    }, 3, TimeUnit.SECONDS);

原文链接:https://blog.csdn.net/freezeriver/article/details/82827302

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值