Day23 多线程1 线程基础和线程同步

多线程学习要点

这里写图片描述

线程基础

程序、进程与线程
程序Program

程序是一段静态的代码,它是应用程序执行的蓝本

进程Process

进程是指一种正在运行的程序,有自己的地址空间
每个进程都是独立的,由3部分组成cpu,data,code

进程的特点
  • 动态性
  • 并发性
  • 独立性

并发和并行的区别

  • 并行:多个CPU同时执行多个任务
  • 并发:一个CPU(采用时间片)同时执行多个任务

这里写图片描述

线程Thread
  • 进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。
  • 线程又被称为轻量级进程(lightweight process)
  • 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程
线程特点
  • 轻量级进程
  • 独立调度的基本单位
  • 可并发执行
  • 共享进程资源
线程和进程的区别

这里写图片描述
程序、进程与线程的生活案例:
班级:204
小组:1,2,3,4,5……
完成一件事情:大扫除
总负责:校长
步骤1:以班级为单位领取大扫除工具,本班级的所有小组都使用该班级领取的资源
步骤2:以小组为单位开始大扫除
步骤3:校长亲自监督,如果发现不合格,直接要求该小组重新打扫;如果某小组打扫完毕,校长可以直接给该小组安排其他任务
对比
CPU:校长
进程:班级 (一个班级可以有多个小组,班级是资源分配的单位)
线程:小组 (校长直接指挥小组进行工作)

线程的创建和启动

线程的创建
方式1:继承Java.lang.Thread类,并覆盖run() 方法
方式2:实现Java.lang.Runnable接口,并实现run() 方法
方式3:实现Callable接口,并实现call()方法
方法run( )称为线程体。

线程的启动
新建的线程不会自动开始运行,必须通过start( )方法启动
不能直接调用run()来启动线程,这样run()将作为一个普通方法立即执行,执行完毕前其他线程无法兵法执行
Java程序启动时,会立刻创建主线程,main就是在这个线程上运行。当不再产生新线程时,程序是单线程的

方式1:继承Java.lang.Thread类,并覆盖run() 方法

public class TortoiseThread extends Thread{ 
    @Override
    public void run() {
        while(true){
            System.out.println("乌龟领先了,加油....,当前线程的名称:"+this.getName()
                    +",当前线程的优先级别:"+this.getPriority());
        }
    }
}



/**
 * 功能:龟兔赛跑
 * 技能:线程的创建和启动
 * 
 * 方式1:继承Thread类
 * 
 * 总结1:如何创建线程类
 * public class TortoiseThread extends Thread{
 *  public void run(){
 * 
 *  }
 * }
 * run() 线程体  线程要完成的任务,要执行的操作
 * 
 * 总结2:如何启动线程
 *  Thread thread = new TortoiseThread();
 *  thread.start();
 *      
 * 总结3:之前的程序都是单线程的,执行main方法就启动一个main线程
 * 
 * 
 * 总结4:乌龟兔子交替领先的实质:获取了CPU,被执行代码
 * 
 * 
 * 总结5:关于Thread的常用的方法
 *      run() 线程体
 *      start()  启动线程对象
 *      getName()
 *      setName("tortoisethread");
 *      getPriority()
 *      Thread.currentThread() 获取当前线程
 *      
 * @author Administrator
 *
 */
public class Test {

    public static void main(String[] args) {
        //创建线程对象
        Thread thread = new TortoiseThread();
        thread.setName("tortoisethread");
        thread.setPriority(10);
        //启动线程
        //thread.run();
        thread.start();

        //兔子也在跑
        while(true){
            System.out.println("兔子领先了,add oil......,当前线程的名称:"+
                    Thread.currentThread().getName()+",当前线程的优先级别:"
                    +Thread.currentThread().getPriority());
        }
    }
}

方式2:实现Java.lang.Runnable接口,并实现run() 方法

public class TortoiseRunnable implements Runnable{

    //private int tickeNum=200;

