[关闭]
@w460461339 2016-11-16T07:09:27.000000Z 字数 9045 阅读 983

Java学习Day13(多线程2,设计原则以及设计模式,工厂类)

Java基础


嘛,今天是在上次多线程的基础上,讲了死锁,等待唤醒机制等内容,并且对之前的关于类,方法以及代码块的同步做了深入的解说。

0.多线程状态图

![此处输入图片的描述][1]

1.多线程2(主要是等待唤醒机制啦)

这一次多线程的内容主要从lock开始讲起,介绍了死锁,等待唤醒机制以及其他的一些内容。

1.1 lock以及unlock方法

lock和unlock是在1.5版本之后出现的。最主要的是解决了,利用synchronized同步代码时,对于何时锁上何时关锁不是很明确的问题。

Lock本身是一个接口,我们在创建对象的时候一般利用其实现类,ReentrantLock类。

再次说明一下锁的意思。在多线程中,由于随机性等以及对共有数据进行复杂操作的原因,会使得数据重复或者其他什么问题,这个时候需要限制,当A线程在进行的时候,B线程不能执行同一块代码块,这个限制就是锁。
构造类

  1. package day24_01;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class MyTicket implements Runnable {
  5. //创建共有数据
  6. private int ticket=100;
  7. //创建锁对象
  8. private Lock lock=new ReentrantLock();
  9. @Override
  10. public void run() {
  11. while(true){
  12. //开始上锁
  13. //利用try finally的原因是要保证unlock一定被执行。
  14. try{
  15. lock.lock();
  16. if(ticket>0){
  17. try {
  18. Thread.sleep(500);
  19. } catch (InterruptedException e) {
  20. // TODO Auto-generated catch block
  21. e.printStackTrace();
  22. }
  23. System.out.println(Thread.currentThread(). getName()+
  24. ":"+(ticket--));
  25. }
  26. }finally{
  27. lock.unlock();
  28. }
  29. //释放锁
  30. lock.unlock();
  31. }
  32. }
  33. }

测试类

  1. package day24_01;
  2. public class SellTicketDemo {
  3. public static void main(String[] args) {
  4. MyTicket my=new MyTicket();
  5. Thread td1=new Thread(my,"One");
  6. Thread td2=new Thread(my,"Two");
  7. td1.start();
  8. td2.start();
  9. }
  10. }

Lock类的好处就是明确了什么时候锁上,什么时候打开。但是在构造同步方法或者同步类的时候,还是需要用synchronized关键字的。

1.2死锁问题。

这么理解,A线程关上了1号门,但是要等进了2号门之后才能把一号门打开;B线程关上了2号门,但是要等了进了1号门之后才能把2号门打开。两个线程互相等待,形成了死锁。
构造类

  1. package day24_02;
  2. //因为实现Runnable会数据和操作分离,比较麻烦,这就直接继承了
  3. public class MyDeadLock extends Thread {
  4. //为保证都是用同一个锁对象,所以定义成静态的。
  5. private static Object obj1=new Object();
  6. private static Object obj2=new Object();
  7. //成员变量
  8. private boolean flag;
  9. //构造方法
  10. public MyDeadLock(boolean flag){
  11. this.flag=flag;
  12. }
  13. //覆盖方法
  14. @Override
  15. public void run() {
  16. if(flag){
  17. //flag为true,进入这里
  18. synchronized(obj1){
  19. //当mdl1走到这一步,并且mdl2也过了第一个锁,就形成死锁了
  20. System.out.println("AAA");
  21. synchronized(obj2){
  22. System.out.println("BBB");
  23. }
  24. }
  25. }else{
  26. //flag为false,进入这里
  27. synchronized(obj2){
  28. //mdl2走到这一步时,并且mdl1也过了第一个锁,就形成死锁了,
  29. System.out.println("BBBBB");
  30. synchronized(obj1){
  31. System.out.println("AAAAA");
  32. }
  33. }
  34. }
  35. }
  36. }

测试类

  1. package day24_02;
  2. public class MyDeadLockDemo {
  3. public static void main(String[] args) {
  4. //创建两个线程,mdl1走true,mdl2走false
  5. MyDeadLock mdl1=new MyDeadLock(true);
  6. MyDeadLock mdl2=new MyDeadLock(false);
  7. //开始线程,
  8. mdl1.start();
  9. mdl2.start();
  10. }
  11. }
