11.多线程&&并发
Posted Abri
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了11.多线程&&并发相关的知识,希望对你有一定的参考价值。
11.1 操作系统中线程和进程的概念
一些常见的概念:
程序:指令和数据的byte序列,eg:qq.exe;a2.
进程:正在运行的程序(如QQ);a3.一个进程中可能有一到多个线程.
线程的概念:Thread 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。 (为什么不由进程来控制和调度线程呢,这是因为内核会把进程看做他运行的最小单位,当进程中(实际可能只是进程中某个线程)出现IO或者硬盘等一些耗时操作时,此时内核会把进程整个挂起来,此时进程中的其他线程也会被挂起来,想想一下一个文字处理器软件,该软件有自动保存功能,只因为实现自动保存的线程发起了IO调用,内核就把进程挂了起来了,此时主管视图的线程也被挂了起来,此时我们会看到什么情况呢,软件打开着却不能进行任何操作,过一会进程运行然后就可以操作,然后再自动保存,有被挂起来,循序的进行这样的尴尬事.而如果由操作系统即内核负责的话,那么他只会把负责自动保存的线程挂起来,不影响其他操作,只是让自动保存的这个线程一会运行,一会阻塞,一会就绪,然后再运行,不会影响进程的运行!)
多线程的概念: 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多线程是在同一时间需要完成多项任务的时候实现的。
多线程的优点:使用线程可以把占据长时间的程序中的任务放到后台去处理
用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度 ·
程序的运行速度可能加快 ·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。
在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
11.1.1 线程和进程的概念及关系
现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。
内存执行进程和线程都是轮换执行,而不是把一个进程或线程执行完,才执行下一个,那样会造成用户的长时间等待;虽然在同一时刻只有一个进程在运行,但是很多程序在短时间内不断地切换,在外界看来,似乎多个程序在同时执行.对于以秒为计算单位的人类来说,CPU给每个程序的运行时间只有几十毫秒甚至纳秒,进程的循环往复使我们看起来所有程序在同时运行.这样兼顾了效率和公平.
线程的运行和进程有异曲同工之妙,线程被运行完,线程生命周期消失,但有一类线程的生命周期却是很长的,由服务器决定,那就是线程池,线程池中的线程运行完后还到线程池,用的时候再拿.服务器关闭线程池销毁,服务器重启会重新创建线程池,里边的线程也是新的;线程有这么几个特性:
1.不知道什么时候会被挑中执行,(线程并不遵循什么先来先得,而是看优先级,另外也很随机,可能先来的线程反而后执行)
2.执行过程中会被随时打断,让出CPU空间
3.一旦出现硬盘、数据库等这样耗时的操作,也得让出CPU去等待;
4.就是数据来了,也不一定马上执行,还得等着CPU挑选;
11.1.2 线程的运行流程和基本状态:
新建线程---->就绪---->CPU---->阻塞----->就绪----->CPU----->阻塞----->就绪----->CPU......------>线程执行完毕,线程被kill;
线程产生后,会带着自己要处理的数据和请求到达就绪序列,此时等待,如果优先级很高,那么会很快执行,否则只能随机被CPU抽中,CPU处理线程,当处理一段时间或者是线程中有进行IO流或者数据库、硬盘等比较耗时的操作时,CPU会停止线程的运行,将线程运行的程度\\地址等等保存,然后赶出CPU,此时线程到达阻塞序列,然后再到就绪序列,等待CPU再次执行,直到线程执行完,然后线程要么被归还到线程池,要么线程被关闭,也就是kill.
上面其实就是线程的基本状态:
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
11.1.3 Java中的线程
在Java中,“线程”指两件不同的事情:
1、java.lang.Thread类的一个实例;
2、线程的执行。
使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。一旦创建一个新的线程,就产生一个新的调用栈。线程总体分两类:用户线程User Thread和守护线程Daemon Thread。当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的
11.1.4 多线程
11.2 多线程的定义与启动
线程的定义有两种方式,继承java.lang.Thread类或者实现java.lang.Runnable接口,观察API我们发现其实java.lang.Thread类也是实现了Runnable接口,由于java是单继承的,不支持多继承,所以我们往往为了改变这种限制,可以使用实现接口的方式来实现多线程技术;这也是最常用的方式.
11.2.1 定义线程
1、扩展java.lang.Thread类。
此类中有个run()方法,应该注意其用法:
public void run()
如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。
Thread的子类应该重写该方法。
2、实现java.lang.Runnable接口。
void run()
使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。
方法run的常规协定是,它可能执行任何所需的操作。
11.2.2 实例化线程
1、如果是扩展java.lang.Thread类的线程,则直接new即可。
2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
11.2.3 启动线程
在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。在调用start()之前当前线程调用run()是同步的,不会产生非线程安全
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。
package thread; /*创建线程两种方法: * 方式一: 继承Thread类 * a: 自定义类 继承 Thread类 * b: 重写run()方法 * c: 创建自定义类对象 * d: 调用start()方法 */ public class MyThread extends Thread { /* * 为什么要继承Thread 类? * 因为继承Thread类, 自定义类 就是一个线程类 * * 为什么要重写run()方法呢? * 我们写的多线程代码,java设计者最初是不知道,提供给我们一个方法,这个方法就是用来执行多线程的代码的 * 这个方法就是run()方法, 这个方法内的代码 就是 多线程需要执行的代码 * * 为什么执行线程的时候,调用的start() 不是 调用run()方法?? * run(): 方法的普通调用,run()方法内是线程要执行的代码 * start(): 把该线程启动, 自动调用run()方法 * */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName()+"--"+i); } super.run(); } }
实现接口方式实现多线程
package thread; /* * 如何获取到线程的名字呢? * * public static Thread currentThread()返回对当前正在执行的线程对象的引用。 */ public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"--"+i); } } }
package thread; /* * 多线程:程序有多条执行路径 * * 进程:就是运行的应用程序 * 线程:应用程序的执行路径 * 单线程:程序只有一条执行路径 * 多线程:程序有多条执行路径 * * 举例: * 多线程: 迅雷下载 * * 单进程单线程: 一个人点一桌菜 * 单进程多线程: 多个人点一桌菜 * 多进程多线程:多个人点多桌菜 * * * 创建一个多线程的程序 * 首先需要创建一个进程,java语言不能直接操作系统,换句话来说,不能直接创建进程 * java提供了一个类,这个类就可以解决进程相关的操作,这个类就是 线程类 ( Thread ) * * 通过查看API,发现创建线程有两种方式: * 方式一: 继承Thread类 * a: 自定义类 继承 Thread类 * b: 重写run()方法 * c: 创建自定义类对象 * d: 调用start()方法 */ public class ThreadDemo{ public static void main(String[] args) { //创建多线程 threadTest(); //获取线程 threadRunnableTest(); } /* * 通过查看API,发现创建线程有两种方式: * 方式一: 继承Thread类 * a: 自定义类 继承 Thread类 * b: 重写run()方法 * c: 创建自定义类对象 * d: 调用start()方法 把该线程启动, 自动调用run()方法 * * 线程中的方法: * public final String getName(): 获取线程的名字 * 默认的名字: Thread-编号, 编号从0开始 * * public final void setName(String name): 指定线程的名字 */ public static void threadTest(){ // 创建自定义类对象 // MyThread m = new MyThread();//创建了一个线程对象 // m.run(); // m.run(); //创建自定义线程类对象 MyThread mth1 = new MyThread(); MyThread mth2 = new MyThread(); mth2.setName("线程1"); mth1.start(); mth2.start(); System.out.println("先运行本句输出语句:从运行结果看,线程运行抛开优先级是没有顺序的是随机的;"); } /* * 创建线程的第二种方式: 实现 Runnable接口 * a: 自定义类 实现 Runnable接口 * b: 重写run方法 * c: 创建自定义类对象 * d: 创建Thread类对象, 在创建的时候,把自定义类对象,做为构造函数的参数 * e: 调用start()方法 * * 构造函数 * public Thread(Runnable target)分配新的 Thread 对象 * public Thread(Runnable target,String name)分配新的 Thread 对象, 同时指定线程的名字 */ public static void threadRunnableTest(){ //创建自定义类对象 MyRunnable mth1 = new MyRunnable(); //创建两个线程对象 Thread th1 = new Thread(mth1); Thread th2 = new Thread(mth1); //Thread th3 = new Thread(mth1,"线程2"); th1.start(); th2.start(); System.out.println("获取线程的名字:"+"qw"); th1.stop(); } }
Thread类源代码解析
测试代码: // 创建自定义类对象 MyRunnable m = new MyRunnable(); // 创建两个线程对象 Thread t1 = new Thread(m); t1.start(); 源代码 Class Thread { public Thread(Runnable target) {// target -- m -- MyRunnable类 init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { //target -- m init(g, target, name, stackSize, null); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //把自定义类中的对象m 赋值给 Thread类中的 target this.target = target; } @Override public void run() { if (target != null) { // m.run(); target.run(); } } }
11.2.4 多线程的异步问题即非线程安全
单任务的特点是排队进行,也就是同步,但是单任务会使CPU的利用率大幅减低.而使用多线程则可以最大程度的利用CPU,使系统的利用率大大提升,但是多线程是异步的,使用多线程就是在使用异步.多线程存在共享数据的情况,共享数据就是多个线程可以访问同一个变量,这会造成非线程安全.非线程安全主要指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改\\值不同步的情况,进而影响程序的执行流程.
11.2.4.1 买票问题-----synchronized锁
package thread; public class TicketThread implements Runnable{ //定义票的张数 private int ticket = 100; @Override public void run() { //多线程中执行的操作,通常是很耗时的,我们循环来模拟耗时的操作 //卖票 while (true) { //为了查看效果,加了睡眠操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(ticket>0){ System.out.println("当前线程"+Thread.currentThread().getName()+"在卖第"+ticket--+"张票"); } } } }
package thread; /* * 有100张票, 3个窗口在卖票, 模拟卖票操作 * * 在使用多线程的时候, 是使用第一种创建方式好(继承Thread类), 还是第二种(实现Runnable接口) ?? * java 是单继承, 多实现, 所以 一般使用 第二种 * */ public class TicketDemo { public static void main(String[] args) { //卖票联系 threadTest(); } //买票,三个窗口 三个线程 public static void threadTest(){ //创建自定义类对象 TicketThread tt = new TicketThread(); //创建线程对象 Thread th1 = new Thread(tt, "窗口1"); Thread th2 = new Thread(tt, "窗口2"); Thread th3 = new Thread(tt, "窗口3"); //开始卖票 th1.start(); th2.start(); th3.start(); } }
当前线程窗口2在卖第100张票
当前线程窗口1在卖第99张票
当前线程窗口3在卖第98张票
当前线程窗口2在卖第97张票
当前线程窗口1在卖第96张票
当前线程窗口3在卖第95张票
当前线程窗口1在卖第94张票
当前线程窗口2在卖第93张票
当前线程窗口3在卖第92张票
当前线程窗口2在卖第91张票
当前线程窗口1在卖第90张票
当前线程窗口3在卖第89张票
当前线程窗口2在卖第88张票
当前线程窗口3在卖第87张票
当前线程窗口1在卖第86张票
当前线程窗口2在卖第85张票
当前线程窗口3在卖第84张票
当前线程窗口1在卖第83张票
当前线程窗口2在卖第82张票
当前线程窗口3在卖第81张票
当前线程窗口1在卖第80张票
当前线程窗口2在卖第79张票
当前线程窗口3在卖第78张票
当前线程窗口1在卖第77张票
当前线程窗口2在卖第76张票
当前线程窗口3在卖第75张票
当前线程窗口1在卖第74张票
当前线程窗口2在卖第73张票
当前线程窗口1在卖第72张票
当前线程窗口3在卖第71张票
当前线程窗口2在卖第70张票
当前线程窗口1在卖第69张票
当前线程窗口3在卖第68张票
当前线程窗口2在卖第67张票
当前线程窗口1在卖第66张票
当前线程窗口3在卖第65张票
当前线程窗口2在卖第64张票
当前线程窗口1在卖第63张票
当前线程窗口3在卖第62张票
当前线程窗口1在卖第60张票
当前线程窗口2在卖第61张票
当前线程窗口3在卖第59张票
当前线程窗口1在卖第57张票
当前线程窗口2在卖第58张票
当前线程窗口3在卖第56张票
当前线程窗口2在卖第55张票
当前线程窗口1在卖第54张票
当前线程窗口3在卖第53张票
当前线程窗口3在卖第52张票
当前线程窗口1在卖第50张票
当前线程窗口2在卖第51张票
当前线程窗口3在卖第49张票
当前线程窗口1在卖第48张票
当前线程窗口2在卖第48张票
当前线程窗口3在卖第47张票
当前线程窗口2在卖第46张票
当前线程窗口1在卖第45张票
当前线程窗口3在卖第44张票
当前线程窗口2在卖第43张票
当前线程窗口1在卖第42张票
当前线程窗口3在卖第41张票
当前线程窗口1在卖第40张票
当前线程窗口2在卖第39张票
当前线程窗口3在卖第38张票
当前线程窗口2在卖第37张票
当前线程窗口1在卖第36张票
当前线程窗口3在卖第35张票
当前线程窗口2在卖第34张票
当前线程窗口1在卖第33张票
当前线程窗口2在卖第32张票
当前线程窗口3在卖第31张票
当前线程窗口1在卖第30张票
当前线程窗口2在卖第29张票
当前线程窗口3在卖第28张票
当前线程窗口1在卖第27张票
当前线程窗口2在卖第26张票
当前线程窗口3在卖第25张票
当前线程窗口1在卖第24张票
当前线程窗口3在卖第23张票
当前线程窗口2在卖第22张票
当前线程窗口1在卖第21张票
当前线程窗口2在卖第20张票
当前线程窗口3在卖第20张票
当前线程窗口1在卖第19张票
当前线程窗口1在卖第18张票
当前线程窗口2在卖第17张票
当前线程窗口3在卖第18张票
当前线程窗口2在卖第16张票
当前线程窗口3在卖第15张票
当前线程窗口1在卖第14张票
当前线程窗口1在卖第13张票
当前线程窗口3在卖第12张票
当前线程窗口2在卖第11张票
当前线程窗口2在卖第10张票
当前线程窗口1在卖第9张票
当前线程窗口3在卖第8张票
当前线程窗口2在卖第7张票
当前线程窗口1在卖第6张票
当前线程窗口3在卖第5张票
当前线程窗口2在卖第4张票
当前线程窗口1在卖第3张票
当前线程窗口3在卖第2张票
当前线程窗口2在卖第1张票
从上面的代码中我们看到,窗口2和窗口3卖出了共同的一张票这是为什么呢,由于一个线程在操作时,输出语句中有--操作,线程进行到这里没有执行--就被挂起了,也就是已经卖出票了,但是没有将库存票数减1,另一个线程进入然后执行完输出卖出第20张票,此时挂起的线程进来继续剩下的,所以看到输出了两个20 ;这是多线程非线程安全的典型例子,那么该怎么解决呢,这里就引入了锁的概念,我们可以在一个线程运行时,将其锁起来,给这个锁一个名字,其他线程进来,获取锁,如果不能获取到锁那么就意味着里边有一个线程在执行,此时这个线程会不停的申请锁,直到能够拿到为止,而且是多个线程在申请这把锁,这里的锁其实是同步代码块和同步方法,关键字为synchronized,下面通过同步代码块和同步方法来实现
package cn.itcast_05; /* * 票 * * public static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行), */ public class Ticket implements Runnable { //100票 private int ticket = 100; // @Override // public void run() { // // //多个窗口卖同一张票 // //t1,t2,t3 // //卖票 // while(true){ // // //t1,t2,t3 // // //模拟休息一会 // try { // Thread.sleep(10); // } catch (InterruptedException e) { // e.printStackTrace(); // } // // //t1,t2,t3 // //当前50票 // if(ticket > 0){ // System.out.println( Thread.currentThread().getName() +"正在卖第 "+ ticket-- +" 张票"); // //ticket-- 这句话, 其实 做了 2个事件 // //第一个操作 卖了当前的这张票ticket 50 // //第二个操作,票数-1, 变成了 49 // // //t1,窗口1正在卖第50票,还没有做 ticket--操作, t2进来了,t1失去了CPU执行权 // //t2,窗口2正在卖第50票,之后 ticket--操作, 票 49 // // } // } // } @Override public void run() { //票出现了负数 //卖票 while(true){ //t1,t2,t3 //模拟休息一会 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //t1,t2,t3 //最后一张票 if(ticket > 0){ //t1, System.out.println( Thread.currentThread().getName() +"正在卖第 "+ ticket-- +" 张票"); //t1, 窗口1 正在卖第1张票, 在做 票--之前, t2进来了,t2发现还有一张票,t2可以出票 //t2 //t3 // t1票-- 1-->0 //t2, 窗口2 正在卖第0张票, // t2票-- 0--> -1 //t3, 窗口3 正在卖第 -1 张票 // t3票-- -1 --> -2 } } } }
package cn.itcast_05; /* * 有100张票, 3个窗口在卖票, 模拟卖票操作 * * 在使用多线程的时候, 是使用第一种创建方式好(继承Thread类), 还是第二种(实现Runnable接口) ?? * java 是单继承, 多实现, 所以 一般使用 第二种 * * 发现模拟真实的情况进行卖票,出现了问题: * * a: 多个窗口卖同一张票 * 如果多线程中的代码很简单,是不会出现问题的 * 如果,当前的多线程代码中 有类似与 如下的代码 SOP( ticket-- ); * 因为当前的这个操作, 它实际做了一个以上的操作,那么就在第一个操作完毕,而第二个操作没执行之前, 另外一个线程进来执行了 * b: 出现了负数的票 * 多个线程,同时通过判断条件 */ public class TicketDemo { public static void main(String[] args) { //创建票对象 Ticket t = new Ticket(); //创建3个线程对象 Thread t1 = new Thread(t, "窗口1:"); Thread t2 = new Thread(t, "窗口2:"); Thread t3 = new Thread(t, "窗口3:"); t1.start(); t2.start(); t3.start(); } }
package cn.itcast_06; public class Ticket implements Runnable { //100票 private int ticket = 100; //锁对象 private Object obj = new Object(); @Override public void run() { //卖票 while(true){ synchronized (obj){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(ticket > 0){ System.out.println( Thread.currentThread().getName() +"正在卖第 "+ ticket-- +" 张票"); } } } } }
package cn.itcast_06; /* * 有100张票, 3个窗口在卖票, 模拟卖票操作 * * 在使用多线程的时候, 是使用第一种创建方式好(继承Thread类), 还是第二种(实现Runnable接口) ?? * java 是单继承, 多实现, 所以 一般使用 第二种 * * 发现模拟真实的情况进行卖票,出现了问题: * * a: 多个窗口卖同一张票 * 如果多线程中的代码很简单,是不会出现问题的 * 如果,当前的多线程代码中 有类似与 如下的代码 SOP( ticket-- ); * 因为当前的这个操作, 它实际做了一个以上的操作,那么就在第一个操作完毕,而第二个操作没执行之前, 另外一个线程进来执行了 * b: 出现了负数的票 * 多个线程,同时通过判断条件 * * 为什么会出现问题?找问题 * 当前多线程的程序 * 是否有共享的内容 * 这个共享的内容,把操作一次以上 * * 这个问题其实可以通过 锁机制来解决,在java中这种锁机制有另外一个称呼: 同步机制 * 可以 同步代码块来解决该问题 * 格式 : * synchronized (锁对象){ * 需要被锁的内容 * } * * 注意: 多个线程对象 必须使用同一个锁对象 */ public class TicketDemo { public static void main(String[] args) { //创建票对象 Ticket t = new Ticket(); //创建3个线程对象 Thread t1 = new Thread(t, "窗口1:"); Thread t2 = new Thread(t, "窗口2:"); Thread t3 = new Thread(t, "窗口3:"); t1.start(); t2.start(); t3.start(); } }
package cn.itcast_07; public class Ticket implements Runnable { //100票 private static int ticket = 100; //锁对象 private Object obj = new Object(); private int x = 0; @Override public void run() { //卖票 while(true){ if (x%2 == 0) { //synchronized (this){//普通方法 synchronized (Ticket.class){//静态方法 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(ticket > 0){ System.out.println( Thread.currentThread().getName() +"正在卖第 "+ ticket-- +" 张票(if)"); } } } else{ Ticket.sendTicket(); } x++; } } //卖票 // synchronized public void sendTicket(){ // try { // Thread.sleep(10); // } catch (InterruptedException e) { // e.printStackTrace(); // } // // if(ticket > 0){ // System.out.println( Thread.currentThread().getName() +"正在卖第 "+ ticket-- +" 张票(else)"); // } // } //静态方法 public static synchronized void sendTicket(){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(ticket > 0){ System.out.println( Thread.currentThread().getName() +"正在卖第 "+ ticket-- +" 张票(else)"); } } }
package cn.itcast_07; /* * 有100张票, 3个窗口在卖票, 模拟卖票操作 * * 在使用多线程的时候, 是使用第一种创建方式好(继承Thread类), 还是第二种(实现Runnable接口) ?? * java 是单继承, 多实现, 所以 一般使用 第二种 * * 发现模拟真实的情况进行卖票,出现了问题: * * a: 多个窗口卖同一张票 * 如果多线程中的代码很简单,是不会出现问题的 * 如果,当前的多线程代码中 有类似与 如下的代码 SOP( ticket-- ); * 因为当前的这个操作, 它实际做了一个以上的操作,那么就在第一个操作完毕,而第二个操作没执行之前, 另外一个线程进来执行了 * b: 出现了负数的票 * 多个线程,同时通过判断条件 * * 为什么会出现问题?找问题 * 当前多线程的程序 * 是否有共享的内容 * 这个共享的内容,把操作一次以上 * * 这个问题其实可以通过 锁机制来解决,在java中这种锁机制有另外一个称呼: 同步机制 * 可以 同步代码块来解决该问题 * 格式 : * synchronized (锁对象){ * 需要被锁的内容 * } * * 注意: 多个线程对象 必须使用同一个锁对象 * * * 方法内的数据 全部为锁上了,这个时候,我们就可以考虑直接给方法上锁 * 同步方法: 就是把锁的关键字 放在方法上 synchronized * 同步方法的锁是谁? * this * 静态修饰的同步方法,锁是谁呢? * 该静态方法所属类的 字节码文件对象 class * * public final Class<?> getClass()返回此 Object 的运行时类 ,字节码文件对象 * * Ticket * 数据类型 .class 就可以获取到当前数据类型所对应的字节码文件对象 * */ public class TicketDemo { public static void main(String[] args) { //创建票对象 Ticket t = new Ticket(); //创建3个线程对象 Thread t1 = new Thread(t, "窗口1:"); Thread t2 = new Thread(t, "窗口2:"); Thread t3 = new Thread(t, "窗口3:"); t1.start(); t2.start(); t3.start(); } }
11.2.4.2 买票问题----Lock锁
以上是关于11.多线程&&并发的主要内容,如果未能解决你的问题,请参考以下文章