    @Override
    public void run() {
        while(true){
            System.out.println("乌龟领先了,加油.....,当前线程的名称:"+
                    Thread.currentThread().getName()+",当前线程的优先级别:"
                    +Thread.currentThread().getPriority());
        }
    }

}


/**
 *  功能:龟兔赛跑
 *  技能:线程的创建和启动
 *  
 *  方式2:实现Runnable接口
 *  
 *  
 * 总结1:如何创建线程
 * public class TortoiseRunnable implements Runnable{
 *  public void run(){
 *      //线程体
 *  }
 * }
 * 
 * 
 * 总结2:如何启动线程
 *  Runnable tr = new TortoiseRunnable();
 *  Thread thread = new Thread(tr);
 *  thread.start();
 * 
 * 总结3:两种方式的优缺点
 *  方法1:extends Thread
 *      优点:编码稍微简单些
 *      缺点:不能再继承其他类
 * 
 *  方式2:implements Runnable
 * 
 *      缺点:编码有些复杂点
 *      优点:可以再继承其他类
 * 
 *  实际开发中使用方式2更多一些,可以更方便的实现多线程之间的资源共享
 * 
 * 
 *  
 * @author Administrator
 *
 */
public class Test {

    public static void main(String[] args) {
        //创建和启动乌龟线程
        Runnable tr = new TortoiseRunnable();
        //tr.run();

        Thread thread = new Thread(tr);
        thread.start();//启动一个新的线程,不见得会立刻执行,会进入就绪队列
        //tr.run();//不是启动线程,是一个简单的方法调用,只有一个线程

        //兔子也在跑
        while(true){
            System.out.println("兔子领先了,add oil......,当前线程的名称:"+
                    Thread.currentThread().getName()+",当前线程的优先级别:"
                    +Thread.currentThread().getPriority());
        }
    }

}


/**
 * 功能:龟兔赛跑
 * 技能:线程的创建和启动
 * 
 * 1.关于线程的构造方法
 *  可以通过构造方法传递线程名称、线程所属的线程组.....
 *  private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc)
 * 
 * 2.关于匿名内部类
 *  如果每个线程体只需要执行一次,采用匿名内部类即可,可以不提供具体的实现类
 * 
 *      Runnable runnable =new Runnable(){
            @Override
            public void run() {
                while(true){
                    System.out.println("乌龟领先了,加油.....,当前线程的名称:"+
                            Thread.currentThread().getName()+",当前线程的优先级别:"
                            +Thread.currentThread().getPriority());
                }               
            }           
        };
 * 

 *      
 * @author Administrator
 *
 */
public class Test {

    public static void main(String[] args) {
        //创建线程对象
//      Thread thread = new TortoiseThread();
//      thread.setName("tortoisethread");

        //Thread thread = new TortoiseThread("tortoisethread");
        //      thread.start();


        //Runnable runnable = new TortoiseRunnable();
        Runnable runnable =new Runnable(){
            @Override
            public void run() {
                while(true){
                    System.out.println("乌龟领先了,加油.....,当前线程的名称:"+
                            Thread.currentThread().getName()+",当前线程的优先级别:"
                            +Thread.currentThread().getPriority());
                }               
            }           
        };

        Thread  thread = new Thread(runnable);
//      thread.setName("乌龟线程");
//      Thread thread = new Thread(runnable, "乌龟线程");
        thread.start();

        Thread  thread2 = new Thread(runnable);
        thread2.start();


    }

}


方式3:实现Callable接口,并实现call()方法

import java.util.Random;
import java.util.concurrent.Callable;//concurrent 并发
import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

/**
 * 创建线程的第三种方式
 * 总结1: 实现Callable接口,实现call()
 *  1.JDK1.5后提供的
 *  2.支持泛型
 *  3.有返回值
 *  4.抛出异常(运行时异常 检查异常)
 *  5.方式3功能强大
 * 
 * 
 * 总结2:如何创建线程对象和启动
 * 借助FutureTask
 * 
 * public class FutureTask<V> implements RunnableFuture<V>
 * public interface RunnableFuture<V> extends Runnable, Future<V>
 * 
 * Callable<Integer> callable = new RandomThread();
 * FutureTask<Integer>  task = new FutureTask<Integer>(callable);
 * Thread thread = new Thread(task);
 * thread.start();
 * 
 * Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

 * @author Administrator
 *
 */
