StringBuffer
增加
append(boolean b) 可以添加任意类型的数据到容器中
insert(int offset,boolean b) 指定插入的索引值,插入对应的内容 (offset可以理解为想插入的东西在插入后的索引值为多少,也就是想插入地方右区间的索引值)
删除
delete(int start ,int end) 根据指定的开始与结束的索引值删除对应的内容
deleteCharAt(int index) 根据指定的索引值删除一个字符
修改
replace(int start,int end,String str) 根据指定的开始与结束索引值替代成指定的内容
reverse() 翻转字符串缓冲类的内容。 abc————>cba
setCharAt(int index,char ch) 把指定索引值的字符替换指定的字符。
subString(int start,int end) 根据指定的索引值截取子串
ensureCapacity(int minimumCapacity) 指定StringBuffer内部的字符数组长度的。
查看
indexOf(String str,int fromIndex) 查找指定字符串第一次出现的索引值,并且指定开始查找的位置
capacity() 查看当前字符数组的长度
charAt(int index) 根据指定的索引值查找字符
lastIndexOf(String str) 查找指定字符最后一次出现的索引值
length() 存储的字符个数
toString() 把字符串缓冲类的内容转换成字符串返回
判断
public static void main(String[] args) { // TODO Auto-generated method stub //先使用StringBuffer无参的构造函数创建一个字符串缓冲类。 StringBuffer sb=new StringBuffer(); sb.append("abc"); System.out.println("字符串缓冲类的内容:"+sb); /* 添加 sb.append(true); sb.append(3.14f); System.out.println("字符串缓冲类的内容:"+sb); */ //插入 sb.insert(2, "小明"); System.out.println("字符串缓冲类的内容:"+sb); //删除 sb.delete(2, 4); //删除的时候也是包头不包尾的 System.out.println("字符串缓冲类的内容:"+sb); sb.insert(2, "小明"); sb.deleteCharAt(3); System.out.println("字符串缓冲类的内容:"+sb); } 结果: 字符串缓冲类的内容:abc 字符串缓冲类的内容:ab小明c 字符串缓冲类的内容:abc 字符串缓冲类的内容:ab小c
public static void main(String[] args) { // TODO Auto-generated method stub //先使用StringBuffer无参的构造函数创建一个字符串缓冲类。 StringBuffer sb=new StringBuffer(); sb.append("abc"); System.out.println("字符串缓冲类的内容:"+sb); /* 添加 sb.append(true); sb.append(3.14f); System.out.println("字符串缓冲类的内容:"+sb); */ //插入 sb.insert(2, "小明"); System.out.println("字符串缓冲类的内容:"+sb); //删除 /*sb.delete(2, 4); //删除的时候也是包头不包尾的 System.out.println("字符串缓冲类的内容:"+sb); sb.insert(2, "小明"); sb.deleteCharAt(3); System.out.println("字符串缓冲类的内容:"+sb); */ //修改 sb.replace(2, 4, "陈小狗"); System.out.println("字符串缓冲类的内容:"+sb); sb.reverse(); System.out.println("字符串缓冲类的内容:"+sb); sb.reverse(); sb.setCharAt(3, '红'); System.out.println("字符串缓冲类的内容:"+sb); System.out.println("字符串的内容:"+sb.substring(2, 4)); sb.ensureCapacity(20); } 结果: 字符串缓冲类的内容:abc 字符串缓冲类的内容:ab小明c 字符串缓冲类的内容:ab陈小狗c 字符串缓冲类的内容:c狗小陈ba 字符串缓冲类的内容:ab陈红狗c 字符串的内容:陈红
public static void main(String[] args) { // TODO Auto-generated method stub StringBuffer sb=new StringBuffer(); sb.append("abcjavaabc"); //查找 System.out.println("索引值为:"+sb.indexOf("abc", 0)); System.out.println("索引值为:"+sb.indexOf("abc", 3)); //sb.ensureCapacity(20); sb.append("javajava"); System.out.println("查看字符数组的长度:"+sb.capacity()); System.out.println("存储字符的个数:"+sb.length()); System.out.println("根据指定的索引值查找字符:"+sb.charAt(2)); System.out.println("转换成字符串输出:"+sb.toString()); } 结果: 索引值为:0 索引值为:7 查看字符数组的长度:34 存储字符的个数:18 根据指定的索引值查找字符:c 转换成字符串输出:abcjavaabcjavajava
StringBuffer与StringBuilder的相同处与不同处:
相同点:
1.两个类都是字符串缓冲类
2.两个类的方法与实现都是一致的
不同点:
1.StringBuffer是线程安全的,操作效率低,StringBuilder是线程非安全的,操作效率高
2.StringBuffer是jdk1.0出现的,StringBuilder是jdk1.5的时候出现的
推荐使用:StringBuilder,因为操作效率高。
System类 系统类 主要用于获取系统的属性数据
System类常用的方法:
arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
import java.util.*; public class Demo1 { /* arraycopy(Object src, int srcPos, Object dest, int destPos, int length) src - 源数组。 srcPos - 源数组中的起始位置。 dest - 目标数组。 destPos - 目标数据中的起始位置。 length - 要复制的数组元素的数量。 * */ public static void main(String[] args) { // TODO Auto-generated method stub int[] srcArr= {10,12,14,16,19}; //把srcArr数组的元素拷贝到destArr数组中 int[] destArr=new int[4]; System.arraycopy(srcArr, 1, destArr, 0, 4); System.out.println("目标数组的元素:"+Arrays.toString(destArr)); } } 结果: 目标数组的元素:[12, 14, 16, 19]
currentTimeMillis() 获取当前系统时间 重点
System.out.println("当前的系统时间是:"+System.currentTimeMillis());
结果:
当前的系统时间是:1549623586306
exit(int status) 退出jvm 如果参数是0表示正常退出jvm,非0表示异常退出jvm 0或者非0的数据都可以退出jvm,对于用户而言没有任何区别 一般
public static void main(String[] args) { // TODO Auto-generated method stub int[] srcArr= {10,12,14,16,19}; //把srcArr数组的元素拷贝到destArr数组中 int[] destArr=new int[4]; System.arraycopy(srcArr, 1, destArr, 0, 4); System.exit(0); //jvm退出... 0或者非0的数据都可以退出jvm。对于用户而言没有任何区别 System.out.println("目标数组的元素:"+Arrays.toString(destArr)); System.out.println("当前的系统时间是:"+System.currentTimeMillis()); } 结果:
(编程习惯:try块中一般传0,catch中一般传非0,其实传什么也可以达到效果)👆
gc() 建议jvm赶快启动垃圾回收器回收垃圾
finalize() 如果一个对象被回收器回收的时候,会先调用对象的finalize()方法 我们如果想要看到System.gc()的结果,可以重写finalize方法
import java.util.*; public class Demo1 { /* gc() 建议jvm赶快启动垃圾回收器回收垃圾 finalize() 如果一个对象被回收器回收的时候,会先调用对象的finalize()方法 * */ public static void main(String[] args) { // TODO Auto-generated method stub int[] srcArr= {10,12,14,16,19}; //把srcArr数组的元素拷贝到destArr数组中 int[] destArr=new int[4]; System.arraycopy(srcArr, 1, destArr, 0, 4); //System.exit(0); //jvm退出... 0或者非0的数据都可以退出jvm。对于用户而言没有任何区别 System.out.println("目标数组的元素:"+Arrays.toString(destArr)); System.out.println("当前的系统时间是:"+System.currentTimeMillis()); for (int i=0;i<4;i++) { new Person("狗娃"+i); System.gc(); //建议赶快启动垃圾回收器回收 } } } class Person{ String name; public Person(String name) { this.name=name; } @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub super.finalize(); System.out.println(this.name+"被回收了"); } } 结果: 目标数组的元素:[12, 14, 16, 19] 当前的系统时间是:1549627330079 狗娃0被回收了 狗娃2被回收了 狗娃3被回收了 狗娃1被回收了
getenv(String name) 根据环境变量的名字获取环境变量
System.out.println("环境变量:"+System.getenv("JAVA_HOME"));
结果:
环境变量:D:\JAVA\jdk\
getProperties() 获取当前的系统所有的属性
getProperty(String key) 根据系统的属性名获取对应的属性值(获取一个属性)
//Properties properties=System.getProperties(); //properties.list(System.out); String value=System.getProperty("os.name"); System.out.println("当前系统:"+value); 结果: 当前系统:Windows 10
Runtime类 该类主要代表了应用程序的运行环境
getRuntime() 返回当前应用程序的运行环境对象
exec(String command) 根据指定的路径执行对应的可执行文件
import java.io.IOException; /* getRuntime() 返回当前应用程序的运行环境对象 exec(String command) 根据指定的路径执行对应的可执行文件 */ public class Demo2 { public static void main(String[] args) throws IOException, InterruptedException { // TODO Auto-generated method stub Runtime runtime=Runtime.getRuntime(); Process process=runtime.exec("C:\\Windows\\notepad.exe"); Thread.sleep(3000); //让当前程序停止3秒 process.destroy(); } } 结果: 打开记事本,三秒之后关闭
freeMemory() 返回jvm空闲的内存,以字节为单位。
maxMemory() 返回 Java 虚拟机试图使用的最大内存量
totalMemory() 返回 Java 虚拟机中的内存总量
public static void main(String[] args) throws IOException, InterruptedException { // TODO Auto-generated method stub Runtime runtime=Runtime.getRuntime(); System.out.println("jvm空闲的内存量:"+runtime.freeMemory()); System.out.println("Java 虚拟机试图使用的最大内存量"+runtime.maxMemory()); System.out.println("Java 虚拟机的内存总量"+runtime.totalMemory()); } 结果: jvm空闲的内存量:124484880 Java 虚拟机试图使用的最大内存量2000683008 Java 虚拟机的内存总量125829120
日期类Date
使用Calendar.getInstance()获取Calendar对象
import java.util.Calendar; import java.util.Date; public class Demo3 { public static void main(String[] args) { // TODO Auto-generated method stub //Date date=new Date(); //获取当前的系统时间 //System.out.println("年份:"+date.getYear());//已经过时,不推荐使用 Calendar calendar=Calendar.getInstance(); //获取当前的系统时间 System.out.println("年:"+calendar.get(Calendar.YEAR)); System.out.println("月:"+(calendar.get(Calendar.MONTH)+1)); System.out.println("日:"+calendar.get(Calendar.DATE)); System.out.println("时:"+calendar.get(Calendar.HOUR_OF_DAY)); System.out.println("分:"+calendar.get(Calendar.MINUTE)); System.out.println("秒:"+calendar.get(Calendar.SECOND)); } } 结果: 年:2019 月:2 日:12 时:9 分:11 秒:20
日期格式化类SimpleDateFormat
作用:
- 可以把日期转换成指定格式的字符串 format()
- 可以把一个字符串转换成对应的日期 parse()
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class Demo3 { public static void main(String[] args) throws ParseException { // TODO Auto-generated method stub //Date date=new Date(); //获取当前的系统时间 //System.out.println("年份:"+date.getYear()); //显示当前的系统时间:2019年2月12日 xx时xx分xx秒 /*日期格式化类SimpleDateFormat * 作用1:可以把日期转换成指定格式的字符串 format() * 作用2:可以把一个字符串转换成对应的日期 parse() * */ Date date=new Date(); SimpleDateFormat dateFormat=new SimpleDateFormat(); //使用了默认的格式创建了一个日期格式化对象 String time=dateFormat.format(date); System.out.println("当前的系统时间:"+time); SimpleDateFormat dateFormat2=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); time=dateFormat2.format(date); System.out.println("当前的系统时间:"+time); String birthday="1999年11月1日 19:27:35"; Date date2=dateFormat2.parse(birthday); //注意:指定的字符串格式必须要与SimpleDateFormat的模式要一致,空格个数也要考虑 System.out.println(date2); } } 结果: 当前的系统时间:2019/2/12 上午10:55 当前的系统时间:2019年02月12日 10:55:31 Mon Nov 01 19:27:35 CST 1999
Math类(数学类) 主要是提供了很多的数学公式
abs(double a) 获取绝对值
ceil(double a) 向上取整
floor(double a) 向下取整
round(float a) 四舍五入
random() 产生一个随机数,返回带正号的 double
值,该值大于等于 0.0
且小于 1.0
public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("绝对值:"+Math.abs(-3)); System.out.println("向上取整:"+Math.ceil(3.14)); System.out.println("向上取整:"+Math.ceil(-3.14)); System.out.println("向下取整:"+Math.floor(3.14)); System.out.println("向下取整:"+Math.floor(-3.14)); System.out.println("四舍五入:"+Math.round(3.54)); System.out.println("四舍五入:"+Math.round(3.49)); System.out.println("随机数:"+Math.random()); } 结果: 绝对值:3 向上取整:4.0 向上取整:-3.0 向下取整:3.0 向下取整:-4.0 四舍五入:4 四舍五入:3 随机数:0.6458472762144221
随机数类:(Random类)
import java.util.Random; /* Random类 随机数类 */ public class Demo5 { public static void main(String[] args) { // TODO Auto-generated method stub Random random=new Random(); int randomNum=random.nextInt(10)+1;//产生的随机数是1-10之间的 System.out.println("随机数:"+randomNum); } } 结果: 随机数:2
注意:随机数产生的区间是根据所给区间 左闭右开 的,也就是包含左边的临界值,不包含右边的
需求:编写一个函数随机产生四位的验证码。
import java.util.Random; /* Random类 随机数类 */ public class Demo5 { public static void main(String[] args) { // TODO Auto-generated method stub /*Random random=new Random(); int randomNum=random.nextInt(10)+1;//产生的随机数是1-10之间的 System.out.println("随机数:"+randomNum); */ char[] arr= {'中','国','传','a','Q','f','B'}; StringBuilder sb=new StringBuilder(); Random random=new Random(); //需要四个随机数,通过随机数获取字符数组中的字符 for (int i=0;i<4;i++) { int index=random.nextInt(arr.length);//产生的随机数必须是数组的索引值范围之内的 sb.append(arr[index]); } //String str=sb.toString(); //System.out.println("验证码为:"+str); System.out.println("验证码为:"+sb); } } 结果: 验证码为:QB传B
进程:正在执行的程序称作为一个进程,进程负责了内存空间的划分
Windows号称是多任务的操作系统,那么Windows是同时运行多个应用程序吗?
从宏观的角度:Windows确实是在同时运行多个应用程序
从微观的角度:cpu是做了一个快速切换执行的动作,由于速度太快,所以我们感觉不到在切换而已。
线程:线程在一个进程中负责了代码的执行,就是进程中一个执行路径
多线程:在一个进程中有多个线程同时在执行不同的任务
疑问:我们以前没有学过线程,而线程负责代码的执行,为什么代码可以执行呢?
任何一个Java程序,jvm在运行的时候都会创建一个main线程执行main方法中的所有代码
一个Java应用程序至少有两个线程,一个是主线程负责了main方法的执行,一个是垃圾回收器线程,负责了回收垃圾
多线程的好处:
- 解决了一个进程能同时执行多个任务的问题
- 提高了资源的利用率
多线程的弊端:
- 增加了cpu的负担
- 降低了一个进程中线程的执行概率
- 引发了线程安全问题
- 出现了死锁现象
如何创建多线程:
创建线程的方式:
方式一:
- 自定义一个类继承Thread类
- 重写Thread类的run方法,把自定义线程的任务写在run方法中
- 重写run方法的目的是什么呢?
- 每个线程都有自己的任务代码,jvm创建的主线程的任务代码就是main方法中的所有代码,自定义线程的任务代码就写在run方法中,自定义线程负责了run方法中的代码
- 重写run方法的目的是什么呢?
- 调用Thread的子类对象,并且调用start方法开启线程
- 注意:一个线程一旦开启,那么线程就会执行run方法中的代码,run方法千万不能直接调用,直接调用run方法就相当于调用了一个普通的方法而已
public class Demo6 extends Thread{ @Override //把自定义线程的任务写在run方法中 public void run() { // TODO Auto-generated method stub for (int i=0;i<10;i++) { System.out.println("自定义线程:"+i); } super.run(); } public static void main(String[] args) { // TODO Auto-generated method stub //创建了自定义的线程对象 Demo6 d=new Demo6(); //调用start方法启动线程 d.start(); for (int i=0;i<10;i++) { System.out.println("main线程:"+i); } } } 结果: main线程:0 自定义线程:0 main线程:1 自定义线程:1 main线程:2 自定义线程:2 自定义线程:3 自定义线程:4 main线程:3 main线程:4 main线程:5 main线程:6 main线程:7 main线程:8 main线程:9 自定义线程:5 自定义线程:6 自定义线程:7 自定义线程:8 自定义线程:9
需求:模拟QQ视频与聊天同时进行
public class Demo7 { public static void main(String[] args) { // TODO Auto-generated method stub TalkThread talkThread=new TalkThread(); talkThread.start(); VideoThraead videoThread=new VideoThraead(); videoThread.start(); } } class VideoThraead extends Thread{ @Override public void run() { // TODO Auto-generated method stub int i=0; while (i++<50) { System.out.println("视频"); } super.run(); } } class TalkThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub int i=0; while (i++<50) { System.out.println("聊天"); } super.run(); } } 结果: 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 视频 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天 聊天
线程的生命周期图
线程常用的方法:
Thread(String name) 初始化线程的名字
getName() 返回线程的名字
setName(String name) 设置线程对象名
sleep() 线程睡眠指定的毫秒数。 静态的方法,哪个线程执行了sleep方法代码那么就是哪个线程睡眠
getPriority() 返回当前线程对象的优先级 默认线程的优先级是5,优先级的数字越大,优先级越高,优先级的范围是1~10,注意:线程的优先级高,并不意味着就一定先执行,只是抢到cpu的概率更大了
setPriority(int newPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
currentThread() 返回CPU正在执行的线程的对象 该方法是一个静态的方法,哪个线程执行了currentThread()代码就返回哪个线程的对象
public class Demo8 extends Thread{ public Demo8() {} public Demo8(String name) { super(name); //调用了Thread类的一个参数的构造方法 } @Override public void run() { // TODO Auto-generated method stub for (int i=0;i<10;i++) { System.out.println(this.getName()+i); try { Thread.sleep(100);//这里只能捕获,不能抛出,因为其父类Thread的run方法并没有抛出异常,子类重写时抛出的异常类型要小于或等于父类抛出的异常 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } super.run(); } public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub //创建了一个线程对象 Demo8 d=new Demo8("狗娃"); d.sleep(1000); d.setName("铁蛋");//d.setName("铁蛋");//设置线程的名字 System.out.println("线程的名字:"+d.getName()); d.start(); } } 结果: 线程的名字:铁蛋 铁蛋0 铁蛋1 铁蛋2 铁蛋3 铁蛋4 铁蛋5 铁蛋6 铁蛋7 铁蛋8 铁蛋9 先出来第一二行,然后每隔0.1秒出来一行
public class Demo8 extends Thread{ public Demo8() {} public Demo8(String name) { super(name); //调用了Thread类的一个参数的构造方法 } @Override public void run() { // TODO Auto-generated method stub System.out.println("this:"+this); System.out.println("当前线程对象:"+Thread.currentThread()); for (int i=0;i<10;i++) { System.out.println(this.getName()+i); System.out.println(Thread.currentThread().getName()+i); /*try { Thread.sleep(100);//这里只能捕获,不能抛出,因为其父类Thread的run方法并没有抛出异常,子类重写时抛出的异常类型要小于或等于父类抛出的异常 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ } } public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub //创建了一个线程对象 Demo8 d=new Demo8("狗娃"); System.out.println("自定义线程的优先级:"+d.getPriority()); System.out.println("主线程的优先级:"+Thread.currentThread().getPriority()); //d.sleep(1000); d.setName("铁蛋");//d.setName("铁蛋");//设置线程的名字 System.out.println("线程的名字:"+d.getName()); d.setPriority(10); d.start(); for (int i=0;i<10;i++) { System.out.println(Thread.currentThread().getName()+i); } Thread mainThread=Thread.currentThread(); System.out.println("主线程的名字:"+mainThread.getName()); } } 结果: 自定义线程的优先级:5 主线程的优先级:5 线程的名字:铁蛋 main0 main1 main2 main3 main4 this:Thread[铁蛋,10,main] 当前线程对象:Thread[铁蛋,10,main] 铁蛋0 铁蛋0 铁蛋1 铁蛋1 铁蛋2 铁蛋2 铁蛋3 铁蛋3 铁蛋4 铁蛋4 铁蛋5 铁蛋5 铁蛋6 铁蛋6 铁蛋7 铁蛋7 铁蛋8 铁蛋8 铁蛋9 铁蛋9 main5 main6 main7 main8 main9 主线程的名字:main
需求:模拟三个窗口在售50张票
public class Demo9 { public static void main(String[] args) { // TODO Auto-generated method stub //创建三个线程对象,模拟三个窗口 SaleTicket thread1=new SaleTicket("窗口1"); SaleTicket thread2=new SaleTicket("窗口2"); SaleTicket thread3=new SaleTicket("窗口3"); //开启线程售票 thread1.start(); thread2.start(); thread3.start(); } } class SaleTicket extends Thread{ static int num=50;//票数 不加static的时候是非静态的成员变量,非静态的成员变量数据是在每个对象中都会维护一份数据的。所以会出现三个窗口卖了150张票的情况 public SaleTicket() {} public SaleTicket(String name) { // TODO Auto-generated constructor stub super(name); } @Override public void run() { // TODO Auto-generated method stub while (true) { if (num>0) { System.out.println(Thread.currentThread().getName()+"售出了第"+num+"号票"); num--; } else { System.out.println("售罄了..."); break; } } //super.run(); } }
问题1:50张票售了150次
可以向票数上加static使其成为静态成员变量来解决
问题2:出现了线程安全问题
出现线程安全问题的根本原因:
- 存在两个或者两个以上的线程对象,而且线程之间共享着一个资源
- 有多个语句操作了共享资源
线程安全问题的解决方案:sun提供了线程同步机制让我们解决这类问题的
Java线程同步机制的方式:
方式一:同步代码块
同步代码块的格式:
synchronized(锁对象){
需要被同步的代码...
}
同步代码块要注意的事项:
- 任意的一个对象都可以作为锁对象
- 在同步代码块中调用了sleep方法并不是释放锁对象的,也就是不会改变锁对象的开关状态,就像你睡觉锁着门,别人也进不来
- 只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率的
- 多线程操作的锁对象必须是唯一共享的,否则无效。
public class Demo9 { public static void main(String[] args) { // TODO Auto-generated method stub //创建三个线程对象,模拟三个窗口 SaleTicket thread1=new SaleTicket("窗口1"); SaleTicket thread2=new SaleTicket("窗口2"); SaleTicket thread3=new SaleTicket("窗口3"); //开启线程售票 thread1.start(); thread2.start(); thread3.start(); } } class SaleTicket extends Thread{ static int num=50;//票数 不加static的时候是非静态的成员变量,非静态的成员变量数据是在每个对象中都会维护一份数据的。所以会出现三个窗口卖了150张票的情况 static Object o=new Object();//为什么要加static呢 public SaleTicket() {} public SaleTicket(String name) { // TODO Auto-generated constructor stub super(name); } @Override public void run() { // TODO Auto-generated method stub while (true) { //同步代码块 synchronized (o) {//可以使用字符串 "锁" ,这是最简单的锁对象,因为它在字符串常量池中 if (num>0) { System.out.println(Thread.currentThread().getName()+"售出了第"+num+"号票"); num--; } else { System.out.println("售罄了..."); break; } } } //super.run(); } } 结果: 窗口1售出了第50号票 窗口1售出了第49号票 窗口1售出了第48号票 窗口1售出了第47号票 窗口3售出了第46号票 窗口3售出了第45号票 窗口3售出了第44号票 窗口3售出了第43号票 窗口3售出了第42号票 窗口3售出了第41号票 窗口3售出了第40号票 窗口3售出了第39号票 窗口3售出了第38号票 窗口2售出了第37号票 窗口2售出了第36号票 窗口2售出了第35号票 窗口2售出了第34号票 窗口2售出了第33号票 窗口2售出了第32号票 窗口2售出了第31号票 窗口2售出了第30号票 窗口2售出了第29号票 窗口2售出了第28号票 窗口2售出了第27号票 窗口2售出了第26号票 窗口2售出了第25号票 窗口2售出了第24号票 窗口2售出了第23号票 窗口2售出了第22号票 窗口2售出了第21号票 窗口2售出了第20号票 窗口2售出了第19号票 窗口2售出了第18号票 窗口2售出了第17号票 窗口2售出了第16号票 窗口2售出了第15号票 窗口2售出了第14号票 窗口2售出了第13号票 窗口2售出了第12号票 窗口2售出了第11号票 窗口2售出了第10号票 窗口2售出了第9号票 窗口2售出了第8号票 窗口2售出了第7号票 窗口2售出了第6号票 窗口2售出了第5号票 窗口2售出了第4号票 窗口2售出了第3号票 窗口2售出了第2号票 窗口2售出了第1号票 售罄了... 售罄了... 售罄了...
方式二:同步函数