java 多线程入门教程

课程笔记

多线程的引入(了解)

  • 1.什么是线程
    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
  • 2.多线程的应用场景
    • 红蜘蛛同时共享屏幕给多个电脑
    • 迅雷开启多条线程一起下载
    • QQ同时和多个人一起视频
    • 服务器同时处理多个客户端请求

多线程并行和并发的区别(了解)

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
  • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

Java程序运行原理和JVM的启动是多线程的吗(了解)

  • A:Java程序运行原理

    • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
  • B:JVM的启动是多线程的吗

    • JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

多线程程序实现的方式1(掌握)

  • 1.继承Thread
    • 定义类继承Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 开启新线程, 内部会自动执行run方法
public class Demo2_Thread {
	public static void main(String[] args) {
		MyThread mt = new MyThread();							//4,创建自定义类的对象
		mt.start();												//5,开启线程
		
		for(int i = 0; i < 3000; i++) {
			System.out.println("bb");
		}
	}

}
class MyThread extends Thread {									//1,定义类继承Thread
	public void run() {											//2,重写run方法
		for(int i = 0; i < 3000; i++) {							//3,将要执行的代码,写在run方法中
			System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
		}
	}
}

多线程程序实现的方式2(掌握)

  • 2.实现Runnable
    • 定义类实现Runnable接口
    • 实现run方法
    • 把新线程要做的事写在run方法中
    • 创建自定义的Runnable的子类对象
    • 创建Thread对象, 传入Runnable
    • 调用start()开启新线程, 内部会自动调用Runnable的run()方法
public class Demo3_Runnable {
		public static void main(String[] args) {
			MyRunnable mr = new MyRunnable();						//4,创建自定义类对象
			//Runnable target = new MyRunnable();
			Thread t = new Thread(mr);								//5,将其当作参数传递给Thread的构造函数
			t.start();												//6,开启线程
			
			for(int i = 0; i < 3000; i++) {
				System.out.println("bb");
			}
		}
	}
	
	class MyRunnable implements Runnable {							//1,自定义类实现Runnable接口
		@Override
		public void run() {											//2,重写run方法
			for(int i = 0; i < 3000; i++) {							//3,将要执行的代码,写在run方法中
				System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
			}
		}
		
	}

实现Runnable的原理(了解)

  • 查看源码
    • 1,看Thread类的构造函数,传递了Runnable接口的引用
    • 2,通过init()方法找到传递的target给成员变量的target赋值
    • 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

两种方式的区别(掌握)

  • 查看源码的区别:

    • a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
    • b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
  • 继承Thread

    • 好处是:可以直接使用Thread类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能用这种方法
  • 实现Runnable接口

    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

匿名内部类实现线程的两种方式(掌握)

  • 继承Thread类
		new Thread() {													//1,new 类(){}继承这个类
			public void run() {											//2,重写run方法
				for(int i = 0; i < 3000; i++) {							//3,将要执行的代码,写在run方法中
					System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
				}
			}
		}.start();
  • 实现Runnable接口
		new Thread(new Runnable(){										//1,new 接口(){}实现这个接口
			public void run() {											//2,重写run方法
				for(int i = 0; i < 3000; i++) {							//3,将要执行的代码,写在run方法中
					System.out.println("bb");
				}
			}
		}).start(); 

获取名字和设置名字(掌握)

  • 1.获取名字
    • 通过getName()方法获取线程对象的名字
  • 2.设置名字
    • 通过构造函数可以传入String类型的名字
			new Thread("xxx") {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
					}
				}
			}.start();
			
			new Thread("yyy") {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "....bb");
					}
				}
			}.start(); 
* 通过setName(String)方法可以设置线程对象的名字
	
			Thread t1 = new Thread() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
					}
				}
			};
			
			Thread t2 = new Thread() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "....bb");
					}
				}
			};
			t1.setName("芙蓉姐姐");
			t2.setName("凤姐");
			
			t1.start();
			t2.start();

