java之多线程

一、Thread

提到java中的线程,首先想到的是Runnable接口和Thread类。

Runnable很简单,只有一个run方法,通常需要通过Thread代理实现创建新线程。

new Thread(new Runnable() {

    @Override
    public void run() {
        System.out.println("hello");
    }
}).start();

 

Thread是Runnable的实现类,它拥有许多功能,下面简单介绍一下Thread的一些方法:

1、setPriority & getPriority

优先级在一定程度上可以控制线程获取cpu时间片的几率,不过因为cpu处理顺序的不确定性,不同优先级线程获得时间片的几率通常只有不大的差别,这也跟具体的平台有关。

setPriority和getPriority分别可设置和获取优先级,jdk中有10个优先级,但不同的操作系统中的优先级可能不同,所以我们最好使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY三个级别,主线程通常就是NORM_PRIORITY级别。

我们可以在任意时刻设置现成的优先级,只要线程没有结束,都是有效的。如果是在Runnable实现类中,需要在run方法中获取当前线程进行设置。

2、yield

yield方法只是对cpu调度的一种建议,它表明当前线程对于cpu的需求不是那么急迫,可以让给其它线程。不过,最终得到cpu资源的可能仍然是当前线程。

所以对于重要的控制和调整,不能依赖于此方法。

3、join

join方法需要在start方法后调用才生效,此方法将会造成所在线程阻塞,直到调用join的线程执行完毕才继续下去,所在线程可以是主线程也可以是工作线程。

join还有两个有参数的重载方法,参数为时间。所设时间为最长等待时间,如果时间到了,即使线程没执行完,所在线程也会不再阻塞,会继续执行下去。

public static void main(String[] args) {
    Thread t1 = new Thread("t1") {
        public void run() {
            for (int i = 0; i < 10; i++) {

                System.out.println(getName());
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    Thread t2 = new Thread("t2") {
        public void run() {
            try {
                t1.join(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName());
        }
    };

    t1.start();
    t2.start();
}

输出:

t1
t1
t1
t1
t1
t2
t1
t1
t1
t1
t1

 

4、interrupt & isInterrupted & interrupted

isInterrupted和interrupted都是用于判断线程是否中断,其实通常就是判断是否调用了interrupt 方法。

isInterrupted和interrupted调用了同一个方法,只不过参数不同。isInterrupted只是查询状态,interrupted除了查询状态,还会清除状态,使状态变为false

例:

public static void main(String[] args) {
    Thread t = new Thread() {
        public void run() {
            interrupt();
            
            System.out.println(isInterrupted());
            
            System.out.println(isInterrupted());
            
            System.out.println(interrupted());  //注意此处为interrupted
            
            System.out.println(isInterrupted());
        }
    };
    t.start();
}

输出:

true
true
true
false

interrupt方法并不能使线程中断,它的主要功能是解除如sleep、join、wait等方法所造成的阻塞,阻塞解除时,这些方法都会抛出异常。

当interrupt解除阻塞时,中断状态为true,解除阻塞抛出异常后,中断状态变为false。

 

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread() {
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
//                e.printStackTrace();                
            }
        }
    };
    t.start();
    t.interrupt();
    System.out.println(t.isInterrupted());  //输出:true(因为异步,不能100%确保此处在t抛出异常前执行)
    TimeUnit.MILLISECONDS.sleep(1);
    System.out.println(t.isInterrupted());  //输出:false
}

 

 

 5、setDaemon

此方法可将线程设置为后台线程,此方法需在start方法前调用。

后台线程特性:当其它非后台线程结束时,后台线程将随着jvm的终止而结束;后台线程中创建的线程也是后台线程。


 

Callable:

相比于Runnable,Callable多出了返回值。

下面为Callable的简单使用示例:

    public static void main(String[] args) {
        ExecutorService service=Executors.newCachedThreadPool();
        Callable<String> command=new Callable<String>() {
            @Override
            public String call() throws Exception {
                TimeUnit.SECONDS.sleep(2);
                return "call result";
            }
        };
        Future<String> future=service.submit(command);
        while(!future.isDone()){
        }
        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdown();
        /*
        output:
            call result
        */    
    }
View Code

 

Callable需要ExecutorService对象调用submit方法来启动,submit方法会返回一个Future对象。

Future对象的isDone方法判断Callable线程是否执行完毕;get方法将获得call方法中的返回值,不过此方法有可能会造成阻塞,会等到Callable执行完毕才会返回。

我们可以在调用get方法前先通过isDone方法判断线程是否执行完毕,或者使用可设置超时时间的有参get方法。

 


 

ExecutorService:

ExecutorService用于管理线程,简化并发开发。常见的ExecutorService有CachedThreadPool、FixedThreadPoll、SingleThreadPool、ScheduledThreadPoolExecutor。

1.CachedThreadPool

CachedThreadPool会根据需求创建线程,添加新任务后,如果有空闲线程,新任务会放到空闲线程中运行,否则重新创建新线程。

当一个线程结束,它会继续保留1分钟时间,在一分钟内如果有新任务,新任务将会分配到已经结束的空闲线程中,如果没有新任务,一分钟后空闲线程将会关闭。
在CachedThreadPool和FixedThreadPool中,前者更加灵活,通常作为优先选项。

2.FixedThreadPoll

FixedThreadPoll可以设置最大线程数,且若不调用shutdown或shutdownNow方法,创建的线程不会关闭。

3.SingleThreadPool

可以看作是线程数为1的FixThreadPool。

4、ScheduledThreadPoolExecutor

这里需要提一下的是scheduleWithFixedDelay和scheduleAtFixedRate方法的区别:

两者只是在延迟方面有些差异,前者是在上一次任务完成后延迟所设时间再进行下一次;后者是在上一次开始后延迟所设时间在进行下一次,不过如果此时上一次未完成,要等待完成再进行下一次。

 

ExecutorService的常用方法:

 1.shutdown&shutdownNow

shutdown方法调用后,ExecutorService不能再添加新的任务;shutdownNow方法调用后也不能在添加新的任务,同时,解除正在运行线程的阻塞状态,并返回未运行的任务的Collection集合。

2.isShutdown&isTerminated&awaitTermination

isShutdown用于判断ExecutorService是否关闭,其实就是看有没有调用shutdown或shutdownNow方法。

isTerminated则是判断任务是否终止,任务终止才为true,而任务终止的条件是:

1).调用showdown或showdownNow方法。

