(四十三)、线程的同步和线程池

线程的同步

同步代码块

synchronized放在对象前面限制一段代码的执行

同步代码块定义语法:

synchronized(对象)
{
需要同步的代码;
}
class TicketOffice implements Runnable {
    private int tickets = 10;

    public void run() {
        while (true) {
            synchronized (this) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    tickets--;
                    System.out.println(Thread.currentThread().getName()
                            + ":卖出第" + tickets + "张票");
                } else {
                    break;
                }
            }
        }
    }
}

在上面的代码中,程序将这些需要具有原子性的代码,放入synchronized语句内,形成了同步代码块。在同一时刻只能有一个线程可以进入同步代码块内运行,只有当该线程离开同步代码块后,其它线程才能进入同步代码块内运行。

同步方法

除了可以对代码块进行同步外,也可以对函数实现同步,只要在需要同步的函数定义前加上synchronized关键字即可。
同步方法定义语法:

  访问控制符synchronized 返回值类型 方法名称(参数)
  {
  //…;
  }

等同于:

  访问控制符 返回值类型 方法名称(参数)
  {
      Synchronized(this){
  //…;
  }
  }
class TicketOffice implements Runnable {
    private int tickets = 10;

    public synchronized void run() {
        while (true) {

            if (tickets > 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tickets--;
                System.out.println(Thread.currentThread().getName() + ":卖出第"
                        + tickets + "张票");
            } else {
                break;
            }
        }
    }
}

可见,编译运行后的结果同上面同步代码块方式的运行结果完全一样,也就是说在方法定义前使用synchronized关键字也能够很好地实现线程间的同步。
在同一类中,使用synchronized关键字定义的若干方法,可以在多个线程之间同步,当有一个线程进入了有synchronized修饰的方法时,其它线程就不能进入同一个对象使用synchronized 来修饰的所有方法,此时会将自身线程阻塞,直到第一个线程执行完它所进入的synchronized修饰的方法为止。

BankCard.java 相当于银行卡

public class BankCard {
    private int currentMoney;

    public int getCurrentMoney() {
        return currentMoney;
    }

    public BankCard(int money) {
        this.currentMoney = money;
    }

    public boolean takeMoney(int money) {
        // 使用公共资源作为同步锁
        // 要保证每个线程抢同一个锁
        synchronized (this) {
            if (money <= currentMoney) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                currentMoney -= money;
                return true;
            }
        }
        return false;
    }
}

模拟取钱操作

public class MyRunnable implements Runnable {
    private BankCard card;

    public MyRunnable(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        if (name.equals("丈夫")) {
            if (card.takeMoney(3000)) {
                System.out.println(name + "取钱成功" + ",余额"
                        + card.getCurrentMoney());
            } else {
                System.out.println(name + "取钱失败" + ",余额"
                        + card.getCurrentMoney());
            }
        } else if (name.equals("妻子")) {
            if (card.takeMoney(7000)) {
                System.out.println(name + "取钱成功" + ",余额"
                        + card.getCurrentMoney());
            } else {
                System.out.println(name + "取钱失败" + ",余额"
                        + card.getCurrentMoney());
            }
        }
    }
}

Main.java两人同时取钱时可能出现同步问题

public class Main {
    /*
     * 多个线程同时修改共享资源时会出现同步问题 
     * 1.同步代码块:synchronized(同步锁){ //需要同步的代码 }
     * 2.同步方法:synchronized修饰的方法
     */
    public static void main(String[] args) {
        BankCard card = new BankCard(8000);
        MyRunnable runnable = new MyRunnable(card);

        Thread thread = new Thread(runnable, "丈夫");
        Thread thread2 = new Thread(runnable, "妻子");

        thread.start();
        thread2.start();

    }
}

关于同步锁

任何对象都可以作为同步锁。

1、  使用同步代码块时必须明确指明使用的同步锁。同步也可以使用字节码做为锁。int.class、String.class等均可以作为同步锁。
2、  同步方法也用到的同步锁,此时的锁对象是this。
3、  使用同步锁建议使用公共资源作为锁。换句话说就是,这段代码要修改哪个公共资源,就使用那个公共资源作为同步锁。

死锁问题

一旦有多个线程,且它们都要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。