获取当前线程的对象(掌握)

  • Thread.currentThread(), 主线程也可以获取
			new Thread(new Runnable() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(Thread.currentThread().getName() + "...aaaaaaaaaaaaaaaaaaaaa");
					}
				}
			}).start();
			
			new Thread(new Runnable() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(Thread.currentThread().getName() + "...bb");
					}
				}
			}).start();
			Thread.currentThread().setName("我是主线程");					//获取主函数线程的引用,并改名字
			System.out.println(Thread.currentThread().getName());		//获取主函数线程的引用,并获取名字

休眠线程(掌握)

  • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
	new Thread() {
		public void run() {
			for(int i = 0; i < 10; i++) {
				System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}.start();

	new Thread() {
		public void run() {
			for(int i = 0; i < 10; i++) {
				System.out.println(getName() + "...bb");
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}.start();

守护线程(掌握)

  • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
			Thread t1 = new Thread() {
				public void run() {
					for(int i = 0; i < 50; i++) {
						System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			};
			
			Thread t2 = new Thread() {
				public void run() {
					for(int i = 0; i < 5; i++) {
						System.out.println(getName() + "...bb");
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			};
			
			t1.setDaemon(true);						//将t1设置为守护线程
			
			t1.start();
			t2.start();

加入线程(掌握)

  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续
final Thread t1 = new Thread() {
	public void run() {
		for(int i = 0; i < 50; i++) {
			System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
};

Thread t2 = new Thread() {
	public void run() {
		for(int i = 0; i < 50; i++) {
			if(i == 2) {
				try {
					//t1.join();						//插队,加入
					t1.join(30);						//加入,有固定的时间,过了固定时间,继续交替执行
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(getName() + "...bb");
		}
	}
};

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

礼让线程(了解)

  • yield让出cpu

设置线程的优先级(了解)

  • setPriority()设置线程的优先级

同步代码块(掌握)

  • 1.什么情况下需要同步
    • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
  • 2.同步代码块
    • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
    • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
class Printer {
	Demo d = new Demo();
	public static void print1() {
		synchronized(d){				//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
		}
	}
public static void print2() {	
	synchronized(d){	
		System.out.print("传");
		System.out.print("智");
		System.out.print("播");
		System.out.print("客");
		System.out.print("\r\n");
	}
}
}

同步方法(掌握)

  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
class Printer {
	public static void print1() {
		synchronized(Printer.class){				//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
		}
	}
	/*
	 * 非静态同步函数的锁是:this
	 * 静态的同步函数的锁是:字节码对象
	 */
	public static synchronized void print2() {	
		System.out.print("传");
		System.out.print("智");
		System.out.print("播");
		System.out.print("客");
		System.out.print("\r\n");
	}
}

线程安全问题(掌握)

  • 多线程并发操作同一数据时, 就有可能出现线程安全问题
  • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
public class Demo2_Synchronized {

	/**
	 * @param args
	 * 需求:铁路售票,一共100张,通过四个窗口卖完.
	 */
	public static void main(String[] args) {
		TicketsSeller t1 = new TicketsSeller();
		TicketsSeller t2 = new TicketsSeller();
		TicketsSeller t3 = new TicketsSeller();
		TicketsSeller t4 = new TicketsSeller();
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

class TicketsSeller extends Thread {
	private static int tickets = 100;
	static Object obj = new Object();
	public TicketsSeller() {
		super();
		
	}
	public TicketsSeller(String name) {
		super(name);
	}
	public void run() {
		while(true) {
			synchronized(obj) {
				if(tickets <= 0) 
					break;
				try {
					Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(getName() + "...这是第" + tickets-- + "号票");
			}
		}
	}
}

火车站卖票的例子用实现Runnable接口(掌握)

死锁(了解)

  • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
    • 尽量不要嵌套使用
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
	new Thread() {
		public void run() {
			while(true) {
				synchronized(s1) {
					System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
					synchronized(s2) {
						System.out.println(getName() + "...拿到" + s2 + "开吃");
					}
				}
			}
		}
	}.start();
	
	new Thread() {
		public void run() {
			while(true) {
				synchronized(s2) {
					System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
					synchronized(s1) {
						System.out.println(getName() + "...拿到" + s1 + "开吃");
					}
				}
			}
		}
	}.start();
}

以前的线程安全的类回顾(掌握)

  • A:回顾以前说过的线程安全问题
    • 看源码:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
    • Vector是线程安全的,ArrayList是线程不安全的
    • StringBuffer是线程安全的,StringBuilder是线程不安全的
    • Hashtable是线程安全的,HashMap是线程不安全的

总结

  • 多线程:多任务。同时执行多项工作。

  • 实现多线程的两种方式(掌握):

    • 继承Thread类,重写run()方法,把要执行的代码放到run里面,然后创建子类对象,调用start()方法启动线程;

    • 实现Runnable接口,重写润run()方法,把要执行的代码放到run里面,然后创建实现类对象,并且以参数的形式传递给Thread类的构造方法,通过Thread类的对象来启动线程;

    • getName();

    • setName(String);

    • Thread.currentThread();

    • 建议用匿名内部类的方式来实现多线程代码;

  • sleep() -->休眠

  • setDaemon() -->守护

  • join() -->加入,插队

  • yield() -->礼让线程

  • setPriority() -->从1-10,默认是5

  • 同步代码块:synchronized

    • 把操作共享数据的代码包起来
  • 同步方法:

    • 普通方法:锁是this对象
    • 静态方法:锁是该类的字节码文件对象
  • 掌握火车站买票的例子:用Runnable接口的实现方式

  • 死锁:两个线程相互持有对方的锁就会造成死锁现象;(避免方法:不要嵌套使用同步代码块)

单例设计模式(掌握)

  • 单例设计模式:保证类在内存中只有一个对象。

  • 如何保证类在内存中只有一个对象呢?

    • (1)控制类的创建,不让其他类来创建本类的对象。private
    • (2)在本类中定义一个本类的对象。Singleton s;
    • (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
  • 单例写法两种:

    • (1)饿汉式 开发用这种方式。
//饿汉式
class Singleton {
	//1,私有构造函数
	private Singleton(){}
	//2,创建本类对象
	private static Singleton s = new Singleton();
	//3,对外提供公共的访问方法
	public static Singleton getInstance() {
		return s;
	}
	
	public static void print() {
		System.out.println("11111111111");
	}
}
* (2)懒汉式 面试写这种方式。多线程的问题?
//懒汉式,单例的延迟加载模式
class Singleton {
	//1,私有构造函数
	private Singleton(){}
	//2,声明一个本类的引用
	private static Singleton s;
	//3,对外提供公共的访问方法
	public static Singleton getInstance() {
		if(s == null)
			//线程1,线程2
			s = new Singleton();
		return s;
	}
	
	public static void print() {
		System.out.println("11111111111");
	}
}
* (3)第三种格式
class Singleton {
	private Singleton() {}
	public static final Singleton s = new Singleton();//final是最终的意思,被final修饰的变量不可以被更改
}

Runtime类

  • Runtime类是一个单例类
Runtime r = Runtime.getRuntime();
//r.exec("shutdown -s -t 300");		//300秒后关机
r.exec("shutdown -a");				//取消关机

Timer(掌握)

  • Timer类:计时器
public class Demo5_Timer {
	/**
	 * @param args
	 * 计时器
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		Timer t = new Timer();
		t.schedule(new MyTimerTask(), new Date(114,9,15,10,54,20),3000);
		
		while(true) {
			System.out.println(new Date());
			Thread.sleep(1000);
		}
	}
}
class MyTimerTask extends TimerTask {
	@Override
	public void run() {
		System.out.println("起床背英语单词");
	}
	
}

两个线程间的通信(掌握)

  • 1.什么时候需要通信
    • 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
    • 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
  • 2.怎么通信
    • 如果希望线程等待, 就调用wait()
    • 如果希望唤醒等待的线程, 就调用notify();
    • 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

三个或三个以上间的线程通信

  • 多个线程通信的问题
    • notify()方法是随机唤醒一个线程
    • notifyAll()方法是唤醒所有线程
    • JDK5之前无法唤醒指定的一个线程
    • 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件

JDK1.5的新特性互斥锁(掌握)

  • 1.同步
    • 使用ReentrantLock类的lock()和unlock()方法进行同步
  • 2.通信
    • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
    • 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
    • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

线程组的概述和使用(了解)

  • A:线程组概述
    • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    • 默认情况下,所有的线程都属于主线程组。
      • public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
      • public final String getName()//通过线程组对象获取他组的名字
    • 我们也可以给线程设置分组
      • 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
      • 2,创建线程对象
      • 3,Thread(ThreadGroup?group, Runnable?target, String?name)
      • 4,设置整组的优先级或者守护线程
    • B:案例演示
      • 线程组的使用,默认是主线程组

		MyRunnable mr = new MyRunnable();
		Thread t1 = new Thread(mr, "张三");
		Thread t2 = new Thread(mr, "李四");
		//获取线程组
		// 线程类里面的方法:public final ThreadGroup getThreadGroup()
		ThreadGroup tg1 = t1.getThreadGroup();
		ThreadGroup tg2 = t2.getThreadGroup();
		// 线程组里面的方法:public final String getName()
		String name1 = tg1.getName();
		String name2 = tg2.getName();
		System.out.println(name1);
		System.out.println(name2);
		// 通过结果我们知道了:线程默认情况下属于main线程组
		// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
		System.out.println(Thread.currentThread().getThreadGroup().getName());

* 自己设定线程组
		// ThreadGroup(String name)
		ThreadGroup tg = new ThreadGroup("这是一个新的组");

		MyRunnable mr = new MyRunnable();
		// Thread(ThreadGroup group, Runnable target, String name)
		Thread t1 = new Thread(tg, mr, "张三");
		Thread t2 = new Thread(tg, mr, "李四");
		
		System.out.println(t1.getThreadGroup().getName());
		System.out.println(t2.getThreadGroup().getName());
		
		//通过组名称设置后台线程,表示该组的线程都是后台线程
		tg.setDaemon(true);

线程的五种状态(掌握)

  • 看图说话
  • 新建,就绪,运行,阻塞,死亡

线程池的概述和使用(了解)

  • A:线程池概述
    • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • B:内置线程池的使用概述
    • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
      • public static ExecutorService newFixedThreadPool(int nThreads)
      • public static ExecutorService newSingleThreadExecutor()
      • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
      • Future<?> submit(Runnable task)
      • Future submit(Callable task)
    • 使用步骤:
      • 创建线程池对象
      • 创建Runnable实例
      • 提交Runnable实例
      • 关闭线程池
    • C:案例演示
      • 提交的是Runnable
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);

// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());

//结束线程池
pool.shutdown();

多线程程序实现的方式3(了解)

  • 提交的是Callable
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);

// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));

// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();

System.out.println(i1);
System.out.println(i2);

// 结束
pool.shutdown();

public class MyCallable implements Callable<Integer> {

	private int number;

	public MyCallable(int number) {
		this.number = number;
	}

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

}
  • 多线程程序实现的方式3的好处和弊端
    • 好处:

      • 可以有返回值
      • 可以抛出异常
    • 弊端:

      • 代码比较复杂,所以一般不用

简单工厂模式概述和使用(了解)

  • A:简单工厂模式概述
    • 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责
  • C:缺点
    • 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
  • D:案例演示
    • 动物抽象类:public abstract Animal { public abstract void eat(); }
    • 具体狗类:public class Dog extends Animal {}
    • 具体猫类:public class Cat extends Animal {}
    • 开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。
public class AnimalFactory {
	private AnimalFactory(){}

	//public static Dog createDog() {return new Dog();}
	//public static Cat createCat() {return new Cat();}

	//改进
	public static Animal createAnimal(String animalName) {
		if(“dog”.equals(animalName)) {}
		else if(“cat”.equals(animale)) {

		}else {
			return null;
		}
	}
}

工厂方法模式的概述和使用(了解)

  • A:工厂方法模式概述
    • 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
  • C:缺点
    • 需要额外的编写代码,增加了工作量
  • D:案例演示
  •   动物抽象类:public abstract Animal { public abstract void eat(); }
      工厂接口:public interface Factory {public abstract Animal createAnimal();}
      具体狗类:public class Dog extends Animal {}
      具体猫类:public class Cat extends Animal {}
      开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。发现每次修改代码太麻烦,用工厂方法改进,针对每一个具体的实现提供一个具体工厂。
    
		狗工厂:public class DogFactory implements Factory {
			public Animal createAnimal() {…}
		        }
		猫工厂:public class CatFactory implements Factory {
			public Animal createAnimal() {…}
		        }  

day25总结

  • 单例设计模式(重点掌握):
    • 饿汉式:
class Singleton {
	// 1.既然是单例,在别的类中不能创建对象,所以
	private Singleton() {}
	private static Singleton s = new Singleton();
	public static Singleton getInstance() {
		return s;
	}
}

class Singleton {
	// 1.既然是单例,在别的类中不能创建对象,所以
	private Singleton() {}
	public static final Singleton s = new Singleton();
}
* 懒汉式:(多线程环境下可能会创建多个对象。解决:双重判断+同步代码块)
class Singleton {
	private Singleton() {}
	private static Singleton s;
	
	/*
	public static Singleton getInstance() {
		if(s == null) {
			s = new Singleton();
		}
		return s;
	}
	*/
	public static Singleton getInstance() {
		if(s == null) { // 提高效率
			synchronized(Singleton.class) {
				if(s == null) { // 防止重复创建对象
					s = new Singleton();
				}						
			}					
		}
		return s;
	}
}
  • 线程间的通信:多个线程对共享数据进行不同的操作;

实战java高并发程序设计笔记

Question:

  1. 线程等待,阻塞,挂起之间的区别?它们会释放资源和锁吗?
  1. 挂起是线程主动进入的,因此它也可以自主的恢复运行状态
  2. 阻塞是线程被动进入的,什么时候结束阻塞状态它自身是无法控制的
  1. 多线程之间如何通信?线程间的数据如何传递?

    1. 可以用流传递数据(管道流,pipestream)
    2. blockingqueue
  2. 多线程控制同步的方式有哪些?

    1. synchronized 关键字
    2. 重入锁
    1. 为什么叫重入锁?一个线程可以多次获得同一把锁,但同时在离开的时候也必须释放相同的次数.
    2. 为什么要使用重入锁? 因为重入锁的逻辑控制的灵活性要远远好于synchronizec关键字.
    3. 可以中断等待状态,对处理死锁有一定的帮助.
    4. 锁的申请限时等待.
  3. 什么是原子类?
    Java原子类实现原理分析

java多线程什么时候释放锁—wait()、notify()

由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁
1. 执行完同步代码块。
2. 在执行同步代码块的过程中,遇到异常而导致线程终止。
3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。

除了以上情况外,只要持有锁的线程还没有执行完同步代码块,就不会释放锁。因此在以下情况下,线程不会释放锁
1. 在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。
2. 在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。
3. 在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃。

避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B和C时,保证使每个线程都按照同样的顺序去访问他们,比如都先访问A,再访问B和C。

java.lang.Object类中提供了两个用于线程通信的方法:wait()和notify()。需要注意到是,wait()方法必须放在一个循环中,因为在多线程环境中,共享对象的状态随时可能改变。当一个在对象等待池中的线程被唤醒后,并不一定立即恢复运行,等到这个线程获得了锁及CPU才能继续运行,又可能此时对象的状态已经发生了变化。

调用obj的wait(),notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内。

调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。
  当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
  
   如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
  
   obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
  
   当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。

wait()/sleep()的区别

前面讲了wait/notify机制,Thread还有一个sleep()静态方法,它也能使线程暂停一段时间。sleep与wait的不同点是:sleep并不释放锁,并且sleep的暂停和wait暂停是不一样的。obj.wait会使线程进入obj对象的等待集合中并等待唤醒。
  
  但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
  
  如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
  
  需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值