------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一.多线程
1.JDK5的新特性:Lock锁
Lock同样可以完成代码同步的任务,它把什么时候获取锁,什么时候释放锁的时间给明确了
相较于synchronized方式,Lock锁的出现使同步操作更为灵活。无需使用限制性强的代码块。
Lock同样为抽象类,需要使用其子类ReentrantLock的对象完成方法调用。
主要方法:
public void lock()获取锁
public void unlock() 释放锁
案例:
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
public class DemoLock {
/**
* 完成多线程卖票动作,使用lock锁实现卖票流程的同步
*/
public static void main(String[] args) {
//创建线程目标类对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread thread = new Thread(mr, "窗口1号");
Thread thread2 = new Thread(mr, "窗口2号");
Thread thread3 = new Thread(mr, "窗口3号");
//执行线程
thread.start();
thread2.start();
thread3.start();
}
}
</strong></span>
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//卖票的线程执行目标类使用Lock锁完成同步
public class MyRunnable implements Runnable {
//定义总票数,供线程共享
int number = 200;
// 创建Lock锁对象供线程共享
Lock lock = new ReentrantLock();
// 重写run方法
public void run() {
while (true) {
//使用Lock锁使线程同步
lock.lock();
try {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
//如果票数是正的,就有票,就卖
if (number > 0) {
System.out.println(Thread.currentThread().getName()
+ "正在销售第 " + number + "号票");
//卖完一张,少一张
number--;
}
} finally {
//使用finally语句是锁打开
lock.unlock();
}
}
}
}
</strong></span>
2.死锁
死锁:在同步中,多个线程使用多把锁之间由于考虑得不够周全,存在等待的现象
死锁案例:
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//定义包含死锁的线程类
public class DeadLock extends Thread {
//定义标记,指定要执行的代码
boolean flag;
public DeadLock(boolean flag) {
super();
this.flag = flag;
}
public void run() {
//如果flag是true,执行if下的语句
if (flag) {
synchronized (TestDeadLock.LOCK1) {
System.out.println("if中锁1");
synchronized (TestDeadLock.LOCK2) {
System.out.println("if中锁2");
}
}
//如果flag是false执行else中的语句
} else {
synchronized (TestDeadLock.LOCK2) {
System.out.println("else中锁2");
synchronized (TestDeadLock.LOCK1) {
System.out.println("else中锁1");
}
}
}
}
}
</strong></span>
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
public class TestDeadLock {
// 创建锁对象1
public static final Object LOCK1 = new Object();
// 创建锁对象2
public static final Object LOCK2 = new Object();
public static void main(String[] args) {
// 创建线程对象
DeadLock dl = new DeadLock(true);
DeadLock dl2 = new DeadLock(false);
// 执行线程
dl.start();
dl2.start();
}
}
</strong></span>
原因分析:
线程1将锁1锁住,线程2将锁2锁住,而线程1要继续执行锁2中的代码,线程2要继续执行锁1中的代码
但是此时,两个锁均处于锁死状态。最终导致两线程相互等待,进入无限等待状态。
解决方法:
不要使用同步代码块嵌套
3.等待唤醒机制
当出现对同一资源的生产与消费时,可以使用多线程完成对同一资源的操作。而消费者需要等待
生产者生产后才能消费,生产者也需要等待消费者消费后才能生产。于是出现了生产者消费者问题。
这时可以使用等待唤醒机制完成相关需求。
方法:
不是Thread类的方法,而是Object类的两个方法:因为锁可以为共享数据本身可以是任意的对象,
在runnable中进行等待唤醒当前所在线程。
等待:
public final void wait() throws InterruptedException
让当前线程进入等待状态,如果线程进入该状态,不唤醒或打断,不会解除等待状态。
进入等待状态时会释放锁。
唤醒:
public final void notify()
唤醒正在等待的线程,继续等待之后的代码执行。
sleep与wait的区别:
sleep指定时间,wait可指定可不指定。
sleep释放执行权,不释放锁。因为一定可以醒来。
wait释放执行权与锁。
等待唤醒机制卖票案例:
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//定义票类
public class Tickets {
private int tickets;
//定义Boolean,作为判断
boolean b ;
public Tickets() {
super();
}
public Tickets(int tickets) {
super();
this.tickets = tickets;
}
public int getTickets() {
return tickets;
}
public void setTickets(int tickets) {
this.tickets = tickets;
}
}
</strong></span>
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//定义生产票的线程目标类
public class TicketsRunnable implements Runnable {
//定义共享资源票为成员
Tickets tickets;
//定义含有共享资源的参数的构造函数
public TicketsRunnable(Tickets tickets) {
super();
this.tickets = tickets;
}
// //重写run方法,加入产票逻辑
public void run() {
while (true) {
//同步锁对象为共享资源对象
synchronized (tickets) {
//定义唤醒机制
if (tickets.b) {
tickets.setTickets(200);
System.out.println("生产了" + tickets.getTickets() + "张票");
} else {
try {
//等待机制
tickets.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产好票后改变Boolean值
tickets.b = false;
//唤醒机制
tickets.notify();
}
}
}
}
</strong></span>
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//定义卖票的线程目标类
public class SellTicketsRunnable implements Runnable {
//定义共享资源票为成员
Tickets tickets;
//定义含有共享资源的参数的构造函数
public SellTicketsRunnable(Tickets tickets) {
super();
this.tickets = tickets;
}
//重写run方法,加入卖票逻辑
public void run() {
while (true) {
//同步锁对象为共享资源对象
synchronized (tickets) {
//定义等待唤醒机制
if (!tickets.b) {
for (int i = tickets.getTickets(); i > 0; i--) {
System.out.println(Thread.currentThread().getName()
+ "正在销售第 " + i + "号票");
}
} else {
try {
//等待机制
tickets.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//卖完票后改变Boolean值
tickets.b = true;
//唤醒机制
tickets.notify();
}
}
}
}
</strong></span>
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//卖票测试类
public class TestTickets {
public static void main(String[] args) {
// 创建票类对象
Tickets tickets = new Tickets(100);
// 创建线程产票目标类
TicketsRunnable tr = new TicketsRunnable(tickets);
// 创建线程卖票目标类
SellTicketsRunnable str = new SellTicketsRunnable(tickets);
// 创建线程对象
Thread trThread = new Thread(tr, "产票中心");
Thread strThread = new Thread(str, "窗口1号");
// 线程执行
trThread.start();
strThread.start();
}
}
</strong></span>
4.线程组
多个线程出现时,可以将线程分组,统一处理。
线程组类:ThreadGroup
主要方法:
返回线程组的名称:
public final String getName()
更改线程组的后台程序状态:
public final void setDaemon(boolean daemon)
注意:
a:线程组的方法是对整个线程组进行操作
b:线程组没有线程添加方法,设置线程组在Thread构造方法中进行.
c:线程组可以包含线程或者线程组。
d:当前线程只能访问所在线程组或者子组。不能访问父组或者兄弟组。
e:如果没有指定线程组,则属于main线程组。
5.线程池
A:线程组概述:
将线程放置到同一个线程池中,其中的线程可以反复使用,无需反复创建线程而消耗过多资源。
线程池的出现降低了资源消耗。线程池可以控制线程的某些行为,如销毁线程。
B:线程池创建工厂类:Executors
返回线程池方法:
public static ExecutorService newXXXThreadPool(int n)
C:线程池类:ExecutorService
主要方法:
提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future:
Future<?> submit(Runnable task)
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future:
<T> Future<T> submit(Callable<T> task)
启动一次顺序关闭,执行以前提交的任务,但不接受新任务:
void shutdown()
注意:线程池提交方法后,程序并不终止,是因为线程池控制了线程的关闭
Callable:相当于有返回值类型的runnable。
Future是将结果抽象的接口。可以将线程执行方法的返回值返回。
三种实现线程的方式:
1.继承Thread
2.实现Runnable接口
3.实现Callable接口
注意:Callable与Runnable接口实现方式一样,区别就是Callable重写的是call()方法,并且有返回值.
6.定时器
Timer类:定时器类
主要方法:
安排指定时间完成指定任务:
public void schedule(TimerTask task, Date time)
安排任务重复执行:
public void schedule(TimerTask task, Date firstTime, long period)
终止此计时器,丢弃所有任务:
public boolean cancel()
取消此定时器任务:
public boolean cancel()
注意事项:a:TimerTask:由 Timer 安排为一次执行或重复执行的任务。
b:如果指定了任务,但是在任务还没开始执行时就取消任务时,任务的代码将一次都不执行。
c:定时器不可以重复schedule任务。
二.单例设计模式
1.保证类在内存中只有一个对象。
2.保证只有一个对象的思路:
A:构造私有
B:自己造一个对象
C:提供公共访问方式
3.两种方式:
A:懒汉式: 需要考虑线程安全问题
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//创建人类的单例模式之懒汉式(延时加载):
public class Person {
//私有构造方法,使外界不能直接通过构造方法创建对象
private Person(){}
//将成员Person私有并静态,使外界不能直接访问,本类中可以访问
private static Person p;
//定义公共静态的方法向外界提供单例对象,方法返回值为单例对象的数据类型
//方式一:不考虑安全不考虑高效的方式
public static Person getInstance1(){
//延时创建对象,使用在创建
if(p==null){
p= new Person();
}
return p;
}
//方式二:考虑安全不考虑高效的方式:
//给方法中加入同步代码块
public static Person getInstance2(){
//延时创建对象,使用在创建
synchronized(Person.class){
if(p==null){
p= new Person();
}
}
return p;
}
//方式三:考虑安全不考虑高效的方式:
//将方法进行同步
public static synchronized Person getInstance3(){
//延时创建对象,使用在创建
if(p==null){
p= new Person();
}
return p;
}
//方式四:考虑安全也考虑高效的方式:
//在方法中再加一次空的判断
public static Person getInstance4(){
//
if(p==null){
synchronized (Person.class){
//延时创建对象,使用在创建
if (p==null) {
p= new Person();
}
}
}
return p;
}
}
</strong></span>
B:饿汉式: 开发经常使用
<span style="font-family:FangSong_GB2312;font-size:18px;"><strong>package cn.blog;
//创建动物类的单例模式之饿汉式
public class Animal {
//定义私有的构造方法,外界不能直接创建对象
private Animal(){}
//定义私有的静态的成员动物对象,使其在自己的类中可以被访问
private static Animal a= new Animal();
//定义公共的静态的成员方法,给外界提供一个单例对象,返回值为对象自己的数据类型
public static Animal getInstance(){
return a;
}
}
</strong></span>
4.JDK的Runtime类本身也是单例模式