使用线程(一)
共三种使用线程的方法:
实现runnable接口
实现Callable接口
继承thread类
实现Runnable与Callable接口的类只能当作一个可以在线程中执行的任务,不是真正意义上的线程
实现Runnable接口因为比较熟悉,暂时不谈
实现Callable接口
与Runnable相比,Callable可以有返回值,返回值通过FutureTask进行封装
public classMyCallable implents Callable<Integer>{
public Integer call(){
return 123;
}
}
public static void main(String[] args) throws ExecutionException ,InterruptedException{
MyCallable mc =bew MyCallable();
FutureTask<Integer> ft=new FutureTask<>(mc);
Thread thread=new Thread(ft);
thread.start();
}
利用Futuretask来接收返回值
继承Thread类
同样也是需要实现run()方法,因为Thread类也实现了Runable接口
当调用start()方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时就会执行该线程的run()方法
比较
还是实现接口好一点,
(1)Java不支持多继承
(2)类可能只要求可执行,继承Thread开销过大
基础机制
Executor
管理多个异步任务的执行(这里的异步是指多个任务的执行互不干扰,不需要进行同步操作)
主要有三种Executor
CachedThreadPoll:一个任务创建一个线程
FixedThreadPool:所有任务只能使用固定大小的线程
SinglethreadExecutor:相当于大小为1的FiexedthreadPool
public static void main(String[] args){
ExecutorService executorService=Exectors.newCacheThreadPool();
for(int i=0;i<5;i++){
executorService.execute(new myRunnable());
}
executorService.shutdown();
}
Daemon
守护线程时程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分
当所有非守护线程结束时,程序也就终止,同时会杀死所有的守护线程
main()属于非守护线程
在线程启动之前使用setDaemon()方法可以将一个线程设置为守护线程
public static void main(String[] args){
Thread thread=new Thread(new MyRunnable());
thread.setDaemon(true);
}
sleep
Thread.sleep方法会休眠当前正在执行的线程
可能会抛出InterruptedException,因为异常不能跨域线程传播回main()中,因此必须在本地进行处理
yield
调用此静态方法,声明了当前线程已经完成了生命周期中的最重要的部分,可以切换给其他线程来执行
public void run(){
Thread.yield();
}
中断
一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束
InterruptedException
通过调用一个线程的interrupt()来中断该线程,如果该线程处于阻塞,限制等待或者无限期等待状态,那么就会抛出InterruptedException,从而把提前结束该线程。但是不能中断I/O阻塞和synchronizd锁阻塞
interrupted
如果一个线程的run()方法执行一个无限循环,并且没有执行sleep()等会抛出InterruptedException’的操作,那么调用线程的interrupted方法就无法使用线程提前结束
但是调用Interrupt方法会设置线程的中断标记,此时调用interrupted方法会返回true。因此可以在循环体中使用interrupted方法来判断线程是否处于中断状态
public class interruptExample{
private staic class MyThread2 extends Thread{
@Override
public void run(){
while(!interrupted()){
}
}
}
public static void main() throws InterruptedException{
Thread thread2=new MyThread2();
thread.start();
thread.interrupt();
}
Executor的中断操作
调用Executor的shutdown方法会等待线程都执行完毕后再关闭,但是如果调用的是shutdownNoew()方法,则相当于调用线程的interrupt方法
如果只想中断Executer中的一个线程,可以通过使用submit()方法来提交一个线程,它会返回一个Future<?>对象,通过调用该对象的cancel(true)方法就可以中断线程
Future<?> future=executorService.submit(()->{
});
future.cancel(true);
互斥同步
java提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是JVM实现的synchronized,第二个是JDK实现的是ReentrantLock
synchronized
同步一个代码块
public void func(){
synchronized(this){
}
}
只作作用域同一个对象,如果调用两个对象以上的同步代码块,就不会进行同步
同步一个方法
public synchronzied void func(){
}
同步一个类
public void func(){
synchronized(SynchronizedExample.class){
}
}
作用与整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步
同步一个静态方法
public synchronized static void fun(){
}
ReentrantLock
ReentrantLock是java.util.concurrent(JUC)中包含的锁
public class LockExample{
private Lock lock=new ReentranrLock();
public void func(){
lock.lock();
try{
for(int i=0;i,10;i++){
}
}finally{
lock.unlock();//确保释放锁
}
}
}
public static void main(String[] args){
LockExample lockExamle=new LockExample();
ExecutorService executorService=Executors.new CachedThraeadPool();
executorService.execute(()->lockExample.func());
executorService.execute(()->lockExample.func());
}
比较
锁的实现
synchronized是JVM实现的,而ReentrantLock是JDK实现的
性能
新版本java对synchronized进行许多优化
等待可中断
当只有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情
ReentrantLock可中断,synchronized不行
公平锁
synchronized是非公平的,ReentrantLock默认情况也是非公平的,但是也可以是公平的
锁绑定多个条件
一个ReentrantLock可以同时绑定多个Condition对象
使用选择
除非要使用ReentrantLock的高级功能,否则优先使用synchronized。JVM原生支持synchronized,而ReentrantLock不是所有的JDK版本都支持。
使用synchronized不用担心没有释放锁而导致死锁的问题,因为JVM会确保锁的释放