一、多线程实现方式二:实现关系(Runnable)
1.步骤:
1)自定义一个类,实现Runnable接口,重写run方法
2)在用户线程(main)创建当前"资源类"对象
然后创建Thread类对象,将"资源类"对象作为参数传递
public Thread(Runnable target,String name)
3)分别启动线程
//Thread类的静态功能
public static Thread currentThread():表示正在执行的线程对象的引用
举例:
public class MyRunnable implements Runnable{
//t1,t2,t3
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建资源类对象
MyRunnable my = new MyRunnable() ;//共享资源
//创建线程类对象
Thread t1 = new Thread(my,"t1") ;
Thread t2 = new Thread(my,"t2") ;
Thread t3 = new Thread(my,"t3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
多线程实现方式1: 继承关系
SellTicket st1 = new SellTicket() ;
SellTicket st2 = new SellTicket() ;
SellTicket st3 = new SellTicket() ;
//设置线程名称
st1.setName("窗口1") ;
st2.setName("窗口2") ;
st3.setName("窗口3") ;
//启动线程
st1.start() ;
st2.start() ;
st3.start();
三个栈指向三个堆:分别在出售100张票 “没有数据共享”
继承关系具有局限性—>重写run方法----->Thread类的run---->通过实现Runnable接口的run 方法
不仅仅继承run方法,还将其他无关方法继承过来,不利于功能的扩展! (体现不出来:面向接口编程)
多线程实现方式2: (推荐)
“资源共享”
每一个线程都在使用同一个对象 st(SellTicket对象的引用) 体现出 “面向接口编程”
静态代理 模式
最大特点:真实角色和代理类都必须实现同一个接口!
代理类 对真实角色的功能进行方法增强!
代理类
真实角色
SellTicket 实现Runnable接口 完成run方法重写
Thread类 本身实现Runnable接口 完成run方法重写
SellTicket st = new SellTicket() ;
//创建多个线程类对象,将资源共享类作为参数传递
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
二、案例:电影卖票
/* 电影院有三个窗口,总共100张票,使用多线程模拟!
*
* 实现方式1:
* 使用继承关系
*
* 本身描述的就是三个窗口都在卖自己的100张(弊端:体现不出来的资源共享的概念)
* /
public class SellTicket extends Thread{
//有100张票
public static int tickets = 100 ;
//创建一个把锁
private Object obj = new Object() ;
@Override
public void run() {
//模拟一直有票
while(true){
synchronized (obj){
//加入网络延迟,睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>0){
System.out.println(getName()+"正在出售第"+tickets+"张票");
tickets -- ;
}else{
break ;
}
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//创建三个线程
SellTicket st1 = new SellTicket() ;
SellTicket st2 = new SellTicket() ;
SellTicket st3 = new SellTicket() ;
//设置线程名称
st1.setName("窗口1") ;
st2.setName("窗口2") ;
st3.setName("窗口3") ;
//启动线程
st1.start() ;
st2.start() ;
st3.start();
}
}
/* 电影院有三个窗口,总共100张票,使用多线程模拟!
* 实现方式2
* 使用Runnable接口的方式
* 第二种方式能够体现 "资源共享"
* 程序出现:
* 1)一个张票可能被卖多次(出现同票)
* 线程的执行具有随机性(原子性操作:最简单,最基本的操作语句:--,++...)
* 2)出现了负票
* 线程的延迟性导致(加入睡眠100毫秒)
* 现在的程序存在 "多线程安全问题"
*/
public class SellTicket implements Runnable {
public static int tickets = 100 ;//100张票
//t1,t2,t3
@Override
public void run() {
//为了模拟一直有票
while(true){
if(tickets>0){ //t1进来
//睡眠100毫秒
//为了模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在第"+(tickets--)+"张票");
}
//线程的执行:具有"原子性操作": tickets-- (线程的随机性导致的)
//t1先进来, 记录以前的值:100
//在t1记录100数据的时候,准备--的时候,t2进来,可能将t1之前记录的100值输出,100 出现同票
//第二种情况:出现了负票
//当前t1线程---第2张票
//睡眠100毫秒
//t3线程抢占到--->(将t1线程的2记录---完成--)第1张票
//t1,t2,t3 ---->t2先抢占到 ---->记录之前1,--完成后,变成0张票
//当前内存中记录值还没输出0的时候,t1抢占到之后,记录t2记录0张票, t1的票数--, 出现-1
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//创建资源共享类对象
SellTicket st = new SellTicket() ;
//创建多个线程类对象,将资源共享类作为参数传递
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
三、多线程安全问题
现在的程序虽然能够体现"资源共享",程序出负票和同票,如何解决安全问题?
-
校验多线程安全问题的标准是什么?
1)查看当前程序是否是多线程环境 是
2)是否存在共享数据 是 tickets
3)是否有多条语句对共享数据进行操作 存在1),2)都必须具备:现在使用的就是多线程环境,而且存在共享数据
从3)入手
Java提供一个同步机制:将多条语句对共享数据操作的使用同步代码块包括起来
格式:
synchronized(锁对象){ //多个线程必须使用的同一把锁 (可以任意的Java类对象)
将多条语句对共享数据操作
}
四、同步锁
synchronized:关键字---- 内置语言实现的(jvm来实现锁定操作: 锁的释放:自动释放)
可以是通过代码块(代码中),可以在方法上使用(同步方法)
synchronized同步锁—属于"悲观锁"
悲观锁:自己线程本身在执行的时候,其他线程不能进入同步代码块中,其他线程不能进入
修改数据,保证数据的安全性! (针对频繁的写入操作)
public class SellTicket implements Runnable {
public static int tickets = 100 ;//100张票
private Demo demo = new Demo();
// private Object obj = new Object() ;
//t1,t2,t3
@Override
public void run() {
//为了模拟一直有票
while(true){
//t1,t2,t3
//加入同步代码块
//把它理解为:门的开/关
// synchronized(new Object()){ //三个线程有自己的锁对象
//t1进来,t2和t3没有办法进入的
// synchronized(obj){ //一个锁对象
//锁对象可以是任意的Java类对象
synchronized(demo){ //一个锁对象
if(tickets>0){ //t1进来
//睡眠100毫秒
//为了模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"正在第"+(tickets--)+"张票");
}
}
}
}
}
//自定义的类
class Demo{
}
public class SellTicketDemo {
public static void main(String[] args) {
//创建资源共享类对象
SellTicket st = new SellTicket() ;
//创建多个线程类对象,将资源共享类作为参数传递
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
-
代理:
让别人替自己本身完成一些事情!举例: 过年回家买车票,让别人帮忙代买! 代理: 代理角色:帮助真实角色对他本身的功能进行增强(完成代理完成不了的事情!) 真实角色:只只专注于自己完成的事情 举例: 结婚这件事情 真实角色:you :你 代理角色:weddingCompany:婚庆公司
Java代理模式----结构型设计模式
静态代理 同一个接口 特点:代理角色和真实角色必须同一个接口 动态代理(后期框架底层使用的这个模式) JDK动态代理:基于接口 CGLib动态代理:基于子类 多线程的实现方式2: MyRunnable myrunnable = new MyRunnable() ; //MyRunnable implements Runnable接口 //线程类 Thread t1 = new Thread(myrunnable,"t1") ; //Thread类本身实现 Runnable接口 Thread t2 = new Thread(myrunnable,"t2") ; Thread t3 = new Thread(myrunnable,"t3") ;
举例:
public class StaticProxyDemo {
public static void main(String[] args) {
//接口多态
//真实角色
Marry marry = new You() ;
marry.marry() ;
System.out.println("-----------------------");
//加入婚庆代理角色
You you = new You() ;
//创建婚庆公司
WeddingCompany wdc = new WeddingCompany(you) ;
wdc.marry();
}
}
//定义接口:Marry
interface Marry{
public abstract void marry() ;
}
//定义一个子实现类:真实角色
class You implements Marry{
@Override
public void marry() {
System.out.println("我很高兴,我要结婚了...");
}
}
//代理角色weddingCompany:婚庆公司 要实现Marry接口
class WeddingCompany implements Marry{
private You you ;
public WeddingCompany(You you){
this.you = you ;
}
@Override
public void marry() {
System.out.println("结婚之前,婚庆公司布置婚礼现场...");
you.marry();
System.out.println("结婚完毕,很高兴,给婚庆付尾款...");
}
}
举例:
public class SellTicket implements Runnable {
//100张票
public static int tickets = 100 ;
// public Object obj = new Object() ;
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//模拟一直有票
while(true){
if(x % 2 == 0){
// synchronized (this){
synchronized (SellTicket.class){
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}else{
//x%2!=0
sellTicket();
}
x ++ ;
}
}
//如果一个方法进来就是一个同步代码块---->
// 可以将synchronized抽取到方法声明上:同步方法 (非静态)
//同步方法:它的锁对象:this 当前类对象的地址值引用
/* private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}*/
静态的同步方法 ---静态的东西都和类直接相关 :锁对象---就是 (反射相关)类名.class--->class 包名.类名{} 字节码文件对象
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//Class sellTicketClass = SellTicket.class;
// System.out.println(sellTicketClass);//class com.qf.thread_06.SellTicket :字节码文件对象
//创建资源共享类对象
SellTicket st = new SellTicket() ;
//创建多个线程类对象,将资源共享类作为参数传递
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
JDK5以后Java提供了比syncrhonized(jvm来操作:自动释放锁)更广泛的锁定操作,
程序员可以在某个位置自己去加锁,(弊端):手动释放锁
java.util.concurrent.locks.Lock 接口 不能实例化
提供成员方法
void lock() :获取锁
void unlock():手动试图释放锁
ReentrantLock :子实现类 跟synchronized用于法相似
可重入的互斥锁!
synchronized: 关键字---> 内置语言实现---通过jvm调用的
由jvm自动释放锁
它不仅仅可以应用在方法体中(同步代码块),也可以引用在方法上(同步方法)
Lock :是一个接口
手动加锁和手动释放锁
一般都是在方法中的语句中使用
举例:
public class SellTicket implements Runnable {
//100张票
public static int tickets = 100 ;
//声明一个锁:Lock
private Lock lock = new ReentrantLock() ;
@Override
public void run() {
//模拟一直有票
while(true){
//获取一个锁:某个线程执行到这块:必须持有锁
lock.lock();
//执行完毕,手动释放锁
//在开发中 :try...catch...finally :捕获异常 ,不会使用throws
//变形格式:try...catch...catch...
//try...finally...
try{
if(tickets >0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}finally {
//释放资源代码块
//释放锁
lock.unlock();
}
}
}
}
public class LockDemo {
public static void main(String[] args) {
//创建资源类对象
SellTicket st = new SellTicket() ;
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
五、死锁——等待唤醒机制
虽然syncrhonized可以解决线程安全问题:同步代码块/同步方法,但是执行效率低,可能出现死锁
两个线程或者多个线程出现互相等待的情况!
解决死锁问题方案:"生产者消费者思想" 必须保证多个多线程必须使用的同一个资源对象!
举例:
public class MyDieLock {
//创建两把锁对象 (静态实例变量)
public static final Object objA = new Object() ;
public static final Object objB = new Object() ;
}
public class DieLock implements Runnable {
//提供一个boolean类型的变量
private boolean flag ;
public DieLock(boolean flag){
this.flag = flag ;
}
@Override
public void run() {
if(flag){
synchronized (MyDieLock.objA){
System.out.println("if objA");
synchronized (MyDieLock.objB){
System.out.println("if objB");
}
}
}else{
synchronized (MyDieLock.objB){
System.out.println("else objB");
synchronized (MyDieLock.objA){
System.out.println("else objA");
}
}
}
}
}
public class DieLockDemo {
public static void main(String[] args) {
//创建资源类对象
DieLock dl1 = new DieLock(true) ;
DieLock dl2 = new DieLock(false) ;
//创建线程类对象
Thread t1 = new Thread(dl1) ;
Thread t2 = new Thread(dl2) ;
//启动线程
t1.start();
t2.start();
/**
* if(flag){
* synchronized (MyDieLock.objA){
* System.out.println("if objA");
* synchronized (MyDieLock.objB){
* System.out.println("if objB"); //需要等待第二个线程 objB释放锁掉
* }
* }
* }else{
* synchronized (MyDieLock.objB){
* System.out.println("else objB");
* synchronized (MyDieLock.objA){
* System.out.println("else objA");//需要等待第一个线程 将objA锁释放掉
* }
* }
* }
*
*
* 情况1:
* if objA
* else objB
*
* 当第一个ObjA锁被使用,输出"if objA"
* 同时第二个线程dl2: ObjB锁被使用 "else objB"
*
*
* 情况2:
* else objB
* if objA
*
*
* 情况3:理想情况:
* if objA
* if objB
* else objB
* else objA
*/
}
}
等待唤醒机制:
/* 包子数据
*/
public class Student {
String name ;
int age ;
//是否存在学生数据标记
boolean flag ; //true有数据,false没有数据
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/* 生产者资源类
*/
public class SetThread implements Runnable {
private Student s ;
public SetThread(Student s){
this.s = s ;
}
//统计变量
int x = 0 ;
@Override
public void run() {
//不断产生数据
while(true){
synchronized (s){
//判断如果当生产者有数据
if(s.flag){
try {
//生产者有数据,需要等待消费者线程使用
s.wait(); //锁对象在调用wait()
//wait():当前锁对象该调用方法的时候,会立即是否锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//当前在执行下面代码的之前:线程的执行资格被GetThread所在的消费者线程
if(x % 2 == 0){
s.name = "高圆圆" ;
s.age = 41 ;
}else{
s.name = "杨德财" ;
s.age = 27 ;
}
x ++ ;
//更改标记
s.flag = true ;
//通知对方线程:赶紧使用数据
s.notify(); //锁对象调用的唤醒方法
}
}
// Student s = new Student();
}
}
/* 消费者资源类
*/
public class GetThread implements Runnable {
private Student s ;
public GetThread(Student s){
this.s = s ;
}
@Override
public void run() {
//模拟一直消费数据
while(true){
synchronized (s){
if(!s.flag){
//如果消费者没有数据了,需要等待生产线程产生数据
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//更改标记
s.flag = false ;//没有数据了
//通知对方线程:生产线程 :产生数据
s.notify() ;
}
}
// Student s = new Student() ;
}
}
/* 测试类
* SetThread:生产数据
* GetThread:消费数据
* Student:学生数据
*
* 问题1 :出现数据 null--0 :
*
* 在生产资源类和消费者资源类中:分别new Student() :不是同一个对象
*
* 模拟一直有数据:加入循环操作while(true)
*
* 问题2:姓名和年龄不符
* 线程的执行具有随机性导致,多线程程序不安全
*
* 校验多线程安全问题的标准是什么?
* 1)查看是否是多线程环境
* 2)是否存在共享数据
* 3)是否有多条语句对共享数据进行操作
*
* 3)改进:使用同步代码块包括起来!
*
* 问题3:学生数据输出的时候,一次性打印很多
* 线程的一点点时间片:足够当前线程执行很多次!
*
* 优化:
* 不需要输出学生数据的时候, 一次性输出很多,而是分别依次输出
* "高圆圆" 41
* "杨德财" 27
* ...
* ..
*/
public class ThreadDemo {
public static void main(String[] args) {
Student s = new Student() ; //同一个资源类对象
//创建生产资源类对象
SetThread st = new SetThread(s) ;
//创建消费者资源类对象
GetThread gt = new GetThread(s) ;
// 创建线程类对象
Thread sThread = new Thread(st) ;
Thread gThread = new Thread(gt) ;
//启动
sThread.start();
gThread.start();
}
}