public class RandomThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        Thread.sleep(4000);//线程在此睡觉4秒钟
        return new Random().nextInt(10);
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //创建线程对象并启动
        Callable<Integer> callable = new RandomThread();

        FutureTask<Integer>  task = new FutureTask<Integer>(callable);

        Thread thread = new Thread(task);
        thread.start();
        //得到返回值并输出
        System.out.println(task.isDone());
        int result =task.get();//当前线程main在此阻塞,等task管理的线程执行完毕后,得到返回值并继续执行
        System.out.println(task.isDone());
        System.out.println(result);     

    }
}

三种线程创建方式的比较

继承Thread类方式的多线程
优势:编写简单
劣势:无法继承其它父类

实现Runnable接口方式的多线程
优势:可以继承其它类,多线程可共享同一个Runnable对象
劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThread()方法

实现Runnable接口方式要通用一些。
第三种方式:实现Callable接口
与实行Runnable相比, Callable功能更强大些
方法不同
可以有返回值,支持泛型的返回值
可以抛出异常
需要借助FutureTask,比如获取返回结果

Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

Thread类常用方法
这里写图片描述

线程的生命周期

这里写图片描述
1、新生状态:
用new关键字建立一个线程对象后,该线程对象就处于新生状态。
处于新生状态的线程有自己的内存空间,通过调用start进入就绪状态
2、就绪状态:
处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU
当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称之为“cpu调度”。
3、运行状态:
在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任务而死亡。
如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。
4、阻塞状态:
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
5、死亡状态:
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过执行stop方法来终止一个线程[不推荐使用】,三是线程抛出未捕获的异常

线程控制方法

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
线程的优先级用数字表示,范围从1到10
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
优先级最低1,最高10,默认5
使用下述方法获得或设置线程对象的优先级。
int getPriority();
void setPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。

1、join ()
阻塞指定线程等到另一个线程完成以后再继续执行
2、sleep ()
使线程停止运行一段时间,将处于阻塞状态
如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!
3、yield ()
让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态
如果调用了yield方法之后,没有其他等待执行的线程,这个时候当前线程就会马上恢复执行!
4、setDaemon()
可以将指定的线程设置成后台线程
创建后台线程的线程结束时,后台线程也随之消亡
只能在线程启动之前把它设为后台线程
5、interrupt()
并没有直接中断线程,而是需要被中断线程自己处理
6、stop()
结束线程,不推荐使用

线程同步

应用场景:
多个用户同时操作一个银行账户。每次取款100元,取款前先检查余额是否足够。如果不够,放弃取款

分析
使用多线程解决
开发一个取款线程类,每个用户对应一个线程对象
因为多个线程共享同一个银行账户,使用Runnable方式解决

思路
创建银行账户类Account
创建取款线程AccountRunnable
创建测试类TestAccount,让两个用户同时取款

/**
 * 银行账号类
 * @author Administrator
 *
 */
public class Account {

    private int balance=600;//余额
    /**
     * 取款
     */
    public void withDraw(int money){
        this.balance = this.balance -money;
    }
    /**
     * 查询余额
     * @return
     */
    public  int getBalance(){
        return balance;
    }
}


/**
 * 取款线程类
 * @author Administrator
 *
 */
public class AccountRunnable implements Runnable{

    private Account account = new Account();

    @Override
    public void run() {
            if(account.getBalance()>400){//余额足够
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
                //取款
                account.withDraw(400);          
                //输出取款信息
                System.out.println(Thread.currentThread().getName()
                        +"取款成功,最新余额是:"+account.getBalance());
            }else{
                //输出无法取款的信息
                System.out.println(Thread.currentThread().getName()
                        +"取款不成功,最新余额是:"+account.getBalance());
            }

        }

}
/**
 * 
 * 功能:多个用户同时对一个账号取款
 * 技能:线程同步(线程安全) synchronized
 * 
 * 一共600元,每次取款400元
 * 
 * 分析
 *  1.定义一个取款线程类即可
 *  2.创建多个取款线程对象并启动开始取款
 * 
 * @author Administrator
 *
 */
public class Test {

    public static void main(String[] args) {
        //创建两个线程对象
        AccountRunnable target = new AccountRunnable();
        Thread th1 = new Thread(target);
        th1.setName("张三");
        Thread th2 = new Thread(target, "张三妻子");


        //启动两个线程对象
        th1.start();
        th2.start();

    }
}

结果: 张三和张三的妻子都取了400,最后存款为-200,这就是没有设置线程同步的结果,会出现不安全因素。

线程同步的解决方案

当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全

线程同步
当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用

线程同步的实现方案
同步代码块
synchronized (obj){ }
同步方法
private synchronized void makeWithdrawal(int amt) {}
Lock锁

同步监视器
synchronized (obj){ }中的obj称为同步监视器
同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器是this,也就是该对象本事

同步监视器的执行过程
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器未锁,锁定并访问

Lock锁
JDK1.5后新增功能,与采用synchronized相比,lock可提供多种锁方案,更灵活
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义, 但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
注意:如果同步代码有异常,要将unlock()写入finally语句块
Lock和synchronized的区别
1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock—-同步代码块(已经进入了方法体,分配了相应资源)—-同步方法(在方法体之外)

线程同步的优点和缺点:
线程同步的好处
解决了线程安全问题

线程同步的缺点
性能下降
会带来死锁

死锁
当两个线程相互等待对方释放“锁”时就会发生死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
多线程编程时应该注意避免死锁的发生

同步代码块解决方案:

/**
 * 取款线程类
 * 
  * 线程同步的解决方案1:同步(synchronized)代码块
 * 
 *  总结1:认识同步监视器(锁子)
 *      synchronized(同步监视器){ }
 *      1.必须是引用数据类型,不能是基本数据类型
 *      2.在同步代码块中可以改变同步监视器对象的值,不能改变其引用
 *      3.尽量不要String和包装类做同步监视器.如果使用了,只要保证代码块中不对其进行任何操作也没有关系
 *      4.一般使用共享资源做同步监视器即可
 *      5.也可以创建一个专门的同步监视器,没有任何业务含义
 *      6.建议使用final修饰同步监视器
 *  
 *  
*  总结2:同步代码块的执行过程
 *      1.第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
 *      2.第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
 *      3.第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,
 *          第二个线程也进入阻塞状态
 *      4.第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
 *      5.第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,重复第一个线程的处理过程(加锁)
 *  强调:同步代码块中能发生线程切换吗?能!!!
 *       但是后续的被执行的线程也无法执行同步代码块(锁仍旧close)
 *  
 *  总结3:线程同步 优点和缺点
 *      优点:安全
 *      缺点: 效率低下  可能出现死锁
 *  
 *  总结4:其他
 *      1.多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块
 *          其他线程无法访问其中的任何一个代码块
 *      2.多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,
 *      但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块
 * 
 * @author Administrator
 *
 */
public class AccountRunnable implements Runnable {
    private Account account = new Account();
    //private int n = 50;
    //private String str = "bjsxt";
    //private Integer in = 5;
    final private Object obj = new Object();
    final private byte [] buf = new byte[1];
    @Override
    public void run() {
        //此处省略300句

        synchronized(account){//同步监视器  锁子
//          str = "abcd";
//          obj = new Object();
//          in  = 20; //-128  ---127
            //account = new Account();
            //余额足够,就取款,如果不够,就提示
            if(account.getBalance()>=400){ //600  600  200  -200
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //取款
                account.withDraw(400);
                //给出结果
                System.out.println(Thread.currentThread().getName()+"取款成功,现在的余额是:"+account.getBalance());

            }else{
                System.out.println(Thread.currentThread().getName()+"取款余额不足,现在的余额是:"+account.getBalance());
            }
        }

        //此处省略400句


    }

    public void method1(){
        synchronized(account){

        }
    }

    public void method2(){
        synchronized(buf){

        }
    }

    public void method3(){
        synchronized(buf){

        }
    }

}

同步方法解决方案:

/**
 * 取款线程类
 * 
 * 线程同步方式2:同步方法
 * 1.run ()不要设置为同步方法,并发变成了串行,多线程变成了单线程
 * 2.同步方法的同步监视器一律都是this
 * 3.一旦锁住了一个同步方法,就锁住了该类的所有同步方法,因为同步监视器都是this
 * 4.Java代码中的同步方法
 *  StringBuffer StringBuilder
 *  Vector ArrayList
 *  Hashtable  HashMap
 *  前一个都是线程同步(线程安全)的,后一个都线程不安全的
 *  
 * 5.线程同步的缺点:性能降低,可能导致死锁
 * 
 * @author Administrator
 *
 */
public class AccountRunnable implements Runnable {
    private Account account = new Account();
    StringBuffer buffer;
    StringBuilder builder;
    @Override
    public  void run() {
         //此处省略300句

        //余额足够,就取款,如果不够,就提示
        withDraw();

        //此处省略400句

    }

    public synchronized void withDraw(){//this
        if(account.getBalance()>=400){ //600  600  200  -200
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //取款
            account.withDraw(400);
            //给出结果
            System.out.println(Thread.currentThread().getName()+"取款成功,现在的余额是:"+account.getBalance());

        }else{
            System.out.println(Thread.currentThread().getName()+"取款余额不足,现在的余额是:"+account.getBalance());
        }
    }


    public synchronized void method1(){//this

    }

    public synchronized void method2(){//this

    }

    public synchronized void method3(){//this

    }
}

Lock锁解决方案:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 取款线程类
 * 线程同步方式3:Lock锁
 * 
 * 线程同步的方案3:Lock
 * 1.JDK1.5后新增的功能
 *  2.如果同步代码有异常,要将unlock()写入finally语句块
 *  3.与采用synchronized相比,lock可以提供多种锁的方案,更加灵活
 *  
 *  java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,
 *  它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。
 *  这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
 *  
 *   ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,
 *   但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
 *   此外,它还提供了在激烈争用情况下更佳的性能。
 *   
 *   Lock和synchronized的区别
 *   1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
 *   2.Lock只有代码块锁,synchronized有代码块锁和方法锁
 *   3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
 *   
 *   优先使用顺序:Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)
 * 
 * @author Administrator
 *
 */
