Granda's Blog

JAVA:多线程

线程的创建

  • 继承Thread 类创建线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyThread extends Thread{
    //重写run()
    public voud run(){
    System.out.println("This is MyThreas...");
    }
    public static void main(String[] args){
    //启动第一个线程
    new MyThread().start();
    //启动第二个线程
    new MyThread().start();
    }
    }
  • 实现Runnable接口创建线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MyRunnable implements Runnable{
    //重写run()
    public voud run(){
    System.out.println("This is MyThreas...");
    }
    public static void main(String[] args){
    MyRunnable myRunnable = new MyRunnable();
    //启动第一个线程
    new Thread(myRunnable).start();
    //启动第二个线程
    new Thread(myRunnable).start();
    }
    }
  • 使用Callable和Future创建线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class CallableTest {
    public static void main(String[] args){
    //使用Java8新加入的Lambda表达式创建匿名的Callable对象
    //使用FutureTask包装Callable对象,该类的get()方法可以获得Callable的call()方法的返回值
    FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
    //重写Callable的call()方法
    int i = 0;
    for ( ; i < 100 ; i++){
    System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
    }
    //call()方法有返回值
    return i;
    });
    for(int i = 0 ; i < 1000 ;i++){
    System.out.println(Thread.currentThread().getName() + i);
    if(i == 20){
    //实质还是以Callable对象来创建并启动线程
    new Thread(task,"带返回值的线程").start();
    }
    }
    try {
    //这里的get()方法会阻塞,直到子线程执行结束获得返回值
    System.out.println("子线程的返回值是:" + task.get());
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

线程的死亡

  • run()或call()方法执行完成,线程正常结束
  • 线程抛出一个未捕获的Exception或Error
  • 调用线程的stop()方法结束线程,容易导致死锁,不推荐使用。(关闭线程可参考《JAVA:如何优雅地关闭一个线程》)
  • 如果线程没有被阻塞,调用interrupt(),表示进入中断状态,执行到wait(),sleep(),join()时,马上会抛出 InterruptedException;如果线程已经被阻塞,线程就将得到InterruptedException异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。如果异常没有被捕获处理,将会导致线程结束

线程的控制

  • join():执行其它线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完成为止。通常由使用线程的程序调用,将大问题划分成许多小问题,每个小问题分配一个线程,当所用的小问题得到处理后,再调用主线程进一步操作
  • sleep):使线程暂停一段时间,并进入阻塞状态
  • yield():让当前的线程暂停,不会阻塞当前线程,只是将线程转入就绪状态
  • setPriority(int newPriority):改变线程的优先级

线程同步

  • 使用同步代码块,下面示例中的obj就是同步监视器

    1
    2
    3
    4
    synchronized(obj){
    ...
    ...
    }
  • 使用同步方法,同步方法的同步监视器是this,也就是调用该方法的对象

    1
    2
    3
    4
    public synchronized void test(){
    ...
    ...
    }
  • 使用同步锁(Lock)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Test{
    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();
    //定义需要线程安全的方法
    public void eat(){
    //加锁(在锁释放之前,只有一个线程可以获得该锁)
    lock.lock();
    try{
    ...
    ...
    }
    finally{
    //释放锁(一定要放在finally块中,否则程序出现异常会无法释放锁对象,造成死锁)
    lock.unlock();
    }
    }
    }

死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class A{
public synchronized void aTest(B b){
try{
Thread.sleep(200);
}catch(Exception e){
e.printStackTrace();
}
b.last();
}
public synchronized void last(){
}
}
class B{
public synchronized void bTest(A a){
try{
Thread.sleep(200);
}catch(Exception e){
e.printStackTrace();
}
a.last();
}
public synchronized void last(){
}
}
public class DeadLock implements Runnable{
A a = new A();
B b = new B();
pubilc void init(){
a.aTest(b);
}
public void run(){
b.bTest(a);
}
public void static main(String[] args){
DeadLock deadLock = new DeadLock();
new Thread(deadLock).start();
deadLock.init();
}
}
  • 从mian()方法开始看,新线程启动,执行run()方法,新线程获得b对象的锁,进入sleep()等待(此时新线程获得b的锁)
  • 接着main线程继续执行init()方法,main线程活动a的锁,进入sleep()等待(main线程获得a的锁)
  • (新线程)接着run()中的b.bTest(a)方法执行到a.last(),需要获得a对象的锁,而当前a的锁被main线程占有,故阻塞等待
  • (main线程)接着init()中的a.aTest(b)方法执行到b.last(),需要获得b对象的锁,而当前b的锁被新线程占有,故阻塞等待
  • 互相阻塞等待对方释放锁,形成死锁

线程的通信

  • 由同步监视器调用wait()、notify()、notifyAll()方法
  1. wait():导致当前进程等待,直到其他线程调用该同步监视器的notify()、notifyAll()方法唤醒
  2. notify():唤醒在该同步监视器下等待的任意一个线程
  3. notifyAll():唤醒在该同步监视器下等待的全部线程
  • 当用Lock同步线程时,使用Condition控制通信
    1
    2
    3
    4
    5
    private final Lock lock = new ReentrantLock();
    //指定lock对象对应的Condition
    private final Conndition cond = lock.newCondition();
    //使用cond对象调用wait()、notify()、notifyAll()方法控制线程

线程池

  • 由于新建线程的开销比较大,故使用线程池在启动时创建大量空闲线程,将Runnable或者Callable对象传给线程池,将会马上执行对应的run()和call()方法,执行完后不会关闭线程,而是让线程变回空闲状态
  • 使用Executors工厂类来创建线程池,提供以下静态工厂方法创建进程池
  1. ExecutorService newCashedThreadPool():创建具有缓存功能的线程池
  2. ExecutorService newFixedThreadPool(int nThreads):创建一个可重用,具有固定线程数的线程池
  3. ExecutorService newSingleThreadExecutor():创建只有一个线程的线程池
  4. ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建具有指定数量线程的线程池,可以指定延时执行任务
  5. ScheduledExecutorService newSingleThreadScheduledExecutor():创建具有一个线程的线程池,可以指定延时执行任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ThreadPoolTest {
public static void main(String[] args){
//创建具有六个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(6);
Runnable target = () -> {
for(int i = 0 ; i < 100 ; i++){
System.out.println(Thread.currentThread().getName()+"输出:"+i);
}
};
//像线程池提交线程
pool.submit(target);
pool.submit(target);
//关闭线程池
pool.shutdown();
}
}