public class MyThread extends Thread {
    private Rice rice;
    private Sorry sorry;

    public MyThread(String name, Rice rice, Sorry sorry) {
        super(name);
        this.rice = rice;
        this.sorry = sorry;
    }

    @Override
    public void run() {
        String name = this.getName();
        if (name.equals("男")) {
            synchronized (sorry) {
                System.out.println("男方说:先给我做饭我在道歉");
                synchronized (rice) {
                    System.out.println("男方吃上了饭");
                }
            }
        } else {
            synchronized (rice) {
                System.out.println("女方说:先给我道歉我在做饭");
                synchronized (sorry) {
                    System.out.println("男方道了歉");
                }
            }
        }
    }
}
/*
 * 死锁
 */
public class DeathLockDemo {

    public static void main(String[] args) {
        Rice rice = new Rice();
        Sorry sorry = new Sorry();
        new MyThread("男", rice, sorry).start();
        new MyThread("女", rice, sorry).start();
    }
}

线程池

为什么需要线程池

一个线程完成一项任务所需时间为:T1创建线程时间,T2在线程中执行任务的时间,T3销毁线程时间。
线程池技术正是关注如何缩短或调整T1、T3时间的技术,从而提高程序的性能。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目。

系统启动一个新线程的成本是比较高的,因为涉及与操作系统的交互,在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,优先考虑使用线程池。
线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的Run方法。
JDK1.5以前,开发者必须手动实现自己的线程池,JDK1.5开始,Java内建支持线程池

线程池组成部分

一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括:创建线程池、销毁线程池和添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池的创建

线程池通过java.util.concurrent.Executors工厂类来创建。该工厂类包含以下几个静态工厂方法来创建:

  • newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。返回一个ExecutorService对象,代表一个线程池。
  • newFixedThreadPool(int nThreads):创建一个可以重用的、具有固定线程数的线程池。返回一个ExecutorService对象,代表一个线程池。
    • newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于newFixedThreadPool(1)。返回一个ExecutorService对象,代表一个线程池。
  • newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以再指定延迟后执行线程任务。 返回ScheduledExecutorService对象。它可以在指定延迟后执行线程任务。
  • newSingleThreadScheduledExecutor():创建只有一条线程的线程池,它可以在指定延迟后执行线程任务。

ThreadPoolDemo.java

public class ThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {

        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + " "
                            + i);
                }
            }
        };

        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(10);// 创建一个固定线程数的线程池,最多同时运行10个线程
        Future<String> submit = pool.submit(runnable, "ok");// 线程结束时,get()返回第二个参数
        System.out.println("==========");
        System.out.println(submit.get());//阻塞式方法,等线程完成返回一个值
        System.out.println("----------");

        // 关闭线程池
        pool.shutdown();// 等正在运行和正在等待的线程结束在关闭
        pool.shutdownNow();// 直接关闭,并返回正在等待的runnable对象一个list集合
    }
}

ExecutorService

代表一个线程池,只要线程池中有空闲线程立即执行线程任务,程序只要将一个Runnable对象或Callable对象提交给该线程池即可,该线程池就会尽快执行该任务。

  • Future < ? > submit(Runnable task):将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务,其中Future对象代表Runnable任务的返回值,但run方法没有返回值,所有Future对象在run方法执行结束以后返回null,但可以调用Future的isDone、isCancelled方法来获得Runnable对象的执行状态。
  • < T > Future< T > submit(Runnable task, T result):result显示指定线程执行结束后的返回值,所有Future对象将在run方法执行结束后返回result。
public class ThreadPoolDemo2 {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        Callable<Integer> callable = new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum += i;
                }
                return sum;
            }
        };

        ExecutorService pool = Executors.newFixedThreadPool(10);
        Future<Integer> submit = pool.submit(callable);
        Integer integer = submit.get();
        System.out.println(integer);

        pool.shutdown();
    }
}

线程的关闭

  • shutdown():启动线程池的关闭序列,执行之后不再接受新任务,但会将以前所有已提交任务执行完毕,当线程池中的所有任务都执行完毕后,线程池中的线程都会死亡。
  • shutdownNow():该方法视图停止所有正在执行的活动任务,暂停处理在等待的任务,并返回等待执行的任务列表。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章