电脑中会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。例如图中的微信、酷狗音乐、电脑管家等等。

进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。
补充:Java中有默认有几个线程?两个:main、gc
那什么又是多线程呢?
提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。
串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。

并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。

了解完这两个概念之后,我们再来说什么是多线程,举个例子,比如我们打开联想电脑管家,电脑管家本身是一个程序,也可以说就是一个进程,它里面包括很多功能,电脑加速、安全防护、空间清理等等功能,如果对于单线程来说,无论我们想要电脑加速,还是空间清理,那么必须得一件事一件事的做,做完其中一件事再做下一件事,有一个执行顺序。但如果是多线程的话,我们可以在清理垃圾的同时进行电脑加速,还可以病毒查杀等等其他操作,这个就是在严格意义上的同一时刻发生的,没有先后顺序。
Java 提供了三种创建线程的方法:
1.继承Thread类
/**
* @ClassName ThreadDemo
* @Description TODO 线程创建的第一种方式:继承Thread类
* @Author ZhangHao
* @Date 2022/12/10 11:45
* @Version: 1.0
*/
public class ThreadDemo extends Thread{
@Override
public void run() {
//新线程入口点
for (int i = 0; i < 100; i++) {
System.out.println("我在玩手机:"+i);
}
}
//主线程
public static void main(String[] args) {
//创建线程对象
ThreadDemo demo = new ThreadDemo();
demo.start();//启动线程
for (int i = 0; i < 1000; i++) {
System.out.println("我在吃饭:"+i);
}
//主线程和多线程并行交替执行
//总结:线程开启不一定立即执行,由cpu调度执行
}
}
写一个小小的案例:使用多线程实现网图下载
/**
* @ClassName ImageDownload
* @Description TODO 网图下载
* @Author ZhangHao
* @Date 2022/12/10 12:55
* @Version: 1.0
*/
public class ImageDownload extends Thread{
private String url;//网图下载地址
private String name;//网图名称
public ImageDownload(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名:"+name);
}
public static void main(String[] args) {
ImageDownload d1 = new ImageDownload("https://cn.bing.com/images/search?view=detailV2&ccid=64mezA1F&id=0567ED050842B109CEFE6D7C2E235E6513915D00&thid=OIP.64mezA1F6eYavcDWrgjHQgHaEK&mediaurl=https%3a%2f%2fimages.hdqwalls.com%2fwallpapers%2fcute-kitten-4k-im.jpg&exph=2160&expw=3840&q=Cat+Wallpaper+4K&simid=608031326407324483&FORM=IRPRST&ck=5E947A96CD5B48E39B116D48F58466AB&selectedIndex=12&ajaxhist=0&ajaxserp=0", "cat1.jpg");
ImageDownload d2 = new ImageDownload("https://cn.bing.com/images/search?view=detailV2&ccid=qXtg4Nx0&id=A80C30163A6B55D16D61F27E632239424517705F&thid=OIP.qXtg4Nx0BUoeUP53fz_HKgHaFI&mediaurl=https%3a%2f%2fimages8.alphacoders.com%2f856%2f856433.jpg&exph=2658&expw=3840&q=Cat+Wallpaper+4K&simid=608046255722156270&FORM=IRPRST&ck=986D5F99CF8474477F4A1F2DB2850C9D&selectedIndex=25&ajaxhist=0&ajaxserp=0", "cat2.jpg");
ImageDownload d3 = new ImageDownload("https://cn.bing.com/images/search?view=detailV2&ccid=kvYsfUHA&id=6311D8D1DC87AA4B69783A97020038B03827134D&thid=OIP.kvYsfUHAAQlEVW3Z3_EEWwHaEK&mediaurl=https%3a%2f%2fwallpapershome.com%2fimages%2fpages%2fpic_h%2f19418.jpg&exph=1080&expw=1920&q=Cat+Wallpaper+4K&simid=608016886736366855&FORM=IRPRST&ck=37C2818B80D19766E7A91B5BB7A060D6&selectedIndex=159&ajaxhist=0&ajaxserp=0", "cat3.jpg");
d1.start();
d2.start();
d3.start();
//每次执行结果有可能不一样,再次证明线程之间是由cpu调度执行
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
}
}
}
2. 实现Runnable接口
/**
* @ClassName RunnableDemo
* @Description TODO 线程创建的第二种方式:实现Runnable接口 (推荐使用)
* @Author ZhangHao
* @Date 2022/12/10 15:07
* @Version: 1.0
*/
//模拟抢火车票
public class RunnableDemo implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run() {
while (ticketNums > 0){
try {
//让线程睡眠一会
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->抢到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
RunnableDemo demo = new RunnableDemo();
//创建线程对象,通过线程对象开启线程(使用的代理模式)
//Thread thread = new Thread(demo,"老王");
//thread.start();
//简写:new Thread(demo).start();
new Thread(demo,"老王").start();
new Thread(demo,"小张").start();
new Thread(demo,"黄牛党").start();
//发现问题:多个线程操作同一个资源时,线程不安全,数据紊乱。(线程并发)
}
}
再来一个小小的案例:模拟《龟兔赛跑》首先得有一个赛道,兔子天生跑得快,但是兔子跑一段路就偷懒睡觉,乌龟在不停的跑,最终乌龟取得胜利!
/**
* @ClassName Race
* @Description TODO 模拟龟兔赛跑
* @Author ZhangHao
* @Date 2022/12/11 9:25
* @Version: 1.0
*/
public class Race implements Runnable {
private static String winner;//胜利者
@Override
public void run() {
//设置赛道
for (int i = 1; i <= 100; i++) {
//让兔子跑得快一点
if (Thread.currentThread().getName().equals("兔子")) {
i += 4;
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
if (i % 4 == 0) {
try {
//模拟兔子跑一段路就睡觉
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
}
//判断游戏是否结束
boolean flag = gameOver(i);
if (flag) {
break;
}
}
}
//判断游戏是否结束
private boolean gameOver(int steps) {
if (winner != null) {
return true;
}
{
if (steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "乌龟").start();
new Thread(race, "兔子").start();
}
}
3. 实现Callable接口
/**
* @ClassName CallableDemo
* @Description TODO 线程创建的第三种方式:实现Callable接口(了解即可)
* @Author ZhangHao
* @Date 2022/12/11 10:24
* @Version: 1.0
*/
public class CallableDemo implements Callable<Boolean> {
private String url;//网图下载地址
private String name;//网图名称
public CallableDemo(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了文件名:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo d1 = new CallableDemo("https://cn.bing.com/images/search?view=detailV2&ccid=64mezA1F&id=0567ED050842B109CEFE6D7C2E235E6513915D00&thid=OIP.64mezA1F6eYavcDWrgjHQgHaEK&mediaurl=https%3a%2f%2fimages.hdqwalls.com%2fwallpapers%2fcute-kitten-4k-im.jpg&exph=2160&expw=3840&q=Cat+Wallpaper+4K&simid=608031326407324483&FORM=IRPRST&ck=5E947A96CD5B48E39B116D48F58466AB&selectedIndex=12&ajaxhist=0&ajaxserp=0", "cat1.jpg");
CallableDemo d2 = new CallableDemo("https://cn.bing.com/images/search?view=detailV2&ccid=qXtg4Nx0&id=A80C30163A6B55D16D61F27E632239424517705F&thid=OIP.qXtg4Nx0BUoeUP53fz_HKgHaFI&mediaurl=https%3a%2f%2fimages8.alphacoders.com%2f856%2f856433.jpg&exph=2658&expw=3840&q=Cat+Wallpaper+4K&simid=608046255722156270&FORM=IRPRST&ck=986D5F99CF8474477F4A1F2DB2850C9D&selectedIndex=25&ajaxhist=0&ajaxserp=0", "cat2.jpg");
CallableDemo d3 = new CallableDemo("https://cn.bing.com/images/search?view=detailV2&ccid=kvYsfUHA&id=6311D8D1DC87AA4B69783A97020038B03827134D&thid=OIP.kvYsfUHAAQlEVW3Z3_EEWwHaEK&mediaurl=https%3a%2f%2fwallpapershome.com%2fimages%2fpages%2fpic_h%2f19418.jpg&exph=1080&expw=1920&q=Cat+Wallpaper+4K&simid=608016886736366855&FORM=IRPRST&ck=37C2818B80D19766E7A91B5BB7A060D6&selectedIndex=159&ajaxhist=0&ajaxserp=0", "cat3.jpg");
//创建执行任务
ExecutorService es = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = es.submit(d1);
Future<Boolean> r2 = es.submit(d2);
Future<Boolean> r3 = es.submit(d3);
//获取结果
Boolean res1 = r1.get();
Boolean res2 = r2.get();
Boolean res3 = r3.get();
System.out.println(res1);//打印结果
System.out.println(res2);
System.out.println(res3);
//关闭服务
es.shutdownNow();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
}
}
}
小结
静态代理
代理模式在我们生活中很常见,比如我们购物,可以从生产工厂直接进行购物,但是在生活中往往不是这样,一般都是厂家委托给超市进行销售,而我们不直接跟厂家进行关联,这其中就引用了静态代理的思想,厂家相当于真实角色,超市相当于代理角色,我们则是目标角色。代理角色的作用其实就是,帮助真实角色完成一些事情,在真实角色业务的前提下,还可以增加其他的业务。AOP切面编程就是运用到了这一思想。
写一个小小的案例,通过婚庆公司,来实现静态代理。
/**
* @ClassName StaticProxy
* @Description TODO 静态代理(模拟婚庆公司实现)
* @Author ZhangHao
* @Date 2022/12/11 11:38
* @Version: 1.0
*/
public class StaticProxy {
public static void main(String[] args) {
Marry marry = new WeddingCompany(new You());
marry.happyMarry();
//注意:真实对象和代理对象要实现同一个接口
}
}
//结婚
interface Marry{
//定义一个结婚的接口
void happyMarry();
}
//你(真实角色)
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("张三结婚了!");
}
}
//婚庆公司(代理角色)
class WeddingCompany implements Marry{
//引入真实角色
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
}
@Override
public void happyMarry() {
//在结婚前后增加业务
before();
target.happyMarry();
after();
}
private void before(){
System.out.println("结婚之前:布置婚礼现场");
}
private void after(){
System.out.println("结婚之后:收尾工作");
}
}
Thread底层就使用的静态代理模式,源码分析
//Thread类实现了Runnable接口
public class Thread implements Runnable{
//引入了真实对象
private Runnable target;
//代理对象中的构造器
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
}
当我们开启一个线程,其实就是定义了一个真实角色实现了Runnable接口,重写了run方法。
public void TestRunnable{
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"张三").start();
//Thread就是代理角色,myThread就是真实角色,start()就是实现方法
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("我是子线程,同时是真实角色");
}
}
动态代理
前面使用到了静态代理,代理类是自己手工实现的,自己创建了java类表示代理类,同时要代理的目标类也是确定的,如果当目标类增多时,代理类也需要成倍的增加,代理类的数量过多,当接口中的方法改变或者修改时,会影响实现类,厂家类,代理都需要修改,于是乎就有了jdk动态代理。
动态代理的好处:
实现步骤:
/**
* @ClassName DynamicProxy
* @Description TODO 动态代理
* @Author ZhangHao
* @Date 2022/12/11 15:11
* @Version: 1.0
*/
public class DynamicProxy {
public static void main(String[] args) {
//创建目标对象
Marry target = new You();
//创建InvocationHandler对象
MyInvocationHandler handler = new MyInvocationHandler(target);
//创建代理对象
Marry proxy = (Marry)handler.getProxy();
//通过代理执行方法,会调用handle中的invoke()方法
proxy.happyMarry();
}
}
//创建结婚接口
interface Marry{
void happyMarry();
}
//目标类实现结婚接口
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("张三结婚了!");
}
}
//创建工具类,即方法增强的功能
class ServiceTools{
public static void before(){
System.out.println("结婚之前:布置婚礼现场");
}
public static void after(){
System.out.println("结婚之后:清理结婚现场");
}
}
//创建InvocationHandler的实现类
class MyInvocationHandler implements InvocationHandler{
//目标对象
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
//通过代理对象执行方法时,会调用invoke()方法
/**
* @Param [proxy:jdk创建的代理类的实例]
* @Param [method:目标类中被代理方法]
* @Param [args:目标类中方法的参数]
* @return java.lang.Object
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强功能
ServiceTools.before();
//执行目标类中的方法
Object obj = null;
obj = method.invoke(target,args);
ServiceTools.after();
return obj;
}
//通过Proxy类创建代理对象(自己手写的嗷)
/**
* @Param [ClassLoader loader:类加载器,负责向内存中加载对象的,使用反射获取对象的ClassLoader]
* @Param [Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。]
* @Param [InvocationHandler h: 我们自己写的,代理类要完成的功能。]
* @return java.lang.Object
**/
public Object getProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
}
总结
都知道人有生老病死,线程也不例外。
Java中线程的状态分为 6种,可以在Thread类的State枚举类查看 。