public class AccountRunnable implements Runnable {
    private Account account = new Account();
    //买一把锁
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        //上锁
        lock.lock();
        try{
            //余额足够,就取款,如果不够,就提示
            if(account.getBalance()>=400){ //600  600  200  -200
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //取款
                account.withDraw(400);


                //给出结果
                System.out.println(Thread.currentThread().getName()+"取款成功,现在的余额是:"+account.getBalance());

            }else{
                System.out.println(Thread.currentThread().getName()+"取款余额不足,现在的余额是:"+account.getBalance());
            }
        }finally{
            //解锁
            lock.unlock();
        }

    }

}

小结:

进程和线程
线程的创建和启动
线程生命周期
线程控制
线程同步
同步代码块
同步方法
ReentrantLock锁

编码题练习:
1.设计一个多线程的程序如下:设计一个火车售票模拟程序。假如火车站要有100张火车票要卖出,现在有5个售票点同时售票,用5个线程模拟这5个售票点的售票情况。

public class SaleTicket implements Runnable {
    public int total = 100;// 总票数
    public int count = 0;// 票号
    public void run() {
        while (total > 0) {
            synchronized (this) {
                if (total > 0) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++; // 票号++
                    total--;// 总数减少
                    // 输出当前的售票窗口和票号
                    System.out.println(Thread.currentThread().getName();
                                                + "\t当前票号:" + count);
                }
            }
        }
    }
}
测试类
public class Test {
    public static void main(String[] args) {
        // 创建线程类对象
        SaleTicket st = new SaleTicket();
        // 启动5次线程
        for (int i = 1; i <= 5; i++) {
            new Thread(st, "售票点" + i).start();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值