线程一些小知识的整理。
一:线程的同步
需求:写一个多线程程序实现铁路售票系统,最少4个售票点,注意线程同步!
技能: 引入线程同步---同步方法和同步代码块---synchronized
1.多线程的特点
1.1 并发性---相当于同时运行
1.2 随机性---运行那个线程不确定,谁先抢到CPU资源,谁先运行
2.怎么消除线程的随机性----可以使用线程同步的方式。
2.1 同步方法---也就是在方法上使用synchronized关键字进行修饰即可。
格式: 权限修饰符 synchronized 返回值类型 方法名称(参数列表){
方法体;
[return 返回值;]
}
分析: 如果原来的普通方法,使用synchronized关键字修饰,变成同步方法,里面暗含一个同步锁---this(当前类对象)
2.2 同步代码块---也就是使用synchronized关键字进行修饰的普通代码块
格式: synchronized(同步锁){
要同步的代码;
}
分析: 关于同步代码块当中的同步锁有三种
1.使用this----当前类对象可以当作是同步锁
2.使用同步代码所在的类的字节码文件对象,作为同步锁----TrainTicketSellSystem.class
3.使用共享资源来充当锁 ---- 例如: 火车票可以看成是共享资源 ---- 单独创建一个类,类中的属性是火车票
3.编写的步骤
3.1 创建一个类,继承Thread类,目的:创建对象---也就是创建线程对象
3.2 创建类中的属性----火车票 int ticket =100;
3.3 重写run()方法,因为run()方法时线程的主体
3.4 创建四个线程对象,调用start方法
发现: 每一个窗口,都卖了200张票,一共卖了1000张票
发现: Thread-0 Thread-1这些是什么? 这些是线程的名称,如果没有对线程进行命名的化,JVM会自动为线程名称,命名的方式
Thread-x x是从0开始的
代码区:
public class TrainTicketSellSystem extends Thread{
//属性----成员变量 火车票
int ticket = 200;
//重写run()方法
public void run(){
//run()方法里面,应该放的是线程的主体代码----售票
//在售票之前,需要进行判断,如果大于零,说明有票可卖
boolean flag = true;
while(flag){
if(ticket >0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖出第"+(200-ticket)+"张票");
}else{
System.out.println("火车票票数不足,请购买下一趟列车");
flag = false;
}
}
}
public static void main(String[] args) {
//创建线程对象,因为本类继承了Thread类,所以创建本类对象,也就是创建了线程对象
TrainTicketSellSystem t1 = new TrainTicketSellSystem();
TrainTicketSellSystem t2 = new TrainTicketSellSystem();
TrainTicketSellSystem t3 = new TrainTicketSellSystem();
TrainTicketSellSystem t4 = new TrainTicketSellSystem();
TrainTicketSellSystem t5 = new TrainTicketSellSystem();
t1.setName("窗口A");
t2.setName("窗口B");
t3.setName("窗口C");
t4.setName("窗口D");
t5.setName("窗口E");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
二:同步方法
需求:写一个多线程程序实现铁路售票系统,最少4个售票点,注意线程同步!
技能: 引入线程同步---同步方法
发现: 创建线程的方式之一,继承Thread,不能共享资源。
分析: 属性----如果没有使用static修饰,则它从属于对象,只有对象开辟空间之后,属性才有具体的值
例如: 创建了5个线程对象,每创建一个对象,都会在堆内存当中,为ticket开辟空间,初始化值都是200
解决:第一种方法,是把成员变量变为静态变成员量(不推荐)
第二种方法,把实现线程的方式,改为实现Runnable接口的形式,而不是继承Thread类(推荐它)
发现: 不要把任何的代码都放到run()方法里面去,原因: 代码显得臃肿
解决: 在单独创建一个方法,把核心代码放入到此方法中即可。
发现: 在创建Runnable对象时,只需要创建一个即可,不要再创建5个啦
原因: 如果创建了5个Runnable对象,和继承Thread类,启动线程,没有任何区别了!
重点:实现Runnable接口,可以实现资源共享
分析:面向对象多态机制
包括: 对象的多态---- 向上转型
父类类型 父类引用 = new 子类类型(); ---发现父类的本质,其实就是一个子类对象!
Runnable r = new TrainTicketSellSystem();
因为子类存在一个属性---火车票 200,如果创建了五个Runnable 接口对象,也就是创建了5个TrainTicketSellSystem子类对象
所以:要创建一个Runnable接口对象,目的: 实现资源共享
发现: 解决了卖了1000张票的问题,但是出现了200张票,都是由同一个窗口售卖,其他窗口没有!
原因:当某一个线程执行start()方法,会默认的调用run()方法,因为run()里面有一个同步方法---sell()
当某一个线程执行此同步方法时,其他的线程必须等待此线程执行结束,才有可能进行执行sell();
但是当其他线程执行sell方法时,火车票数已经 不满足需求,直接执行break,跳出循环!
解决:方案1: 使用sleep()方法,但是发现,在线程同步的情况在,是不会释放锁,同时其他先也进不来!---不推荐
方案2: 重点: 释放锁,让其他先也能参与!
释放锁---线程自动释放----只有当前线程执行完毕代码之后,会自动释放锁
步骤:
1.同步方法内的while()循环移出 ,目的: 不让同一个线程把所有的票给卖光
2.在run方法内,写一个while()循环,循环内部写入sell方法,同时要加以判断
判断票数是否<=0,如果不满足,说明200张票还没有买完,反之,200张票卖光了
代码区:
public class TrainTicketSellSystem implements Runnable{
//属性----成员变量 火车票
int ticket = 200;
//重写run()方法
public void run(){
//run()方法里面,应该放的是线程的主体代码----售票
while(true){
sell();
//把死循环,变为有限循环
if(ticket<=0){ //如果满足,说明,没有票可卖了
break;
}
}
}
//添加同步关键字---synchronized,使用普通变为同步方法---暗含的同步锁,是当前类对象,也就是说是this
public synchronized void sell(){
//在售票之前,需要进行判断,如果大于零,说明有票可卖
if(ticket >0){
System.out.println(Thread.currentThread().getName()+"卖出第"+(201-ticket--)+"张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//创建主方法,目的: 创建线程对象
public static void main(String[] args) {
//1.创建Runnable接口对象
Runnable r = new TrainTicketSellSystem();
//2.因为Runnable接口对象,没有start方法,必须借助Thread类
Thread t1 = new Thread(r,"窗口A");
Thread t2 = new Thread(r,"窗口B");
Thread t3 = new Thread(r,"窗口C");
Thread t4 = new Thread(r,"窗口D");
Thread t5 = new Thread(r,"窗口E");
//3.启动线程---JVM会默认的调用run()方法---调用sell()方法
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
三:同步代码块--this(同步锁)
需求:写一个多线程程序实现铁路售票系统,最少4个售票点,注意线程同步!
技能: 引入线程同步---同步代码块
1.同步代码块的格式
synchronized(同步锁){
要同步的代码;
}
2.关于同步代码块当中的同步锁!
强调:同步锁只能是对象的形式,不能是基本数据类型!
2.1 使用this----暗含的是当前类对象
即可用到同步方法,也可以用到同步代码块。
2.2 使用共享资源---推荐使用此锁
共享资源必须是以对象的形式存在,不能是基本数据类型!
如果在某个类当中,使用基本数据类型充当共享资源是,需要把基本数据类型---->类的形式
此处并不是使用包装类,只是创建一个类,把这个资源(基本数据类型)放入到此类中即可。
2.3 使用字节码文件对象
字节码---二进制代码---那个文件中,存在的是以二进制为内容的----.class文件
字节码文件对象,就是一个.class文件
在后续章节当中,会学习反射机制----反射的源头类---Class
发现: 每一个窗口各卖了一张票
发现: 窗口卖的票数,远远的超出预定的票数
原因: 因为在同步的同时,使用的是死循环
解决: 把死循环改成有限循环
发现: 在使用同步以及,循环语句时,窗口多卖了4张票
代码区:
public class TrainTicketSellSystem implements Runnable{
//属性---成员变量
int ticket = 200;
//重写run()方法
public void run() {
//while循环语句,目的: 让各个窗口,都在卖票
while(true){
//同步代码块锁,使用this充当同步锁
synchronized(this){
//加一个判断语句,如果票数小于等于0,说明票已经卖完
if(ticket <=0){
break;
}
//把要同步的代码,放入到此代码块中
System.out.println(Thread.currentThread().getName()+"出售第"+(201-ticket--)+"张票");
//sleep方法的作用:线程的切换
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class Test {
public static void main(String[] args) {
//1.创建Runnable接口对象
Runnable r = new TrainTicketSellSystem();
//2.创建Thread类对象
Thread t1 = new Thread(r,"窗口A");
Thread t2 = new Thread(r,"窗口B");
Thread t3 = new Thread(r,"窗口C");
Thread t4 = new Thread(r,"窗口D");
Thread t5 = new Thread(r,"窗口E");
//3.启动线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
四:同步代码块--共享资源(同步锁)
public class Ticket {
private int ticketNum =200;
public int getTicketNum() {
return ticketNum;
}
public void setTicketNum(int ticketNum) {
this.ticketNum = ticketNum;
}
}
public class Test2 {
public static void main(String[] args) {
//1.创建Runnable接口对象
Runnable r = new TrainTicketSellSystem2();
//2.创建Thread类对象
Thread t1 = new Thread(r,"窗口A");
Thread t2 = new Thread(r,"窗口B");
Thread t3 = new Thread(r,"窗口C");
Thread t4 = new Thread(r,"窗口D");
Thread t5 = new Thread(r,"窗口E");
//3.启动线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
public class TrainTicketSellSystem2 implements Runnable{
//使用共享资源来充当同步锁
private Ticket t = new Ticket();
//重写run()方法
public void run() {
//while循环语句,目的: 让各个窗口,都在卖票
while(true){
//同步代码块锁,使用this充当同步锁
synchronized(t){
//加一个判断语句,如果票数小于等于0,说明票已经卖完
if(t.getTicketNum() <=0){
break;
}
//把要同步的代码,放入到此代码块中
System.out.println(Thread.currentThread().getName()+"出售第"+(201-t.getTicketNum())+"张票");
//在Ticket类中,有get set方法,set方法的目的就是设置火车票数,因为在while循环内部,每循环一次就减1
t.setTicketNum(t.getTicketNum()-1);
//sleep方法的作用:线程的切换
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
四:同步代码块--字节码对象(同步锁)
public class TrainTicketSellSystem3 implements Runnable{
//使用共享资源来充当同步锁
// private Ticket t = new Ticket();
private int ticket =200;
//重写run()方法
public void run() {
//while循环语句,目的: 让各个窗口,都在卖票
while(true){
//同步代码块锁,使用this充当同步锁
synchronized(TrainTicketSellSystem3.class){
//加一个判断语句,如果票数小于等于0,说明票已经卖完
if(ticket <=0){
break;
}
//把要同步的代码,放入到此代码块中
System.out.println(Thread.currentThread().getName()+"出售第"+(201-ticket--)+"张票");
//sleep方法的作用:线程的切换
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test3 {
public static void main(String[] args) {
//1.创建Runnable接口对象
Runnable r = new TrainTicketSellSystem3();
//2.创建Thread类对象
Thread t1 = new Thread(r,"窗口A");
Thread t2 = new Thread(r,"窗口B");
Thread t3 = new Thread(r,"窗口C");
Thread t4 = new Thread(r,"窗口D");
Thread t5 = new Thread(r,"窗口E");
//3.启动线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
五:账户取款
需求:多个用户同时操作一个银行账户。
每次取款400元,取款前先检查余额是否足够。
如果不够,放弃取款
技能: 线程同步
1.实现多线程的方式,有两种,一种是继承Thread类,一种是实现Runnable接口
作业: 两者之间的区别!最好使用代码体现之
大多数实现多线程,都使用实现Runnable接口的形式,原因: 实现资源共享;打破了类的单继承限制
步骤:
1.创建一个类,此类实现Runnable接口,并重写run()方法
2.同步的方式,使用同步代码块,既然使用同步代码块,需要有一个同步监视器---同步锁(共享资源)
3.创建一个类Account账户类,目的: 创建对象,充当同步锁; 取钱的方法和查询钱的方法,都存在于此类中。
4.完善同步代码块
在run()方法当中,编写同步代码块
使用同步锁是Acount类对象
在同步代码块中,应该先判断账户的钱和取钱的金额,是否相符
切换用户: sleep()方法
5.在测试类中,创建两个线程对象,定启动线程!
代码区:
/*
* Account类---账户类
* 作用: 1.充当同步监视器---同步锁
* 2.把取钱的方法以及查询前的方法,都定义在此类中!
* 因为: 在同步代码块当中,会调用这两个方法!
*/
public class Account {
//定义成员变量,目的: 显示银行卡的余额
int money = 600;
//定义两个方法,一个方法是取钱,一个是查询
public void withDraw(int money){
this.money=this.money-money;
}
public int getMoney(){
return money;
}
}
public class AccountRunnable implements Runnable{
//定义成员变量---目的: 同步锁
//作用域: 在整个类任何地方都有效
private Account accout = new Account();
//重写run()方法
public void run() {
//使用同步代码块
synchronized(accout){
while(true){
//先判断,银行卡余额是否满足,取款的要求
if(accout.getMoney() >= 400){
accout.withDraw(400);
//取款
System.out.println(Thread.currentThread().getName()+"取款成功,现在的余额为:"+accout.getMoney());
if(accout.getMoney() < 400){
break;
}
}else{
System.out.println(Thread.currentThread().getName()+"取款失败,余额不足,现在的余额为:"+accout.getMoney());
break;
}
}
}
}
}
public class Tset {
public static void main(String[] args) {
//1.创建Runnable接口对象
Runnable r = new AccountRunnable();
//2.根据Thread类构造方法,传入Runnable接口对象,并对线程命名
Thread t1 = new Thread(r,"高宇");
Thread t2 = new Thread(r,"王梦凡");
//3.启动线程
t1.start();
t2.start();
}
}
原博地址(也是我,O(∩_∩)O哈哈~):http://m15231417197.lofter.com/
练习代码地址(嘿嘿嘿):https://download.csdn.net/download/m15231417197/10716486