新建(NEW):使用new关键字创建一个线程的时候,就进入新建状态。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态统的称为“运行”。
2.1 就绪(ready):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态。
2.2 运行中(running):就绪状态的线程在获得CPU时间片后变为运行状态。
阻塞(BLOCKED):阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
超时等待(TIMED_WAITING):处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
sleep()方法就会使线程进入到超时等待状态,并且不会释放机锁,当前线程里调用其它线程t的join方法,当前线程进入TIME_WAITING状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
终止(TERMINATED):当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。线程一旦终止了,就不能复生。
| 方法 | 说明 |
|---|---|
| setPriority(int newPriority) | 更改线程的优先级 |
| static void sleep(long millis) | 在指定的毫秒内让正在执行的线程进入休眠状态 |
| void join() | 让其他线程等待当前线程先终止理解成vip插队 |
| static void yield() | 暂停正在执行的线程对象,并执行其他的线程理解为礼让 |
| void interrupt() | 中断线程,别使用这个方法 |
| boolean isAlive() | 测试线程是否处于活动状态 |
1. 停止线程
/**
* @ClassName TestStop
* @Description TODO 测试停止线程
* @Author ZhangHao
* @Date 2022/12/12 20:39
* @Version: 1.0
*/
public class TestStop implements Runnable {
//设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("子线程" + i++);
}
}
//设置公开的方法,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程:" + i);
if (i == 700) {
testStop.stop();
System.out.println("线程停止了!");
}
}
//主线程和子线程并行交替执行,当主线程i=700时,子线程停止执行,主线程继续执行直到执行完成。
}
}
2. 线程休眠
/**
* @ClassName TestSleep
* @Description TODO 线程休眠
* @Author ZhangHao
* @Date 2022/12/12 21:31
* @Version: 1.0
*/
public class TestSleep implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run() {
while (ticketNums > 0){
try {
Thread.sleep(10);//模拟网络延时,放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->抢到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
new Thread(demo,"老王").start();
new Thread(demo,"小张").start();
new Thread(demo,"黄牛党").start();
}
}
写一个小小的案例:使用sleep()完成倒计时和时间播报的功能
/**
* @ClassName TestSleep2
* @Description TODO 倒计时,时间播报
* @Author ZhangHao
* @Date 2022/12/12 21:39
* @Version: 1.0
*/
public class TestSleep2 {
public static void main(String[] args) {
//tenDown();
printNowDate();
}
//倒计时
public static void tenDown(){
int i = 10;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i--);
if (i <= 0) {
break;
}
}
}
//时间播报
public static void printNowDate(){
//获取当前时间
Date date = new Date(System.currentTimeMillis());
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
//更新当前时间
date = new Date(System.currentTimeMillis());
System.out.println(format);
}
}
}
3. 线程礼让
/**
* @ClassName TestYield
* @Description TODO 线程礼让
* @Author ZhangHao
* @Date 2022/12/13 12:46
* @Version: 1.0
*/
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
//通俗的讲,线程礼让其实就是A、B线程处于就绪状态等待被cpu调度执行,
//当其中有一个线程被cpu调度执行了,则当前这个线程再退回就绪状态重新和另外一个线程竞争
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始!");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程停止!");
}
}
4. 线程插队
/**
* @ClassName TestJoin
* @Description TODO 线程插队
* @Author ZhangHao
* @Date 2022/12/13 13:03
* @Version: 1.0
*/
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip线程" + i);
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
for (int i = 0; i < 500; i++) {
if (i == 200) {
try {
thread.start();
thread.join();//插队执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程" + i);
}
}
}
5. 线程状态
/**
* @ClassName TestState
* @Description TODO 线程状态
* @Author ZhangHao
* @Date 2022/12/13 13:22
* @Version: 1.0
*/
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);//线程睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程体执行完毕~");
});
Thread.State state = thread.getState();//观察线程状态
System.out.println(state);//NEW:没有调用start()方法之前都是new
thread.start();
state = thread.getState();
System.out.println(state);//RUNNABLE:进入运行状态
//只要线程还没有死亡,就打印线程状态
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(100);//100毫秒打印一次状态
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();//更新状态
System.out.println(state);
}
}
/*
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
线程体执行完毕~
TERMINATED
*/
}
6. 线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10,不在这个范围内的都会报出异常.
使用以下方式改变和获取优先级
优先级的设定建议在start()之前
/**
* @ClassName TestPriority
* @Description TODO 线程的优先级
* @Author ZhangHao
* @Date 2022/12/13 13:46
* @Version: 1.0
*/
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级:5
System.out.println(Thread.currentThread().getName()+"---->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
t1.start();//默认优先级是5
Thread t2 = new Thread(myPriority);
t2.setPriority(3);
t2.start();
Thread t3 = new Thread(myPriority);
t3.setPriority(8);
t3.start();
Thread t4 = new Thread(myPriority);
t4.setPriority(Thread.MAX_PRIORITY);//最大优先级10
t4.start();
Thread t5 = new Thread(myPriority);
//t5.setPriority(-1);//Exception in thread "main" java.lang.IllegalArgumentException
//t5.start();
//优先级越大代表被调度的可能性越高,优先级低不代表不会被调度,还是看CPU心情
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---->"+Thread.currentThread().getPriority());
}
}
7. 守护线程
/**
* @ClassName TestDaemon
* @Description TODO 守护线程
* @Author ZhangHao
* @Date 2022/12/13 14:09
* @Version: 1.0
*/
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认是false用户线程,正常的线程都是用户线程
thread.start();
new Thread(you).start();
//记住:虚拟机不用等待守护线程执行完毕,只需确保用户线程执行完毕程序就结束了。
//当用户线程执行完成之后,守护线程还执行了一段时间,是因为虚拟机关闭需要一段时间。
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你!");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("开心的活着!"+i);
}
System.out.println("------goodbye!------");
}
}
并发
同一个对象被多个线程同时操作,并发编程又叫多线程编程。生活中的例子很常见,比如过年了学生都需要在手机上抢火车票,几万个人同时去抢那10张票,最终只有10个幸运儿抢到,手速慢的学生是不是就没有抢到呀。
并行
同一时刻多个任务(进程or线程)同时执行,真正意义上做到了同时执行,但是这种情况往往只体现在多核CPU,单核CPU是做不到同时执行多个任务的,多核CPU内部集成了多个计算机核心(Core),每个核心相当于一个简单的CPU,多核CPU中的每个核心都可以独立执行一个任务,并且多个核心之间互不干扰,在不同核心上执行的多个任务,称为并行。
串行
多个任务按顺序依次执行,就比如小学在学校上厕所,小学的学校一般都是公共的厕所,而且是固定的坑位,大家按照提前排好的次序依次进行上厕所,也就是多个任务之间一个一个按顺序的执行。
线程同步
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想快速吃到饭,然后几万个学生就一拥而上,全部挤在打饭的窗口,最后饭不仅没吃到,还挨了一顿打,这也就是并发问题引起的,所以我们需要一种解决方案,最天然的解决办法就是,排队一个个来。排队在编程中叫:队列。这种解决办法就叫线程同步。
处理多线程问题时,多个线程访问同一个对象,并且当中会有一些线程需要修改这个对象,这个时候就需要用到线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入对象等待池形成队列,等待前面的线程用完,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间,会出现冲突问题,所以为了保证安全性,还加入了锁机制。synchronized关键字,当一个线程获得对象之后需要上锁,独占着资源,其他线程必须等待,等当前线程使用完释放锁即可。解决了线程安全的问题,同样也带来了一些问题:
一个线程拿到锁之后,其他需要这把锁的线程挂起。
在多线程竞争下,加锁和释放锁会导致频繁上下切换带来调度延迟和性能问题。
如果优先级高的线程等待优先级低的线程释放锁,会导致优先级倒置,引发性能问题。
要想保证安全,就一定会失去性能,要想保证性能,就一定会失去安全。鱼和熊掌不可兼得的道理。
线程同步:队列 + 锁
用三个小小的案例演示并发引起的问题:
/**
* @ClassName Ticket
* @Description TODO 模拟买票
* @Author ZhangHao
* @Date 2022/12/14 10:40
* @Version: 1.0
*/
public class Ticket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
//多线程同时抢票,加入延迟之后,会出现买到重复票和负数票。
}
}
class BuyTicket implements Runnable{
private int ticketNum = 10;//票数
private boolean flag = true;//设置标志位
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
private void buy() throws InterruptedException {
if(ticketNum<=0){
flag = false;
return;
}
Thread.sleep(100);//模拟延时,放大问题的发生性
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
}
}
李四抢到了第10张票
张三抢到了第10张票
王五抢到了第9张票
王五抢到了第8张票
张三抢到了第8张票
李四抢到了第8张票
张三抢到了第7张票
王五抢到了第7张票
李四抢到了第7张票
张三抢到了第6张票
王五抢到了第6张票
李四抢到了第6张票
张三抢到了第4张票
李四抢到了第5张票
王五抢到了第5张票
王五抢到了第3张票
李四抢到了第2张票
张三抢到了第1张票
李四抢到了第0张票
王五抢到了第-1张票
Process finished with exit code 0
/**
* @ClassName Bank
* @Description TODO 银行取钱
* @Author ZhangHao
* @Date 2022/12/14 11:05
* @Version: 1.0
*/
public class Bank {
public static void main(String[] args) {
Card card = new Card(200);
new MyThread(card,100,"老婆").start();
new MyThread(card,150,"我").start();
//出现将卡的余额取成负数
}
}
//银行卡
class Card {
public int money;//余额
public Card(int money) {
this.money = money;
}
}
//取钱
class MyThread extends Thread {
//卡号
private Card card;
//要取的钱
private int takeMoney;
//手里的钱
private int nowMoney;
public MyThread(Card card,int takeMoney,String name){
super(name);
this.card = card;
this.takeMoney = takeMoney;
}
@Override
public void run() {
if (card.money - takeMoney < 0) {
System.out.println(Thread.currentThread().getName() + "--->余额不足");
return;
}
try {
Thread.sleep(1000);//放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
//取钱
card.money = card.money - takeMoney;
//手里的钱
nowMoney += takeMoney;
//this.getName() = Thread.currentThread().getName()
//因为本类继承了Thread类可以直接使用其方法
System.out.println(Thread.currentThread().getName() + "取了" + takeMoney + "w,手里还有" + nowMoney + "w,银行卡余额还剩" + card.money);
}
}
我取了150w,手里还有150w,银行卡余额还剩-50
老婆取了100w,手里还有100w,银行卡余额还剩-50
/**
* @ClassName UnsafeList
* @Description TODO 多线程不安全的集合
* @Author ZhangHao
* @Date 2022/12/14 11:20
* @Version: 1.0
*/
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> strList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
strList.add(Thread.currentThread().getName());
System.out.println(strList);
}).start();
}
Thread.sleep(1000);
System.out.println("集合大小:"+strList.size());
}
}
//ConcurrentModificationException异常(并发修改异常)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.hnguigu.demo06.UnsafeList.lambda$main$0(UnsafeList.java:19)
at java.lang.Thread.run(Thread.java:748)
集合大小:9997
Process finished with exit code 0
1. 同步方法
public synchronized void method(int args) {}
由于我们可以通过private关键字来保证数据对象只被封装的方法访问(get/set),所以我们只需要针对方法提供一套机制,这套机制就是synchronized关键字,包括两种用法synchronized方法和synchronized块。
同步方法中的同步监视器就是this,这个对象的本身。
synchronized关键字是一个修饰符,直接加入在方法返回值前面就可以实现同步。
同步方法的弊端:
2. 同步块
同步块:synchronized(obj){}
obj称为同步监视器
同步监视器的执行流程
使用线程同步解决并发带来的问题
/**
* @ClassName Ticket
* @Description TODO 模拟买票
* @Author ZhangHao
* @Date 2022/12/14 10:40
* @Version: 1.0
*/
public class Ticket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
}
}
class BuyTicket implements Runnable{
private int ticketNum = 10;//票数
private boolean flag = true;//设置标志位
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
//加入了synchronized关键字就是同步方法,锁的对象是this
private synchronized void buy() throws InterruptedException {
if(ticketNum<=0){
flag = false;
return;
}
Thread.sleep(100);//模拟延时,放大问题的发生性
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
}
}
/**
* @ClassName Bank
* @Description TODO 银行取钱
* @Author ZhangHao
* @Date 2022/12/14 11:05
* @Version: 1.0
*/
public class Bank {
public static void main(String[] args) {
Card card = new Card(200);
new MyThread(card,100,"老婆").start();
new MyThread(card,150,"我").start();
}
}
//银行卡
class Card {
public int money;//余额
public Card(int money) {
this.money = money;
}
}
//取钱
class MyThread extends Thread {
//卡号
private Card card;
//要取的钱
private int takeMoney;
//手里的钱
private int nowMoney = 0;
public MyThread(Card card,int takeMoney,String name){
super(name);
this.card = card;
this.takeMoney = takeMoney;
}
@Override
public void run() {
//如果在这里加上synchronized关键字来修饰这个方法,锁的是this也就是MyThread,而真正操作的对象是Card,所以需要使用同步块实现
//锁的是需要变化的量,需要增删改的对象
synchronized (card){
if (card.money - takeMoney < 0) {
System.out.println(Thread.currentThread().getName() + "--->余额不足");
return;
}
try {
Thread.sleep(1000);//放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
//取钱
card.money = card.money - takeMoney;
//手里的钱
nowMoney += takeMoney;
//this.getName() = Thread.currentThread().getName()
//因为本类继承了Thread类可以直接使用其方法
System.out.println(Thread.currentThread().getName() + "取了" + takeMoney + "w,手里还有" + nowMoney + "w,银行卡余额还剩" + card.money);
}
}
}
/**
* @ClassName UnsafeList
* @Description TODO 多线程不安全的集合
* @Author ZhangHao
* @Date 2022/12/14 11:20
* @Version: 1.0
*/
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> strList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
//锁住需要变化的对象,这里就是list
synchronized(strList){
strList.add(Thread.currentThread().getName());
System.out.println(strList);
}
}).start();
}
Thread.sleep(1000);
System.out.println("集合大小:"+strList.size());
}
}
补充:juc(java.util.concurrent)包下的线程安全的集合
/**
* @ClassName CopyOnWriteArrayList
* @Description TODO 测试JUC并发编程下线程安全的ArrayList集合
* @Author ZhangHao
* @Date 2022/12/14 13:19
* @Version: 1.0
*/
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
//这里加入sleep()方法是防止在子线程还没完成之前,就打印了集合大小
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("集合大小:"+list.size());
}
}
3. 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程在等待对方释放锁资源,都停止执行的情形,某一个代码块同时拥有两个以上对象的锁时,就可能会发生“死锁”的问题。
/**
* @ClassName DeadLock
* @Description TODO 死锁
* @Author ZhangHao
* @Date 2022/12/14 16:15
* @Version: 1.0
*/
public class DeadLock {
public static void main(String[] args) {
Makeup makeup1 = new Makeup(0, "灰姑娘");
Makeup makeup2 = new Makeup(1, "白雪公主");
makeup1.start();
makeup2.start();
//最终结果:程序僵持运行着
}
}
//口红
class Lipstick{
String name = "迪奥口红";
}
//镜子
class Mirror{
String name = "魔镜";
}
//化妆
class Makeup extends Thread{
//使用static保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//选择化妆的人
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆
private void makeup() throws InterruptedException {
if(choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName + "--->获得" + lipstick.name);
Thread.sleep(1000);
synchronized (mirror){//一秒钟之后想要镜子的锁
System.out.println(this.girlName + "--->获得" + mirror.name);
}
}
}else{
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName + "--->获得" + mirror.name);
Thread.sleep(2000);
synchronized (lipstick){//两秒钟之后想要口红的锁
System.out.println(this.girlName + "--->获得" + lipstick.name);
}
}
}
}
}
灰姑娘拿着口红的锁不释放,随后一秒钟后又要魔镜的锁,白雪公主拿着魔镜的锁不释放,两秒钟后又要口红的锁,双方都不释放已经使用完了的锁资源,僵持形成死锁。
解决办法就是用完锁就释放。
/**
* @ClassName DeadLock
* @Description TODO 死锁
* @Author ZhangHao
* @Date 2022/12/14 16:15
* @Version: 1.0
*/
public class DeadLock {
public static void main(String[] args) {
Makeup makeup1 = new Makeup(0, "灰姑娘");
Makeup makeup2 = new Makeup(1, "白雪公主");
makeup1.start();
makeup2.start();
}
}
//口红
class Lipstick{
String name = "迪奥口红";
}
//镜子
class Mirror{
String name = "魔镜";
}
//化妆
class Makeup extends Thread{
//使用static保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//选择化妆的人
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆
private void makeup() throws InterruptedException {
if(choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName + "--->获得" + lipstick.name);
Thread.sleep(1000);
}
synchronized (mirror){//一秒钟之后想要镜子的锁
System.out.println(this.girlName + "--->获得" + mirror.name);
}
}else{
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName + "--->获得" + mirror.name);
Thread.sleep(2000);
}
synchronized (lipstick){//两秒钟之后想要口红的锁
System.out.println(this.girlName + "--->获得" + lipstick.name);
}
}
}
}
产生死锁的四个必要条件:
上面就是形成死锁的必要条件,只需要解决其中任意一个或者多个条件就可以避免死锁的发生。
4. Lock锁
从JDK5.0开始,Java提供了更强大的线程同步机制,通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
/**
* @ClassName TestLock
* @Description TODO Lock锁
* @Author ZhangHao
* @Date 2022/12/14 16:47
* @Version: 1.0
*/
public class TestLock {
public static void main(String[] args) {
MyLock myLock = new MyLock();
new Thread(myLock, "张三").start();
new Thread(myLock, "老王").start();
new Thread(myLock, "黄牛").start();
}
}
class MyLock implements Runnable {
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//加锁
if (ticketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
} else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
}
synchronized和lock锁的区别:
应用场景:生产者和消费者的问题。
假设有一个仓库只能放一件产品,生产者将生产出来的产品放到仓库,消费者从仓库取走商品消费。如果仓库中没有产品,则消费者等待生产者生产商品,有商品则通知消费则取走商品。
这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互成条件。
Java提供了几个方法解决线程之间的通信问题
| 方法名 | 作用 |
|---|---|
| wait() | 表示线程一直等待,直到其他线程通知,与sleep不同的是,它会释放锁 |
| wait(timeout) | 指定等待的毫秒数 |
| notify() | 唤醒一个处于等待状态的线程 |
| notifyAll() | 唤醒同一个对象上所有调用wait()的线程,优先级高的线程有限调度 |
注意:均是Object类的方法 , 都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
解决办法:
1. 管城法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
/**
* @ClassName TestPC
* @Description TODO 生产者和消费者模型
* @Author ZhangHao
* @Date 2022/12/14 20:28
* @Version: 1.0
*/
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//定义两个线程:生产者和消费者
class Producer extends Thread {
//生产者需要将生产的鸡丢入仓库
SynContainer container;
Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了--->" + i + "只鸡");
}
}
}
class Consumer extends Thread {
//消费者需要从仓库里面取鸡
SynContainer container;
Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
Chicken pop = container.pop();
System.out.println("消费了--->" + pop.count + "只鸡");
}
}
}
//商品
class Chicken {
int count;//数量
public Chicken(int count) {
this.count = count;
}
}
//缓冲区
class SynContainer {
//需要一个容器装载,假如一个仓库只能装10只鸡
Chicken[] chickens = new Chicken[10];
//计数
int count = 0;
//生产者放入容器的方法
public synchronized void push(Chicken chicken) {
//如果仓库装满了鸡,就通知消费者消费,生产者等待。
while (count == chickens.length - 1) {
try {
this.wait();//生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产者生产商品丢入仓库
chickens[count] = chicken;
count++;
//通知消费者消费,唤醒消费者。
this.notifyAll();
}
//消费者消费产品的方法
public synchronized Chicken pop() {
//仓库里面没有鸡,就通知生产者生产,消费者等待
while (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费产品
count--;
Chicken chicken = chickens[count];
//通知生产者生产,唤醒生产者。
this.notifyAll();
return chicken;
}
}
注意:这里如果使用if判断逻辑上是完全没问题的,但是这里会出现一个虚假唤醒,通俗的说如果某个线程处于wait()状态,如果用if判断的话,唤醒后线程会直接从wait方法后执行,不会重新进行if判断,但如果使用while来作为判断语句的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件。
2. 信号灯法
通过设置标志位来完成线程之间的通信
/**
* @ClassName TestTV
* @Description TODO 信号灯法
* @Author ZhangHao
* @Date 2022/12/14 21:33
* @Version: 1.0
*/
public class TestTV {
public static void main(String[] args) {
Tv tv = new Tv();
new Thread(new Player(tv)).start();
new Thread(new Watcher(tv)).start();
}
}
//定义两个线程:生产者和消费者
//表演者
class Player implements Runnable {
Tv tv;
Player(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 1; i < 20; i++) {
if (i%2==0){
tv.play("光头强");
}else{
tv.play("喜洋洋");
}
}
}
}
//观众
class Watcher implements Runnable {
Tv tv;
Watcher(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 1; i < 20; i++) {
tv.watch();
}
}
}
//表演
class Tv {
//演员表演,观众观看
String program;//节目
boolean flag = true;//设置标志位,默认是没有节目观看
//演员表演
public synchronized void play(String program) {
//如果有节目观看,演员就等待观众观看
if (!flag) {
try {
this.wait();//等待观看
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + program);
this.notifyAll();//唤醒观众
this.program = program;
this.flag = !this.flag;
}
//观众观看
public synchronized void watch() {
//如果没有节目观看,就通知演员表演
if (flag) {
try {
this.wait();//等待演出
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了:" + program);
this.notifyAll();//唤醒演员表演
this.flag = !this.flag;
}
}
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
/**
* @ClassName TestPool
* @Description TODO 线程池
* @Author ZhangHao
* @Date 2022/12/14 21:58
* @Version: 1.0
*/
public class TestPool {
public static void main(String[] args) {
//创建线程池,参数是线程池的大小,决定了能装多少个线程
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
//关闭连接
executorService.shutdown();
}
}
class ThreadPool implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
完结撒花!
/**
* @ClassName TestCallable
* @Description TODO 补充Callable启动线程
* @Author ZhangHao
* @Date 2022/12/14 22:28
* @Version: 1.0
*/
public class TestCallable {
public static void main(String[] args){
MyCallable callable = new MyCallable();
//Runnable实现类
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
//获取callable返回值
try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("完结撒花!");
return 100;
}
}
好文要顶!
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我正在尝试使用ruby编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.