2).任务完成ExecutorService不再工作。

awaitTermination与isTerminated类似,不过相比于isTerminated增加了超时时间。此方法会造成阻塞,无论是线程池任务完成还是超时时间到了都返回值。
3.invokeAll&invokeAny

前者在所有任务完成后返回值,后者在有任何一个任务完成就返回值,两者都会阻塞线程,两者分别都有一个超时重载方法。


 

volatile:

根据JMM原理,在多线程工作中,共享在资源保存在主内存中,但是工作线程不能直接对主存中的数据进行造作,而是在各自的线程中创建一个缓存副本,对副本中的数据进行操作。

volatile修饰的数据具有可见性,各个线程在读取这类数据时都会从主存中读取,每次写入也都会刷新到主存。

但是volatile并不具有原子性,在使用它时,通常要遵循两个条件:

1、对变量的操作不能依赖于它之前的值

2、变量的值不能受其它变量的限制

例如当我们在循环模式中使用的boolean类型的标记就通常使用volatile修饰。

受限于这两个个条件,volatile的适用场合非常小,再加上容易出错,所以我们的首选方法通常是使用synchronized。


 

synchronized:

synchronized具有原子性,在实行Synchronized保护的代码时,要经过一下几个步骤:

检查锁是否可用——获取锁——执行代码——释放锁。

synchronized可分为方法锁和同步块,两者又分别可分为静态和非静态。

方法锁:

方法锁主要有以下几个特点:

1.在一个类中,所有静态方法的锁是同一把锁——类锁,因此它们是互斥的;

2.在一个对象中,所有的非静态方法也是共用一把锁——对象锁,它们之间也互斥;

3.同一个类的不同对象,非静态方法用的锁是不同的。

同步块:

同步块中的锁也可分为类锁与方法锁,稍有不同的是,在一个类中,可以有多个锁,最后究竟是否互斥,只需判断锁是否是同一个对象即可。

通常,我们会将锁对象设置为private,以防止不小心改变锁,而产生错误,下面就是一个错误的示例,它会抛出空指针异常:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Worker w = new Worker();
        w.start();
        TimeUnit.SECONDS.sleep(3);
        w.object = null;
    }
}

class Worker extends Thread {
    Object object = new Object();

    public void test() {
        synchronized (object) {
            System.out.println("hello");
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            test();
        }
    }
}
View Code

 

除了使用synchronized关键字,也可以显示调用Lock对象来达到目的:

public class Test {

    public static void main(String[] args) {
        
        MyRunnable task=new MyRunnable();
        for (int i = 0; i < 1000; i++) {
            new Thread(task).start();
        }
        while(Thread.activeCount()>1){
            Thread.yield();
        }
        System.out.println(task.getCount());
    }
    

}

class MyRunnable implements Runnable {

    private Lock lock = new ReentrantLock();

    private int count = 0;

    public int getCount() {
        return count;
    }

    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
View Code

这种方法使用了try-finnally,看起来代码比synchronized多,但是它有个有点,在出现异常时,可以在finnally中进行清理工作,使系统保持良好的状态。

需要注意的是,如果有返回值,需要在try中return,防止unlock过早调用,破坏了原子性。

大体上,使用synchronized更加简单,出错的可能性更小,所以,如果没有特殊情况,通常优先使用synchronized。

不过Lock对象可以实现一些synchronized无法实现的一些功能,如Lock对象可以尝试获取锁,然后根据是否获取成功进行处理:

    private Lock lock = new ReentrantLock();

    public void test() {
        if (lock.tryLock()) {
            try {
                //获取锁后的操作
            } finally {
                lock.unlock();
            }
        } else {
            //未获取锁的操作
        }
    }
View Code

 

原子类:

jdk中包含了诸如AtomicInteger、AtomicReference、AtomicBoolean、AtomicLong等原子性变量类,通过他们可以实现一些原子操作:

public class Test {

    public static void main(String[] args) {

        MyRunnable task = new MyRunnable();
        for (int i = 0; i < 10000; i++) {
            new Thread(task).start();
        }
        while(Thread.activeCount()>1){
            Thread.yield();
        }
        System.out.println(task.getCount());

    }

}

class MyRunnable implements Runnable {

    private AtomicInteger count=new AtomicInteger(0);
    
    public int getCount(){
        return count.get();
    }
    
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.addAndGet(1);
    }

}
View Code

 


 

wait、notify、notifyAll:

wait方法能使能使线程挂起,等到被notify或notifyAll唤醒才继续执行下去,这个功能有点类似于空循环,直到某个条件达成才中断循环,不过空循环会浪费cpu资源,是一种不良的使用方式。

wait此方法能够释放锁,在wait等待期间与它原本互斥的synchronized同步块可以执行。

wait还有一个超时方法,当超时后,线程自动唤醒,继续执行下去,当然,前提是你得获得锁。

notify只能唤醒一个wait中的线程,而notifyAll能唤醒所有wait中的线程,当然,这得在同一个锁的基础上。所以当我们看到wait无法被唤醒使,去看看是否是他们是否是同一个锁,主要是在以this为锁的时候容易犯错。

 


 

CountDownLatch:

我们可能会遇到这样的情况,有个任务task需要等待task0,task1……taskn都完成后才能执行,要实现这个效果,可以使用join方法,不过使用CountDownLatch更加简单:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class T {
    public static void main(String[] args) {
        final int COUNT=10;
        CountDownLatch latch=new CountDownLatch(COUNT);
        ExecutorService service=Executors.newCachedThreadPool();
        for (int i = 0; i < COUNT; i++) {
            service.execute(new MyRunnable(latch));
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("all tasks are finished");
        service.shutdown();
    }
}

class MyRunnable implements Runnable{

    private final CountDownLatch latch;
    
    public MyRunnable(CountDownLatch latch) {
        this.latch=latch;
    }
    
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count");
        latch.countDown();
    }
    
}
View Code

 

 


 

 

Semaphore

semaphore是计数信号量,可通过acquire请求获得信号量,release方法释放信号量。

线程运行时请求获得信号量,如果semaphore中的资源不足,则会造成阻塞,等获得信号量时才继续运行下去。

例子:比如公共厕所有两个蹲位,同一时间最多只能有两个人使用。

public class Test {


    public static void main(String[] args) {
        for(int i =0 ;i< 10;i++) {
            new Worker("hero:"+i).start();;
        }
    }
}

class Worker extends Thread{
    static Semaphore semaphore =new Semaphore(2);
    public Worker(String name) {
        super(name);
    }
    @Override
    public void run() {
        try {
            semaphore.acquire(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName()+" 获得宝座");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName()+" 退位让贤");
        semaphore.release();
        
    }
}

 

posted @ 2016-04-06 19:00  maozs  阅读(271)  评论(0编辑  收藏  举报