1.3等待唤醒问题的需求

需求:

希望有两个线程A,B;对同一个数据进行操作;
A线程创造数据;B线程使用数据
但仅当数据被使用后A才创造数据;
亦仅当数据被创造后B才使用数据;
一开始数据不存在;

有可能存在的问题:

A:如何针对同一个数据进行操作
    把对象在线程外部创建,传入线程即可
B:如何处理线程安全问题
    加锁
C:如何实现输出交替,不抢
    这个其实是由于标记的问题,可以用

做的时候出现了这么个异常:

IllegalMonitorStateException

API解释:

抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。

大意就是说,你没有针对该对象的锁资源时,就调用该对象的作为锁时才能用的方法,就报错了。

什么情况会出错:

1>当前线程不含有当前对象的锁资源的时候,调用obj.wait()方法;
2>当前线程不含有当前对象的锁资源的时候,调用obj.notify()方法。
3>当前线程不含有当前对象的锁资源的时候,调用obj.notifyAll()方法。

我之前是在没有写synchronized的时候,就调用了notify方法(因为wait在if里面,不一定被执行,所以没有报它的错),因此报错。

1.4等待唤醒实例

对象构造类

  1. package day24_03;
  2. public class Student {
  3. private String name=null;//其实可以不用写初值的
  4. private int age=0;
  5. private boolean flag=false;//标志变量
  6. //其实可以不要含参构造,只要get,set方法就好
  7. public Student() {
  8. super();
  9. // TODO Auto-generated constructor stub
  10. }
  11. public Student(String name, int age) {
  12. super();
  13. this.name = name;
  14. this.age = age;
  15. }
  16. //一大堆get和set方法
  17. public String getName() {
  18. return name;
  19. }
  20. public void setName(String name) {
  21. this.name = name;
  22. }
  23. public int getAge() {
  24. return age;
  25. }
  26. public void setAge(int age) {
  27. this.age = age;
  28. }
  29. public boolean isFlag() {
  30. return flag;
  31. }
  32. public void setFlag(boolean flag) {
  33. this.flag = flag;
  34. }
  35. }

