-
2021-09-15 01:09:41
一、错误示例
static int a = 0; //多线程同时操作 val a @Test public void test4() throws InterruptedException { Runnable runnable = () -> { while (a < 10) { a++; System.out.print(a + "\t"); } }; Thread t1 = new Thread(runnable); t1.start(); //线程t1 启动 Thread t2 = new Thread(runnable); t2.start(); //线程t2 启动 Thread t3 = new Thread(runnable); t3.start(); //线程t3 启动 }
输出结果: 1 3 4 6 7 8 9 10 2 6
原因:
1.代码可见 我用lambda实现了runnable的实现类
2.下面创建了三个线程分别对同一个runnable变量进行start启动,让jvm去跑这个runnable
这时候三个线程是不同的
但是线程里面有个while条件,注意where条件
a如果小于10,就进循环,然后 a++ 了
这时候我说有bug,你听我细细道来,
假如第一个线程t1 第一次进来a是小于10的…因为第一次a=0
然后t1线程还没有执行到a++的时候t2又进来了
…这时候t1能放过t2吗?
t2给t1说:你管得着吗?你又没有关门,凭什么不让我进来,
这时候t1执行了a++, 但是t1还没有执行输出,t2也执行了a++
输出了:1 (反正不知道是哪个线程输出的)
输出了:3 (我估计是t3线程的干扰)二、正确示例
static int a = 0; //多线程同时操作 val a static final Object obj = new Object(); //锁对象 @Test public void test4() throws InterruptedException { Runnable runnable = () -> { synchronized (obj) { while (a < 10) { a++; System.out.print(a + "\t"); } } }; Thread t1 = new Thread(runnable); t1.start(); //线程t1 启动 Thread t2 = new Thread(runnable); t2.start(); //线程t2 启动 Thread t3 = new Thread(runnable); t3.start(); //线程t3 启动 }
输出结果: 1 2 3 4 5 6 7 8 9 10
原因:
static final Object obj = new Object();
synchronized (obj) {
}
每个线程执行到synchronized代码块的时候,都会把同一个变量 锁上
因为是static修饰的,所以多个线程都会同意这个锁是同一个锁
因为每个线程进来之前都会去看看这个锁对象有没有被占用
所以实现了分离
t1进来了,其他都进不去,当t1跑完循环,离开了 t2进来了,但是条件不满足了,a已经不小于10了
所以这个正确的结果是同一个线程完成的更多相关内容 -
Java多线程实例
2020-09-03 04:34:38本文给大家介绍java多线程实例,对java多线程知识感兴趣的朋友参考下吧 -
java多线程的讲解和实战
2018-05-02 16:46:51详细的讲解了java多线程的原理,并配有代码进行实战,适合java初学者和想对多线程有进一步了解的人。 -
Java多线程并发执行demo代码实例
2020-08-18 23:50:22主要介绍了Java多线程并发执行demo代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
编程java字符串多线程介绍_java多线程实例
2020-07-13 11:14:47@ Java组第3次交流讲件 线程 熊斌斌!Wncy ./javaDocs/htmlzh_cn/api/ @ 字符串 字符串概述 字符串是由字符组成的序列 Java中的字符串分为 定长字符串(Stng)和 可变字符串( String Buffer) abcdefghijklmnopgrstuwxyz... -
java多线程使用详解与案例,超详细
2021-03-14 17:31:47java多线程使用 java使用多线程的三种方式: 继承Thread类,并重写run方法。 实现Runnable接口,重写run方法 实现Callable<返回值>接口,重写call方法 实例如下 继承Thread class test extends Thread{ @...文章目录
java多线程使用线程lamda表达式方式启动(简单、常用)
new Thread(()->{ System.out.println("开始了一个线程"+new Date()); }).start();
java使用多线程的三种方式:
- 继承Thread类,并重写run方法。
- 实现Runnable接口,重写run方法
- 实现Callable<返回值>接口,重写call方法
实例如下
继承Thread
class test extends Thread{ @Override public void run() { System.out.println("你好啊"); } }
开始启动线程:
new test().start()
实现Runnable
class God implements Runnable{ @Override public void run() { while (true) { System.out.println("上帝守护着你"); } } }
开始启动线程
new Thread(New God()).start
实现Callable
class dThread implements Callable<Boolean> { private String url; private String name; public dThread(String url,String name) { this.url=url; this.name= name; } @Override public Boolean call() { Download d = new Download(); System.out.println(Thread.currentThread().getName()+"开始下载"); d.imageDownload(url,name); return Thread.currentThread().isAlive(); } }
开始启动线程
FutureTask <String>threadTask= new FutureTask<>(new dThread()); new Thread(threadTask).start
开始执行线程只需在线程对象调用start或call方法便开始执行线程
#####################################################
线程池的使用:
我们需要使用ExecutorService对象里面
一个方法newFixedThreadPool(5);,参数为线程存放个数。
实例对象调用execute方法,参数为runable对象
使用案例如下package cn.xjt.gaoji; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 测试线程池 * @author xujiangtao * @create 2020-08-02-14:42 */ public class TestPool { //pool:池子 public static void main(String[] args) { //创建了一个大小为10的线程池 //固定线程池 ExecutorService e = Executors.newFixedThreadPool(5); //单线程池 Executors.newSingleThreadExecutor(); //可缓存线程池 Executors.newCachedThreadPool(); //时间周期线程池 Executors.newScheduledThreadPool(6); //工作窃取线程池 Executors.newWorkStealingPool(2); //可以指定并行数 //调用execute方法开启线程 e.execute(new MyThread()); //execute:实行 e.execute(new test()); e.execute(new MyThread()); e.execute(new MyThread()); e.shutdown(); //关闭线程 } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } } class test extends Thread{ @Override public void run() { System.out.println("你好啊"); } }
守护线程:
daemon守护线程会因为用户线程关闭而关闭,默认为用户线程
Thread实例对象setDarmon()方法可以设置是否为守护者线程,默认为false。守护线程会随着用户线程的结束而结束
java守护线程案例如下:package cn.xjt.demo03; /** * daemon守护线程会因为用户线程关闭而关闭,默认为用户线程 * y * @author xujiangtao * @create 2020-08-01-10:36 */ public class TestDaemon { public static void main(String[] args) { God god = new God(); HandsomeYou handsomeyou = new HandsomeYou(); Thread thread = new Thread(god); thread.setDaemon(true); //上帝守护线程 thread.start(); new Thread(handsomeyou).start(); } } class God implements Runnable{ @Override public void run() { while (true) { System.out.println("上帝守护着你"); } } } class HandsomeYou implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("Hello world 你开心的活着"); } System.out.println("======goodbye world========"); } }
使用lamda表达式简化java的书写简单化案例如下:
package cn.xjt.demo03; /** * @author xujiangtao * @create 2020-07-31-20:10 */ public class TestLamda { //第1步简化->静态内部类 static class son2 implements Like{ @Override public void lamda() { System.out.println("i'm lamda2"); } } public static void main(String[] args) { Like s = new son1(); s.lamda(); s=new son2(); s.lamda(); //第2步简化->局部内部类 class son3 implements Like{ @Override public void lamda() { System.out.println("i'm lamda3"); } } s=new son3(); s.lamda(); //第3步简化->匿名内部类 s=new Like() { @Override public void lamda() { System.out.println("i'm Lamda4"); } }; s.lamda(); //第4步简化->Lamda s= ()-> { System.out.println("i'm Lamda5"); }; s.lamda(); } } //接口类 interface Like{ void lamda(); } //正常实现类 class son1 implements Like{ @Override public void lamda() { System.out.println("i'm lamda"); } }
多线程综合案例
1、买票案例
使用生产者消费者模式。通过缓存区,保证产品数量大于消费数量
package cn.xjt.gaoji; /** * 测试生产者消费者-->利用缓冲区:管程法 * @author xujiangtao * @create 2020-08-01-21:53 */ //成产者,消费者,产品,缓存区 public class TestPC { public static void main(String[] args) { Syncontainer syncontainer = new Syncontainer(); new Productor(syncontainer).start(); new Consumer(syncontainer).start(); } } //生产者 class Productor extends Thread{ Syncontainer container; public Productor(Syncontainer container) { this.container=container; } //生产 @Override public void run() { for (int i = 0; i < 10; i++) { try { container.push(new Chicken(i)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("生产了"+(i+1)+"只鸡"); } } } //消费者 class Consumer extends Thread{ Syncontainer container; public Consumer(Syncontainer container) { this.container=container; } //消费 @Override public void run() { for (int i = 0; i < 10; i++) { try { System.out.println("消费了->"+container.pop().id+"只鸡"); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } } //产品 class Chicken{ int id; Chicken(int id) { this.id=id; } } //缓冲区 class Syncontainer{ //需要一个容器大小 Chicken[] chickens = new Chicken[10]; int count =0; //生产者放入产品 public synchronized void push(Chicken chicken) throws InterruptedException { //如果容器满了,需要消费者消费 if(count ==chickens.length) { this.wait(); } //如果没有满,就需要生产产品放入 chickens[count] = chicken; count++; //可以通知消费者了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop() throws InterruptedException { //判断缓冲区是否还有产品 if(count==0) { //等待生产者生产,消费者等待 this.wait(); } count--; return chickens[count]; // this.notifyAll(); //吃完了,通知生产者生产 } }
2、信号灯法,通过标志为解决
package cn.xjt.gaoji; /** * 测试生产者,消费者问题 2:信号灯法 * 标志位解决 * @author xujiangtao * @create 2020-08-02-13:11 */ public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new actor(tv).start(); new watcher(tv).start(); } } //生产者->演员 class actor extends Thread{ TV tv; public actor(TV tv) { this.tv=tv; } @Override public void run() { for (int i = 0; i < 10; i++) { try { if(i%2==0) { tv.play("抖音:记录美好生活"); System.out.println(i); }else{ tv.play("快乐大本营"); System.out.println(i); } } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费者->观众 class watcher extends Thread{ TV tv; public watcher(TV tv) { this.tv=tv; } @Override public void run() { for (int i = 0; i < 10; i++) { try { tv.watch("观众"); System.out.println(i); } catch (InterruptedException e) { e.printStackTrace(); } } } } //产品->Tv class TV{ String video; // String Watchername; boolean flag = true; //演员表演,观众等待 T //观众观看,演员等待 F //表演方法 public synchronized void play(String vidio) throws InterruptedException { //判断如果是true,则表演节目 if(flag) { System.out.println("演员表演了->"+vidio); Thread.sleep(1000); //通知观众观看 this.notifyAll(); //通知观众观看节目 this.video=vidio; flag=!this.flag; }else { //否则,等待观众观看 this.wait(); } } public synchronized void watch(String watchername) throws InterruptedException { //判断如果为flase,则观众观看节目 if(!flag) { System.out.println(watchername+"观看了"+video); Thread.sleep(1000); this.notifyAll(); flag=!flag; }else{ //否则等待,直到表演者表演完,再继续观看 this.wait(); } } }
3、买票与卖票。
使用加锁。保证票的数量在有的情况才可以出售,解决并发问题demo。
package cn.xjt.gaoji; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 买票系统会出现不安全 * 需要加上锁 * 通过ReenTrantlock对象进行加锁,解锁 * private final ReentrantLock locks = new ReentrantLock(); * @author xujiangtao * @create 2020-08-01-16:59 */ public class TestReentrantLock { public static void main(String[] args) { buyticket person = new buyticket(); Thread t = new Thread(person); t.start(); t=new Thread(person); t.start(); t= new Thread(person); t.start(); } } //一个卖票的类 class buyticket implements Runnable{ //Reentrantlock:可重进锁 private final ReentrantLock locks = new ReentrantLock(); //票数 int ticket =10; @Override //重写的方法 public void run() { while (true) { try { //开启一个锁 开启一个锁最好放在try语句 locks.lock(); if(ticket>0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticket--); } else { System.out.println("你买的票已经被购完"); break; } }finally { //关闭一个锁 locks.unlock(); } } } }
-
实例分析Java单线程与多线程
2020-08-27 22:42:24本篇文章通过代码实例给大家详细讲述了Java单线程与多线程的相关原理和知识点总结,需要的朋友可以学习下。 -
java 多线程实例
2015-10-30 17:10:31java多线程应用实例,包括wait、notify、notifyAll等的应用方法,经过调试,可以直接运行。 -
Java 多线程访问的经典例子.txt
2019-12-12 17:08:40Java 多线程访问的经典例子,一个个代码 手动敲出,可以加深或理解 多线程访问 线程安全与同步的问题. -
详解java多线程的同步控制
2021-01-20 02:10:44同步控制是并发程序必不可少的重要手段,本文我们将通过重入锁、读写锁、信号量、倒计数器和循环栅栏以及他们的实例来介绍Java并发程序中的同步控制。 目录线程安全 Thread Safety重入锁 ReentrantLock读写锁 ... -
java多线程例子.pdf
2021-09-30 17:29:05java多线程例子.pdf -
JAVA多线程实例 可直接下载
2011-03-22 09:39:00利用JAVA编写的多线程运行实例 压缩文件,可直接运行 ...利用JAVA编写的多线程运行实例 压缩文件,可直接运行利用JAVA编写的多线程运行实例 压缩文件,可直接运行利用JAVA编写的多线程运行实例 压缩文件,可直接运行 -
Java多线程的实现方式比较(两种方式比较)
2020-09-03 04:35:49Java多线程实现方式有两种,第一种是继承Thread类,第二种是实现Runnable接口,两种有很多差异,下面跟着本文一起学习吧 -
java多线程编程实例
2018-05-25 10:01:22这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下。1.相关知识:Java多线程程序设计到的知识:(1)对同一个数量...这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下。
1.三个售票窗口同时出售20张票
程序分析:
(1)票数要使用同一个静态值
(2)为保证不会出现卖出同一个票数,要java多线程同步锁。
设计思路:
(1)创建一个站台类Station,继承Thread,重写run方法,在run方法里面执行售票操作!售票要使用同步锁:即有一个站台卖这张票时,其他站台要等这张票卖完!
(2)创建主方法调用类
(一)创建一个站台类,继承Thread
package com.xykj.threadStation; public class Station extends Thread { // 通过构造方法给线程名字赋值 public Station(String name) { super(name);// 给线程名字赋值 } // 为了保持票数的一致,票数要静态 static int tick = 20; // 创建一个静态钥匙 static Object ob = "aa";//值是任意的 // 重写run方法,实现买票操作 @Override public void run() { while (tick > 0) { synchronized (ob) {// 这个很重要,必须使用一个锁, // 进去的人会把钥匙拿在手上,出来后才把钥匙拿让出来 if (tick > 0) { System.out.println(getName() + "卖出了第" + tick + "张票"); tick--; } else { System.out.println("票卖完了"); } } try { sleep(1000);//休息一秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(二)创建主方法调用类
package com.xykj.threadStation; public class MainClass { /** * java多线程同步锁的使用 * 示例:三个售票窗口同时出售10张票 * */ public static void main(String[] args) { //实例化站台对象,并为每一个站台取名字 Station station1=new Station("窗口1"); Station station2=new Station("窗口2"); Station station3=new Station("窗口3"); // 让每一个站台对象各自开始工作 station1.start(); station2.start(); station3.start(); } }
程序运行结果:
窗口1卖出了第20张票 窗口2卖出了第19张票 窗口3卖出了第18张票 窗口3卖出了第17张票 窗口1卖出了第16张票 窗口2卖出了第15张票 窗口3卖出了第14张票 窗口1卖出了第13张票 窗口2卖出了第12张票 窗口2卖出了第11张票 窗口1卖出了第10张票 窗口3卖出了第9张票 窗口3卖出了第8张票 窗口1卖出了第7张票 窗口2卖出了第6张票 窗口3卖出了第5张票 窗口1卖出了第4张票 窗口2卖出了第3张票 窗口3卖出了第2张票 窗口1卖出了第1张票 票卖完了
可以看到票数是不会有错的!
2.两个人AB通过一个账户A在柜台取钱和B在ATM机取钱!
程序分析:钱的数量要设置成一个静态的变量,两个人要取的同一个对象值。
(一)创建一个Bank类package com.thread.demo.demo2; import java.util.Objects; public class Bank { // 假设一个账户有1000块钱 static double money = 1000; // 柜台Counter取钱的方法 private void Counter(double money) { Bank.money -= money; System.out.println("柜台取钱" + money + "元,还剩" + Bank.money + "元!"); } // ATM取钱的方法 private void ATM(double money) { Bank.money -= money; System.out.println("ATM取钱" + money + "元,还剩" + Bank.money + "元!"); } //提供一个对外取款途径,防止直接调取方法同时取款时,并发余额显示错误 public synchronized void outMoney(double money, String mode) throws Exception{ if(money > Bank.money){ //校验余额是否充足 throw new Exception("取款金额"+money+",余额只剩"+Bank.money+",取款失败"); } if(Objects.equals(mode, "ATM")){ ATM(money); } else { Counter(money); } } }
(二)创建一个PersonA类
package com.thread.demo.demo2; public class PersonA extends Thread { Bank bank; String mode; public PersonA(Bank bank, String mode) { this.mode = mode; this.bank = bank; } public void run (){ while(bank.money >= 100){ try { bank.outMoney(100, mode); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
(三)创建一个PersonB类
package com.thread.demo.demo2; public class PersonB extends Thread { Bank bank; String mode; public PersonB(Bank bank, String mode) { this.bank = bank; this.mode = mode; } public void run() { while (bank.money >= 200) { try { bank.outMoney(200, mode); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
(四)创建主方法的调用类
package com.thread.demo.demo2; /** * 两个人AB通过一个账户A在柜台取钱和B在ATM机取钱 * */ public class MainClass { public static void main(String[] args) { Bank bank = new Bank(); // 实例化两个人,传入同一个银行的对象 PersonA a = new PersonA(bank, "Counter"); PersonB b = new PersonB(bank, "ATM"); a.start(); b.start(); } }
运行结果:
可以看到取完就停止运行了。3.龟兔赛跑问题
龟兔赛跑:2000米
要求:
(1)兔子每 0.1 秒 5 米的速度,每跑20米休息1秒;
(2)乌龟每 0.1 秒跑 2 米,不休息;
(3)其中一个跑到终点后另一个不跑了!
程序设计思路:
(1)创建一个Animal动物类,继承Thread,编写一个running抽象方法,重写run方法,把running方法在run方法里面调用。
(2)创建Rabbit兔子类和Tortoise乌龟类,继承动物类
(3)两个子类重写running方法
(4)本题的第3个要求涉及到线程回调。需要在动物类创建一个回调接口,创建一个回调对象。
(一)创建Animal动物类
package com.thread.demo.demo3; public abstract class Animal extends Thread { public int length = 2000;// 比赛长度 public abstract void runing(); @Override public void run() { super.run(); while (length > 0) { runing(); } } // 在需要回调数据的地方(两个子类需要),声明一个接口 public static interface Calltoback { public void win(); } // 2.创建接口对象 public Calltoback calltoback; }
(二)创建Rabbit兔子类
package com.thread.demo.demo3; public class Rabbit extends Animal { public Rabbit() { setName("兔子"); } @Override public void runing() { //兔子速度 int dis = 5; length -= dis; System.out.println("兔子跑了" + dis + "米,距离终点还有" + length + "米"); if (length <= 0) { length = 0; System.out.println("兔子获得了胜利"); // 给回调对象赋值,让乌龟不要再跑了 if (calltoback != null) { calltoback.win(); } } try { if ((2000 - length) % 20 == 0) { // 每20米休息一次,休息时间是1秒 sleep(1000); } else { //没0.1秒跑5米 sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } }
(三)创建Tortoise乌龟类
package com.thread.demo.demo3; public class Tortoise extends Animal { public Tortoise() { setName("乌龟");// Thread的方法,给线程赋值名字 } // 重写running方法,编写乌龟的奔跑操作 @Override public void runing() { // 乌龟速度 int dis = 2; length -= dis; System.out.println("乌龟跑了" + dis + "米,距离终点还有" + length + "米"); if (length <= 0) { length = 0; System.out.println("乌龟获得了胜利"); // 让兔子不要在跑了 if (calltoback != null) { calltoback.win(); } } try { sleep(100); //没0.1秒跑2米 } catch (InterruptedException e) { e.printStackTrace(); } } }
(四)创建一个让动物线程停止的类,这里要实现回调接口
package com.thread.demo.demo3; import com.thread.demo.demo3.Animal.Calltoback; public class LetOneStop implements Calltoback { // 动物对象 Animal an; // 获取动物对象,可以传入兔子或乌龟的实例 public LetOneStop(Animal an) { this.an = an; } // 让动物的线程停止 @Override public void win() { // 线程停止 an.stop(); } }
(五)创建一个主方法调用类
package com.thread.demo.demo3; public class MainClass { /** * 龟兔赛跑:2000米 */ public static void main(String[] args) { // 实例化乌龟和兔子 Tortoise tortoise = new Tortoise(); Rabbit rabbit = new Rabbit(); // 回调方法的使用,谁先调用calltoback方法,另一个就不跑了 LetOneStop letOneStop1 = new LetOneStop(tortoise); // 让兔子的回调方法里面存在乌龟对象的值,可以把乌龟stop rabbit.calltoback = letOneStop1; LetOneStop letOneStop2 = new LetOneStop(rabbit); // 让乌龟的回调方法里面存在兔子对象的值,可以把兔子stop tortoise.calltoback = letOneStop2; // 开始跑 tortoise.start(); rabbit.start(); } }
运行结果:
4. 线程示例总结
(1)代码块锁是一个防止数据发生错误的一个重要手段;
(2)对象的统一性是非常重要的,这要想到对象的传入问题,要操作的对象只能new一次,其他的操作都是对这个传入的对象进行的,才能保证数据一致性,完整性和正确性。
-
java多线程-同步块实例讲解
2020-09-01 17:13:19本文主要介绍java多线程-同步块的知识,这里整理了相关的详细资料及简单示例代码,有兴趣的小伙伴可以参考下 -
java多线程的常见例子
2016-09-13 11:43:44本文有比较常见的java多线程问题的解决方法。对部分知识也有总结。java多线程的常见例子
一.相关知识:
Java多线程程序设计到的知识:
(一)对同一个数量进行操作
(二)对同一个对象进行操作
(三)回调方法使用
(四)线程同步,死锁问题
(五)线程通信
等等
二.示例一:三个售票窗口同时出售20张票;
程序分析:1.票数要使用同一个静态值
2.为保证不会出现卖出同一个票数,要java多线程同步锁。
设计思路:1.创建一个站台类Station,继承Thread,重写run方法,在run方法里面执行售票操作!售票要使用同步锁:即有一个站台卖这张票时,其他站台要等这张票卖完!
2.创建主方法调用类
(一)创建一个站台类,继承Thread
package com.xykj.threadStation; public class Station extends Thread { // 通过构造方法给线程名字赋值 public Station(String name) { super(name);// 给线程名字赋值 } // 为了保持票数的一致,票数要静态 static int tick = 20; // 创建一个静态钥匙 static Object ob = "aa";//值是任意的 // 重写run方法,实现买票操作 @Override public void run() { while (tick > 0) { synchronized (ob) {// 这个很重要,必须使用一个锁, // 进去的人会把钥匙拿在手上,出来后才把钥匙拿让出来 if (tick > 0) { System.out.println(getName() + "卖出了第" + tick + "张票"); tick--; } else { System.out.println("票卖完了"); } } try { sleep(1000);//休息一秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(二)创建主方法调用类
package com.xykj.threadStation; public class MainClass { /** * java多线程同步锁的使用 * 示例:三个售票窗口同时出售10张票 * */ public static void main(String[] args) { //实例化站台对象,并为每一个站台取名字 Station station1=new Station("窗口1"); Station station2=new Station("窗口2"); Station station3=new Station("窗口3"); // 让每一个站台对象各自开始工作 station1.start(); station2.start(); station3.start(); } }
程序运行结果:
可以看到票数是不会有错的!
三.示例二:两个人AB通过一个账户A在柜台取钱和B在ATM机取钱!
程序分析:钱的数量要设置成一个静态的变量。两个人要取的同一个对象值
(一)创建一个Bank类
package com.xykj.bank; public class Bank { // 假设一个账户有1000块钱 static int money = 1000; // 柜台Counter取钱的方法 public void Counter(int money) {// 参数是每次取走的钱 Bank.money -= money;//取钱后总数减少 System.out.println("A取走了" + money + "还剩下" + (Bank.money)); } // ATM取钱的方法 public void ATM(int money) {// 参数是每次取走的钱 Bank.money -= money;//取钱后总数减少 System.out.println("B取走了" + money + "还剩下" + (Bank.money)); } }
(二)创建一个PersonA类
package com.xykj.bank; public class PersonA extends Thread { // 创建银行对象 Bank bank; // 通过构造器传入银行对象,确保两个人进入的是一个银行 public PersonA(Bank bank) { this.bank = bank; } //重写run方法,在里面实现使用柜台取钱 @Override public void run() { while (Bank.money >= 100) { bank.Counter(100);// 每次取100块 try { sleep(100);// 取完休息0.1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(三)创建一个PersonB类
package com.xykj.bank; public class PersonB extends Thread { // 创建银行对象 Bank bank; // 通过构造器传入银行对象,确保两个人进入的是一个银行 public PersonB(Bank bank) { this.bank = bank; } // 重写run方法,在里面实现使用柜台取钱 @Override public void run() { while (Bank.money >= 200) { bank.ATM(200);// 每次取200块 try { sleep(100);// 取完休息0.1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(四)创建主方法的调用类
package com.xykj.bank; public class MainClass { /** * 两个人AB通过一个账户A在柜台取钱和B在ATM机取钱 * */ public static void main(String[] args) { // 实力化一个银行对象 Bank bank = new Bank(); // 实例化两个人,传入同一个银行的对象 PersonA pA = new PersonA(bank); PersonB pB = new PersonB(bank); // 两个人开始取钱 pA.start(); pB.start(); } }
运行结果:
可以看到取完就停止运行了。
四.示例三:龟兔赛跑问题
龟兔赛跑:20米 //只要为了看到效果,所有距离缩短了
要求:
1.兔子每秒0.5米的速度,每跑2米休息10秒,
2.乌龟每秒跑0.1米,不休息
3.其中一个跑到终点后另一个不跑了!
程序设计思路:
1.创建一个Animal动物类,继承Thread,编写一个running抽象方法,重写run方法,把running方法在run方法里面调用。
2.创建Rabbit兔子类和Tortoise乌龟类,继承动物类
3.两个子类重写running方法
4.本题的第3个要求涉及到线程回调。需要在动物类创建一个回调接口,创建一个回调对象
(一)创建Animal动物类
package com.xykj.rabbit_tortoise; public abstract class Animal extends Thread{ public double length=20;//比赛的长度 public abstract void runing();//抽象方法需要子类实现 //在父类重写run方法,在子类只要重写running方法就可以了 @Override public void run() { super.run(); while (length>0) { runing(); } } //在需要回调数据的地方(两个子类需要),声明一个接口 public static interface Calltoback{ public void win(); } //2.创建接口对象 public Calltoback calltoback; }
(二)创建Rabbit兔子类
package com.xykj.rabbit_tortoise; public class Rabbit extends Animal { public Rabbit() { setName("兔子");// Thread的方法,给线程赋值名字 } // 重写running方法,编写兔子的奔跑操作 @Override public void runing() { // 跑的距离 double dis = 0.5; length -= dis;//跑完后距离减少 if (length <= 0) { length = 0; System.out.println("兔子获得了胜利"); //给回调对象赋值,让乌龟不要再跑了 if (calltoback != null) { calltoback.win(); } } System.out.println("兔子跑了" + dis + "米,距离终点还有" + (int)length + "米"); if (length % 2 == 0) {// 两米休息一次 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
(三)创建Tortoise乌龟类
package com.xykj.rabbit_tortoise; public class Tortoise extends Animal { public Tortoise() { setName("乌龟");// Thread的方法,给线程赋值名字 } // 重写running方法,编写乌龟的奔跑操作 @Override public void runing() { // 跑的距离 double dis = 0.1; length -= dis; if (length <= 0) { length = 0; System.out.println("乌龟获得了胜利"); // 让兔子不要在跑了 if (calltoback != null) { calltoback.win(); } } System.out.println("乌龟跑了" + dis + "米,距离终点还有" + (int) length + "米"); try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
(四)创建一个让动物线程停止的类,这里要实现回调接口
package com.xykj.rabbit_tortoise; import com.xykj.rabbit_tortoise.Animal.Calltoback; public class LetOneStop implements Calltoback { // 动物对象 Animal an; // 获取动物对象,可以传入兔子或乌龟的实例 public LetOneStop(Animal an) { this.an = an; } //让动物的线程停止 @Override public void win() { // 线程停止 an.stop(); } }
(五)创建一个主方法调用类,
package com.xykj.rabbit_tortoise; public class MainClass { /** * 龟兔赛跑:20米 * */ public static void main(String[] args) { //实例化乌龟和兔子 Tortoise tortoise = new Tortoise(); Rabbit rabbit = new Rabbit(); //回调方法的使用,谁先调用calltoback方法,另一个就不跑了 LetOneStop letOneStop1 = new LetOneStop(tortoise); rabbit.calltoback = letOneStop1;//让兔子的回调方法里面存在乌龟对象的值,可以把乌龟stop LetOneStop letOneStop2 = new LetOneStop(rabbit); tortoise.calltoback = letOneStop2;//让乌龟的回调方法里面存在兔子对象的值,可以把兔子stop //开始跑 tortoise.start(); rabbit.start(); } }
运行结果:
可以看到结果兔子赢了。
一般来说兔子获得了胜利是在最后输出的,
但是,由于线程一直在执行所以会出现:
“兔子跑了0.5米,距离终点还有0米”还没来得及输出完,
而“兔子获得了胜利”已经输出完毕了。
五.实例四:
在一个KFC内,服务员负责生产食物,消费者负责消费食物;
当生产到一定数量可以休息一下,直到消费完食物,再马上生产,一直循环
程序涉及到的内容:
1.这设计到java模式思想:生产者消费者模式
2.要保证操作对象的统一性,即消费者和服务者都是跟同一个KFC发生关系的,KFC只能new一次
3.this.notifyAll();和 this.wait();一个是所有唤醒的意思,一个是让自己等待的意思;
比如本题中,生产者生产完毕后,先所有唤醒(包括消费者和生产者),再让所有自己(生产者)等待
这时,消费者开始消费,直到食材不够,先所有唤醒(包括消费者和生产者),再让所有自己(消费者)等待
一直执行上面的操作的循环
4.生产者和消费者都要继承Thread,才能实现多线程的启动
程序设计的步骤思路:
1.创建一个食物类Food,有存放/获取食物的名称的方法
2.创建一个KFC类,有生产食物和消费食物的方法
3.创建一个客户类Customer,继承Thread,重写run方法,在run方法里面进行消费食物操作
4.创建一个服务员类Waiter,继承Thread,重写run方法,在run方法里面进行生产食物的操作
5.创建主方法的调用类
(一)创建一个食物类Food
package com.xykj.producer_consumer; public class Food { String name=""; //通过构造方法传入食物的名字 public Food(String name) { this.name=name; } //get、set 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } }
(二)创建一个KFC类
package com.xykj.producer_consumer; import java.util.ArrayList; import java.util.List; public class KFC { //食物的种类 String[] names = { "薯条", "烧板", "鸡翅", "可乐" }; //生产的最大值,到达后可以休息 static final int Max = 20; //存放食物的集合 List foods = new ArrayList (); // 生产食物的方法 public void prod(int index) { synchronized (this) { // 如果食物数量大于20 while (foods.size() > Max) { System.out.println("食材够了"); this.notifyAll();//这个唤醒是针对生产者和消费者,有all try { String name=Thread.currentThread().getName(); this.wait();//这个唤醒是针对生产者,没有all System.out.println("生产者:"+name); } catch (InterruptedException e) { e.printStackTrace(); } } // 开始生产食物食物//有一点要注意的 System.out.println("开始生产食物"); for (int i = 0; i < index; i++) { Food food = new Food(names[(int) (Math.random() * 4)]); foods.add(food); System.out.println("生产了" + food.getName() + foods.size()); } } } // 消费食物的方法 public void consu(int index) { synchronized (this) { while (foods.size() < index) { System.out.println("食材不够了"); this.notifyAll();//这个唤醒是针对生产者和消费者,有all try { String name=Thread.currentThread().getName(); this.wait();//这个唤醒是针对消费者,没有all System.out.println("消费者:"+name); } catch (InterruptedException e) { e.printStackTrace(); } } // 足够消费 System.out.println("开始消费"); for (int i = 0; i < index; i++) { Food food = foods.remove(foods.size() - 1); System.out.println("消费了一个" + food.getName() + foods.size()); } } } }
(三)创建一个客户类Customer
package com.xykj.producer_consumer; public class Customers extends Thread{ KFC kfc; //KFC要传入,保证每一个服务员和用户在同一个KFC对象内 public Customers(KFC kfc) { this.kfc=kfc; } @Override public void run() { int size=(int)(Math.random()*5);//每次要消费的食物的数量 while (true) { kfc.consu(size);//在消费的方法里面传入参数 } } }
(四)创建一个服务员类Waiter
package com.xykj.producer_consumer; public class Waiter extends Thread{ KFC kfc; //KFC要传入,保证每一个服务员和用户在同一个KFC对象内 public Waiter(KFC kfc) { this.kfc=kfc; } @Override public void run() { int size=(int)(Math.random()*5)+5;//每次生产的数量 while (true) { kfc.prod(size);//传入每次生产的数量 } } }
(五)创建主方法的调用类
package com.xykj.producer_consumer; public class MainClass { /** * 生产者消费者模式 * * */ public static void main(String[] args) { // 只实例化一个KFC对象,保证每一个服务员和用户在同一个KFC对象内 KFC kfc = new KFC(); //实例化4个客户对象 Customers c1 = new Customers(kfc); Customers c2 = new Customers(kfc); Customers c3 = new Customers(kfc); Customers c4 = new Customers(kfc); //实例化3个服务员对象 Waiter waiter1 = new Waiter(kfc); Waiter waiter2 = new Waiter(kfc); Waiter waiter3 = new Waiter(kfc); //让所有的对象的线程都开始工作 waiter1.start(); waiter2.start(); waiter3.start(); c1.start(); c2.start(); c3.start(); c4.start(); } }
六.示例五:设计四个线程对象对同一个数据进行操作,
两个线程执行减操作,两个线程执行加操作。
程序分析:1.创建一个ThreadAddSub类继承Thread,重写run方法
2.在run方法里面实现加和减的操作,每次操作后睡眠1秒
3.创建主方法调用类
(一)创建一个ThreadAddSub类
package com.xykj.add; public class ThreadAddSub extends Thread { //判断要进行的操作 boolean operate = true; //要操作的数 static int sum = 0; // 把操作运算通过构造方法传进来 public ThreadAddSub(boolean operate) { super(); this.operate = operate; } @Override public void run() { super.run(); while (true) { if (operate) { sum+=5; System.out.println("加后,sum="+sum); } else { sum-=4; System.out.println("减后,sum="+sum); } try { sleep(500);// 睡眠0.5秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
(二)创建主方法调用类
emptypackage com.xykj.add; public class MainClass { /** * (线程同步) * */ public static void main(String[] args) { //创建一个存放ThreadAddSub对象的数组 ThreadAddSub[] tSub=new ThreadAddSub[4]; for (int i = 0; i < tSub.length; i++) { //把实例化ThreadAddSub对象赋值到数组内 //第一三个是true,二四个是false tSub[i]=new ThreadAddSub(i%2==0?true:false); //让线程开始工作 tSub[i].start(); } } }
线程示例总结:
代码块锁是一个防止数据发生错误的一个重要手段。
对象的统一性是非常重要的,这要想到对象的传入问题,
要操作的对象只能new一次,其他的操作都是对这个传入的对象进行的,
才能保证数据一致性,完整性和正确性。
练习题目:
1. (多线程)代码实现火车站4个卖票窗口同时买票的场景,输出示例:
窗口1卖票
窗口2卖票
窗口1卖票
...
2. (线程同步)代码实现火车站4个窗口同时卖100张票的代码逻辑,同一个窗口不能卖同一
张张票。
3. (线程通信)小明打算去提款机上取钱,发现卡上没钱,这时候他告知妈妈去存钱,妈妈
存了钱了,告知小明存好了可以取钱了。(PS:小明分多次取钱,每次取100,当发现钱不够
100,就等待妈妈存钱,小明他妈每次存2000,当发现钱小于100就存钱,就存钱,并且
通知小明去取钱,当大于100就等待小明钱不够是再存)
4. (线程同步)设计四个线程对象对同一个数据进行操作,两个线程执行减操作,两个线程执行
加操作。
5. (线程通信)制作两个线程对象,要求用同步块的方式使第一个线程运行2次,然后将自己
阻塞起来,唤醒第二个线程,第二个线程再运行2次,然后将自己阻塞起来,唤醒第一个线
程……两个线程交替执行。
6. (线程同步)设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。
7. (线程通信)子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着
再回到主线程又循环100,如此循环50次。
-
Java多线程编程实战指南(核心篇)
2018-07-17 23:04:30Java多线程编程实战指南(核心篇) 高清pdf带目录 随着现代处理器的生产工艺从提升处理器主频频率转向多核化,即在一块芯片上集成多个处理器内核(Core),多核处理器(Multicore Processor)离我们越来越近了――如今... -
java多线程Runnable实例
2014-01-24 21:38:58java多线程runnable实例,经过测试的,可以直接运行 -
Java多线程回调方法实例解析
2020-08-28 21:56:38主要介绍了Java多线程回调方法实例解析,具有一定参考价值,需要的朋友可以了解下。 -
java多线程实例
2011-12-07 20:22:28java多线程实例 -
Android 文件下载功能Java多线程下载功能的例子实例源码.zip
2022-05-14 14:33:06Android 文件下载功能Java多线程下载功能的例子实例源码 -
讲给女朋友听的java多线程(2万字深入理解多线程,有实例代码辅助理解)
2021-01-20 02:43:34读完本文大概需要30分钟左右,本文有大量的实例代码和典型例题帮助大家理解java多线程。虽然多线程比较难以理解,仅靠一篇文章恐怕搞不定。但读完本文最起码能让你搞懂java多线程的70%以上,至于熟练运用,还是得... -
java 多线程并发实例
2014-03-11 13:30:52代码里面包含一个并发4个线程同时运行 全部开始 全部停止 单个停止还有点问题。 还有生产者消费者 里面的里面能帮助你理解多线程的运用! -
JAVA实现多线程的两种方法实例分享
2020-09-05 02:35:51介绍了JAVA实现多线程的两种方法实例分享,有需要的朋友可以参考一下 -
用Java多线程写管道过滤器
2019-04-24 19:18:15使用Java多线程实现管道过滤器,本文档写了六个线程,五个管道,内容很简单,如果想要复杂功能请手动添加。 -
java多线程并发编程例子
2012-03-08 12:11:38关于java.util.concurrent多线程核心包内各种线程资源的使用场景例子 -
java多线程示例
2019-08-04 01:31:54NULL 博文链接:https://892530175-qq-com.iteye.com/blog/2030207 -
JAVA多线程的实例
2013-04-17 23:02:12描述java多线程的例子,可供进一步学习多线程的人参考