黑马程序员——多线程——多线程与单例设计模式

本文介绍了Java中的多线程概念,重点讲解了JDK5引入的Lock锁,强调了其相比synchronized的灵活性。同时,列举了实现线程的三种方式,并提到了定时器Timer类的使用方法。在单例设计模式部分,解释了其核心思想是确保类只有一个实例,并提供了创建和访问该实例的全局访问点,讨论了懒汉式的实现及其线程安全问题。
摘要由CSDN通过智能技术生成

------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类本身也是单例模式






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值