SetThread类(产生数据类)

  1. package day24_03;
  2. public class SetThread extends Thread {
  3. private Student s;
  4. private int x=0;
  5. //从外界传入s,保证和GetThread用的是用一个
  6. public SetThread(Student s) {
  7. super();
  8. this.s = s;
  9. }
  10. @Override
  11. public void run() {
  12. //while保证一直运行
  13. while(true){
  14. //构造同步代码块,加锁!!
  15. synchronized(s){
  16. if(s.isFlag()){
  17. try {
  18. //当发现数据被创建后,等待
  19. //wait只有当s为锁对象时调用才合法
  20. s.wait();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. //交替给同一对象写不同的名字
  26. if(x%2==0){
  27. s.setName("Roland");
  28. s.setAge(30);
  29. }else{
  30. s.setName("Avril");
  31. s.setAge(24);
  32. }
  33. x++;
  34. //更改状态变量
  35. s.setFlag(true);
  36. //唤醒线程
  37. //当s中的flag是false时,当GetThread抢到资源时,进它
  38. //if,然后就wait了,这个时候SetThread一定能抢到
  39. //并且不走if,直接进行赋值;
  40. //注意Get和Set的锁对象时同一个,此时s还在wait
  41. //所以在Set全部操作完后,要把Get唤醒。
  42. //Get那里同理。
  43. s.notify();
  44. }
  45. }
  46. }
  47. }

GetThread类(使用数据类)

  1. package day24_03;
  2. public class GetThread extends Thread {
  3. private Student s=null;
  4. public GetThread(Student s) {
  5. super();
  6. this.s = s;
  7. }
  8. //不多解释,和Set类差不多
  9. @Override
  10. public void run() {
  11. while(true){
  12. synchronized(s){
  13. if(!s.isFlag()){
  14. try {
  15. s.wait();
  16. } catch (InterruptedException e) {
  17. // TODO Auto-generated catch block
  18. e.printStackTrace();
  19. }
  20. }
  21. System.out.println(s.getName()+"---"+s.getAge());
  22. s.setFlag(false);
  23. s.notify();
  24. }
  25. }
  26. }
  27. }

测试类

  1. package day24_03;
  2. public class WaitCallDemo {
  3. public static void main(String[] args) {
  4. //创建同一个锁对象
  5. Student sd=new Student();
  6. //创建线程对象(用extends Thread实现)
  7. SetThread st=new SetThread(sd);
  8. GetThread gt=new GetThread(sd);
  9. //开启线程
  10. st.start();
  11. gt.start();
  12. }
  13. }

在之前需求分析中提到的三个问题:

 A:如何针对同一个数据进行操作
    外部传入数据
 B:如何处理线程安全问题
    用synchronized(比lock以及unlock用的广泛)
 C:如何实现输出交替,不抢
    利用锁对象的wait和notify(比sleeo好用)

另外,可以把Get和Set的内容全部放到Student类里面,写成用synchronizd修饰方法,到时候Get和Set直接调用里面的方法就好。

1.5线程组

在没有指定的情况下,新建的线程和main都在同一个名为main的线程组下。但可以通过自定义的方式进行修改。

线程组的目的就是为了能对线程进行批量的修改,比如开启,关闭,删除等。

  1. package day24_04;
  2. public class ThreadGroupDemo {
  3. public static void main(String[] args) {
  4. //创建线程对象
  5. MyThread mt=new MyThread();
  6. Thread my1=new Thread(mt);
  7. Thread my2=new Thread(mt);
  8. //获取线程组对象
  9. ThreadGroup tg1=my1.getThreadGroup();
  10. ThreadGroup tg2=my2.getThreadGroup();
  11. //打印名字
  12. System.out.println("my1:"+tg1.getName()+"--"+"my2"+tg2.getName());
  13. System.out.println("----------");
  14. //创建新的线程组对象
  15. ThreadGroup tg3=new ThreadGroup("Brand New Group");
  16. //在创建新的线程对象时就将它们分到tg3线程组下
  17. Thread my3=new Thread(tg3,mt,"Thread3");
  18. Thread my4=new Thread(tg3,mt,"Thread4");
  19. //打印线程组名字
  20. System.out.println(my3.getThreadGroup().getName()+"--"+my4.getThreadGroup().getName());
  21. }
  22. }
1.6线程池

所谓线程池,就是为了避免反复创建线程而消耗的大量时间,事先先创建出指定个线程,然后从里面调用即可。用完后线程自动回归线程池。

线程池最麻烦的一点就是不知道要事先放多少个线程,这个往往需要前期较多的工作测试。

另外,JDK5中内置了线程池,我们不用担心。

具体是这么用的:

A:利用Executors工厂类下的这几个方法:
    public static ExecutorService newCachedThreadPool()
    public static ExecutorService newFixedThreadPool(int nThreads)
    public static ExecutorService newSingleThreadExecutor()
   尤其是第二个,创建固定线程数目的线程池对象,并返回ExecutorService对象

B:利用ExecutorService对象去接受其返回值。

C:调用ExecutorService中方法,开启线程:
    Future<?> submit(Runnable task)
    <T> Future<T> submit(Callable<T> task)
  并利用Future对象去接收

D:调用Future中的get方法来接收线程返回值(仅适用于Callable)

线程池Runnable实现
(只写测试类了)

  1. package day24_05;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. public class ThreadPoolDemo {
  6. public static void main(String[] args) {
  7. //等式左边接收,右边创建一个有两个线程的线程池
  8. ExecutorService es=Executors.newFixedThreadPool(2);
  9. //es.submit就表明从线程池中拿出一个线程,
  10. //并开启执行new MyThread(10)的线程。
  11. Future ft=es.submit(new MyThread(10));
  12. }
  13. }

线程池Callable实现
(第三种多线程方式)
(构造类)

  1. package day24_05;
  2. import java.util.concurrent.Callable;
  3. public class MyThreadCallable implements Callable<Integer> {
  4. private int i;
  5. public MyThreadCallable(int i) {
  6. super();
  7. this.i = i;
  8. }
  9. //实现Call方法, 并设置线程返回值为Integer类型
  10. @Override
  11. public Integer call() throws Exception {
  12. int result=i*i;
  13. return result;
  14. }
  15. }

测试类

  1. package day24_05;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.Future;
  6. public class MyCallableDemo {
  7. public static void main(String[] args) throws InterruptedException, ExecutionException {
  8. //创建只有2个线程的线程池
  9. ExecutorService es=Executors.newFixedThreadPool(2);
  10. //开启线程,并设置Future对象ft1和ft2的get方法返回值类型为Integer
  11. Future<Integer> ft1=es.submit(new MyThreadCallable(10));
  12. Future<Integer> ft2=es.submit(new MyThreadCallable(20));
  13. //取得两个线程的返回值
  14. Integer a=ft1.get();
  15. Integer b=ft2.get();
  16. System.out.println(a+"---"+b);
  17. //关闭线程池
  18. es.shutdown();
  19. }
  20. }

但是太麻烦,所以基本不用

1.7匿名内部类创建线程

这个只是写奇巧淫技而已= -

  1. package day24_06;
  2. public class MyThreadAnDemo {
  3. public static void main(String[] args) {
  4. //相当于以继承Thread的方式创建线程
  5. new Thread(){
  6. @Override
  7. public void run() {
  8. for(int i=0;i<100;i++){
  9. System.out.println("666"+":"+i);
  10. }
  11. }
  12. }.start();
  13. //相当于以实现Runnable接口的方式创建线程
  14. new Thread(new Runnable(){
  15. @Override
  16. public void run() {
  17. for(int i=0;i<100;i++){
  18. System.out.println("777"+":"+i);
  19. }
  20. }
  21. })
  22. { }.start();//这里start前面的大括号{}才是Thread的。
  23. }
  24. }

2.定时器

定时器:

Timer
public Timer()
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task,long delay,long period)
TimerTask
public abstract void run()
public boolean cancel()

大概就是,让自定义类实现TimerTask类,然后通过Timer对象去调用schedule方法,来实现定时功能。

  1. import java.util.Timer;
  2. import java.util.TimerTask;
  3. import day24_06.MytimerTask;
  4. public class MyTimer {
  5. public static void main(String[] args) {
  6. //创建定时器对象
  7. Timer t=new Timer();
  8. //创建定时器任务对象
  9. Mytask mt=new Mytask(t);
  10. //延迟10s后执行任务
  11. t.schedule(mt, 10000);
  12. }
  13. }
  14. class Mytask extends TimerTask{
  15. private Timer t;
  16. public Mytask() {
  17. super();
  18. // TODO Auto-generated constructor stub
  19. }
  20. public Mytask(Timer t) {
  21. super();
  22. this.t = t;
  23. }
  24. //重写run方法,结束后cancel关闭。
  25. @Override
  26. public void run() {
  27. System.out.println("bang"+"--"+"you are dead");
  28. t.cancel();
  29. }
  30. }

3.设计原则和设计模式

设计模式(理解)
(1)面试对象的常见设计原则:

    单一
    开闭
    里氏
    依赖注入
    接口
    迪米特

(2)设计模式概述和分类:

    A:经验的总结
    B:三类
        创建型
        结构型
        行为型

(3)改进的设计模式:

    A:简单工厂模式
    B:工厂方法模式
    C:单例模式(掌握)
        a:饿汉式
        b:懒汉式

(4)Runtime:

    JDK提供的一个单例模式应用的类。
    还可以调用dos命令。

这里就演示下单例模式

3.1单例模式

单例设计思想:

保证类在内存中只有一个对象

如何实现类在内存中只有一个对象呢?:

构造私有
本身提供一个对象
通过公共的方法让外界访问

饿汉式单例设计模式
只写构造类了

  1. package day24_07;
  2. public class Student {
  3. public Student() {
  4. super();
  5. // TODO Auto-generated constructor stub
  6. }
  7. //只有一个对象
  8. private static Student s=new Student();
  9. //只能这么获得该对象
  10. public static Student getS() {
  11. return s;
  12. }
  13. }

懒汉式单例设计模式

  1. package day24_07;
  2. public class Teacher {
  3. public Teacher() {
  4. super();
  5. // TODO Auto-generated constructor stub
  6. }
  7. private static Teacher t;
  8. //在需要时才创建
  9. public static Teacher getT() {
  10. t=new Teacher();
  11. return t;
  12. }
  13. }
3.2单例设计模式之懒汉式和饿汉式对比

单例模式:

    饿汉式:类一加载就创建对象
    懒汉式:用的时候,才去创建对象

面试题:单例模式的思想是什么?请写一个代码体现。

    开发:饿汉式(是不会出问题的单例模式)
    面试:懒汉式(可能会出问题的单例模式)
        A:懒加载(延迟加载) 
        B:线程安全问题
            a:是否多线程环境   是
            b:是否有共享数据   是
            c:是否有多条语句操作共享数据     是
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注