重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
其实你已经很好了,我认为写程序首先要有自己的思路,其次才是看你真正掌握的技能...比如一艘船如果有足够大的马力,但是缺少正确的方向..那样子会装上暗礁的,所以在编程方面业务逻辑是很重要的,接下来只要有一般的技能基础就可以了;
成都创新互联是一家专业提供平乐企业网站建设,专注与成都做网站、网站设计、H5建站、小程序制作等业务。10年已为平乐众多企业、政府机构等服务。创新互联专业网络公司优惠进行中。
就拿你说的修改密码来说吧:第一步:我首先要知道要修改人的ID,然后才能按照一定的方法修改数据库中的表:一个update user set user_password=“要修改的密码” where user_id=“指定修改人的ID” 再加上一定的连接数据库的方法..程序员修改密码的目的就达到了...加油!
这个问题其实涉及到socket套接字的缓冲机制,客户端和服务器都绑定有输入输出流,在你发送的时候如果字节数没有到达默认的缓冲大小的时候会产生你这样的问题,你只需要在发送完后flush刷新一下缓冲区就可以了
我认为Java语言的10大问题是:
1、缺少闭包(closure):我想这个不需要解释了。函数式编程已经存在几十年了,但最近几年,它们获得了越来越多的关注,最主要的原因,是它可以自然地编写并行程序。我部分的同意Joshua Bloch强调在Java中引入闭包的问题需要再想一想(BGGA提议的方式真的很糟),至少闭包的缺失,使得在Java中做任何真正的函数式编程都是不可能的。
2、缺少一等函数:这个问题与前一个有些关联,但我认为它更糟糕。在Java里,要达到类似效果的唯一方式,是使用著名的、丑陋悲惨的单方法匿名内部类,但这看上去的确是一个拙劣的方法。甚至在C#中,也通过代理机制,提供了一个更好的实现。
3、原生类型(Primitive types):如果在Java中一切皆对象,那是多么完美啊,但他们偏偏不这样设计。因而,这一点导致了一些问题,比如,不能把一个int放到集合(Collection)里,这个在Java5中通过自动装箱特性得到了解决(下面会提到)。它也造成了传值与传引用上的困扰,原生类型数据是通过值传给方法的(复制一份拷贝,然后传给函数),而真正的对象是通过传递(译注:其实是复制对象地址再传递,因此应该也是传值方式,只是由于函数内部可通过这个对象地址访问对象,因此效果上类似传引用)。
4、自动装箱(Autoboxing)和自动拆箱(autounboxing):这个特性是为了解决因原生类型的存在所导致的问题,在Java5引入的。它允许静默地转换原生类型到相应的对象,但这常常导致其它的问题。比如Integer可以为null,但int不能,因此这时JVM只能抛出一个难以调试的空指针异常(NullPointerException)。此外,它还可能导致其它奇怪的行为,就像下面的例子,我们就很难理解,变量test为什么是false:
Intger a = new Integer(1024);
Intger b = new Integer(1024);
boolean test = a b || a == b || a b;
5、缺少范型具类化:范型是Java5引入的一个很酷的特征,但是为了保持与旧版本Java的兼容性,导致缺失某些重要的特性,尤其是不能在运行时反省范型的类型。例如,你有一个方法,接受List参数,如果传进来一个List,你却不能知道运行里该范型的确切类型。同理,你也不能创建范型数组。这意味着,尽管下面的代码看起来很自然,但却不编译不了:
List[] listsOfStrings = new List[3];
6、不可避免的范型警告:你有发现过自己陷入不可能去掉的关于范型的警告么?如果你像我一样大量使用范型,我打赌你碰到过。事实上,是这个问题的规模化症状,让他们认为需要引入一个特定的注解(@SuppressWarnings("unchecked"))来处理这种情况,我觉得,范型应该可能被设计的更好。
7、不能传void给方法调用:我得承认,这种给方法传递void的需求,乍一看有些怪异。我喜欢DSL,当我实现自己的DSL库(lambdaj)的一个特定特性时,我不得不需要一个方法声明成这样的签名:void doSomething(Object parameter),这里为这个方法传进来的参数parameter,是另一个方法调用的结果,它唯一的目的,是注册调用(的对象)自身,以可以在以后执行它。让我吃惊的是,即使println方法返回void,看上去也并没有一个好理由,不允许我把代码写成这样,:
doSomething(System.out.println("test"));
8、没有原生的代理机制:代理是一种非常有效和应用广泛的模式,但Java提供的代理机制,只针对接口,而不是具体类。这是为什么象cblib这样提供这种机制的库,被如此多的主流框架,如Spring和Hibernate,采用的原因。此外,由于cglib通过运行时创建被代理类的子类来实现的,因此这些种方式有一个众所周知的限制——不能代理final类,比如String。
9、差劲的Switch...case语句:Java规定,switch...case只能选择int和enum(Java5开始)。这一点如果跟更现代的语言如Scala相比,看起来简直太弱了。
10、受检查异常(Checked exception):类似原生类型,受检查异常也已经成为Java的一个罪孽之源。它迫使程序员必须做下面两件极其糟糕讨厌的事情中的一个:让你的代码里充斥大量的、糟糕难读的、容易出错的try...catch语句,而这样做的最大意义,只是将捕获的异常,包装成运行时异常,然后再重新抛出;或者是让大量的抛出声明子句污染你的API,让接口缺少灵活性和可扩展性。
真正的问题是,这里我提到的这几大主要问题,唯一的解决办法,是要做一个痛苦的决择,定义一套新的语言规范,放下当前版本的向后兼容性。我猜他们永远也不会这么做,虽然我相信,如果编写一个能够自动转换旧Java源码的程序,让它们与假设的新版本兼容,并不是很困难。最后,这就是我决定开始寻找一个更好的JVM兼容语言的原因。
Java8在2014年三月发布了。我们打算将Pondus的所有生产服务器升级到这一新版本。从那时起,我们将大部分代码库迁移到lambda表达式、数据流和新的日期API上。我们也会使用Nashorn来把我们的应用中运行时发生改变的部分变成动态脚本。
除了lambda,最实用的特性是新的数据流API。集合操作在任何我见过的代码库中都随处可见。而且对于那些集合操作,数据流是提升代码可读性的好方法。
但是一件关于数据流的事情十分令我困扰:数据流只提供了几个终端操作,例如reduce和findFirst属于直接操作,其它的只能通过collect来访问。工具类Collctors提供了一些便利的收集器,例如toList、toSet、joining和groupingBy。
例如,下面的代码对一个字符串集合进行过滤,并创建新的列表:
stringCollection
.stream()
.filter(e - e.startsWith( "a"))
.collect(Collectors.toList());
在迁移了300k行代码到数据流之后,我可以说,toList、toSet、和groupingBy是你的项目中最常用的终止操作。所以我不能理解为什么不把这些方法直接集成到Stream接口上面,这样你就可以直接编写:
stringCollection
.stream()
.filter(e - e.startsWith( "a"))
.toList();
这在开始看起来是个小缺陷,但是如果你需要一遍又一遍地编写这些代码,它会非常烦人。
有toArray()方法但是没有toList(),所以我真心希望一些便利的收集器可以在Java9中这样添加到Stream接口中。是吧,Brian?ಠ_ಠ
注:Stream.js是浏览器上的Java 8 数据流API的JavaScript接口,并解决了上述问题。所有重要的终止操作都可以直接在流上访问,十分方便。详情请见API文档。
无论如何,IntelliJ IDEA声称它是最智能的Java IDE。所以让我们看看如何使用IDEA来解决这一问题。
使用 IntelliJ IDEA 来帮忙
IntelliJ IDEA自带了一个便利的特性,叫做实时模板(Live Template)。如果你还不知道它是什么:实时模板是一些常用代码段的快捷方式。例如,你键入sout并按下TAB键,IDEA就会插入代码段System.out.println()。更多信息请见这里。
如何用实时模板来解决上述问题?实际上我们只需要为所有普遍使用的默认数据流收集器创建我们自己的实时模板。例如,我们可以创建.toList缩写的实时模板,来自动插入适当的收集器.collect(Collectors.toList())。
下面是它在实际工作中的样子:
让我们看看如何自己构建它。首先访问设置(Settings)并在左侧的菜单中选择实时模板。你也可以使用对话框左上角的便利的输入过滤。
下面我们可以通过右侧的+图标创建一个新的组,叫做Stream。接下来我们向组中添加所有数据流相关的实时模板。我经常使用默认的收集器toList、toSet、groupingBy 和 join,所以我为每个这些方法都创建了新的实时模板。
这一步非常重要。在添加新的实时模板之后,你需要在对话框底部指定合适的上下文。你需要选择Java → Other,然后定义缩写、描述和实际的模板代码。
// Abbreviation: .toList
.collect(Collectors.toList())
// Abbreviation: .toSet
.collect(Collectors.toSet())
// Abbreviation: .join
.collect(Collectors.joining( "$END$"))
// Abbreviation: .groupBy
.collect(Collectors.groupingBy(e - $END$))
特殊的变量$END$指定在使用模板之后的光标位置,所以你可以直接在这个位置上打字,例如,定义连接分隔符。
提示:你应该开启"Add unambiguous imports on the fly"(自动添加明确的导入)选项,便于让IDEA自动添加 java.util.stream.Collectors的导入语句。选项在 Editor → General → Auto Import中。
让我们在实际工作中看看这两个模板:
连接分组
Intellij IDEA中的实时模板非常灵活且强大。你可以用它来极大提升代码的生产力。你知道实时模板可以拯救生活的其它例子吗?请让我知道!
作者:布客飞龙 segmentfault.com/a/1190000006033999
在Java平台上进行多线程编程的缺陷
就其自身来说,并发编程是一种技术,提供了操作的同时执行,不论是在单一系统上还是分布在大量系统上。这类操作实际是一些指令顺序,例如单独某个顶级任务的子任务,这类操作能够并行执行,或者是作为线程,或者是作为进程。线程和进程之间的本质区别在于:进程通常是独立的(例如独立的地址空间),所以只能通过系统提供的进程间通信机制进行交互,而线程通常共享单一进程的状态信息,能够直接共享系统资源和内存中的对象。
可以使用下面两种方法之一,通过多个进程来实现并发。第一种方法是在同一个处理器上运行进程,由操作系统处理进程之间的上下文环境切换。(可以理解,这种切换要比同一进程内多线程之间的上下文环境切换更慢。)第二种方法是构建大规模的并行和复杂的分布式系统,在不同的物理处理器上运行多个进程。
从内建支持的角度来说,Java 语言通过线程提供并发编程;每个 JVM 都能支持许多线程同时执行。可以用以下两种方法之一在 Java 语言中创建线程:
继承 java.lang.Thread 类。在这种情况下,已经重写的子类的 run() 方法必须包含实现线程运行时行为的代码。要执行这个代码,需要实例化子类对象,然后调用对象的 start() 方法,这样就可以在内部执行 run() 方法了。
创建 Runnable 接口的定制实现。这个接口只包含一个 run() 方法,在这个方法中,要放置应用程序代码。要执行这个代码,需要实例化实现类的对象,然后在创建新 Thread 时,把对象作为构造函数的参数传入。然后调用新创建的线程对象的 start() 方法,开始执行控制的新线程。
线程安全性和同步
如果 Java 对象中的某个方法能够安全地运行在多线程环境中,那么就称该方法是 线程安全的。要获得这种安全性,必须有一种机制,通过该机制,运行同一方法的多个线程就能够同步其操作,这样,在访问相同的对象或代码行时,就会只允许一个线程被处理。这种同步要求线程使用叫作 信号 的对象彼此进行沟通。
有一种类型的信号叫作 互斥信号 或 互斥体。顾名思义,这个信号对象的拥有权是互斥的,也就是说,在任意指定时间,只有一个线程能够拥有互斥体。其他想获得所有权的线程会被阻塞,它们必须等待,直到拥有互斥体的线程释放互斥体。如果多个线程按顺序排队等候同一互斥体,那么在当前拥有者释放它的时候,只有一个等候线程能够得到它;其他线程将继续阻塞。
在 1970 年代初,C.A.R. Hoare 和其他人共同开发了一个叫作 监视器 的概念。一个 监视器 就是一个代码主体,它的访问受到互斥体的保护。任何想执行这个代码的线程,都必须在代码块顶部得到关联的互斥体,然后在底部再释放它。因为在指定时间只有一个线程能够拥有互斥体,所以这就有效地保证了只有拥有它的线程才能执行监视器的代码块。(受保护的代码不需要相邻 —— 例如,Java 语言中的每个对象都有一个与之关联的监视器。)
任何想在 Java 语言中进行线程编程的开发人员,都会立即把上面的内容当成 synchronized 关键字所带来的效果。可以确保包含在 synchronized 块中的 Java 代码在指定时间只被一个线程执行。在内部,可以由运行时将 synchronized 关键字转换成某一种情况:所有的竞争线程都试图获得与它们(指线程)正在操作的对象实例关联的那个(惟一的一个)互斥体。成功得到互斥体的线程将运行代码,然后在退出 synchronized 块时释放互斥体。
等候和通知
wait/notify 构造在 Java 语言的线程间通信机制中也扮演了重要的角色。基本的想法是:一个线程需要的某个条件可以由另外一个线程促成。这样,条件的 wait 就可以得到满足。一旦条件为真,那么引发条件的线程就会 notify 等候线程苏醒,并从中止的地方继续进行。
wait/notify 机制要比 synchronized 机制更难理解和判断。要想判断出使用 wait/notify 的方法的行为逻辑,就要求判断出使用它的所有方法的逻辑。一次判断一个方法,把该方法和其他方法隔离开,是对整体系统行为得出错误结论的可靠方式。显然,这样做的复杂性会随着要判断的方法的数量增长而迅速提高。
线程状态
我前面提到过,必须调用新创建的线程的 start() 方法来启动它的执行。但是,仅仅是调用 start() 方法并不意味着线程会立即开始运行。这个方法只是把线程的状态从 new 变成 runnable。只有在操作系统真正安排线程执行的时候,线程状态才会变成 running (从 runnable)。
典型的操作系统支持两种线程模型 —— 协作式和抢占式。在协作式 模型中,每个线程对于自己对 CPU 的控制权要保留多久、什么时候放弃有最终意见。在这个模型中,因为可能存在某个无赖线程占住控制权不放,所以其他线程可能永远无法得到运行。在 抢占式 模型中,操作系统本身采用基于时钟“滴答”的计时器,基于这个计时器,操作系统可以强制把控制权从一个线程转移到另外一个线程。在这种情况下,决定哪个线程会得到下一次控制权的调度策略就有可能基于各种指标,例如相对优先级、某个线程已经等待执行的时间长短,等等。
如果出于某些原因,处在 running 状态的线程需要等候某个资源(例如,等候设备的输入数据到达,或者等候某些条件已经设定的通知),或者在试图获得互斥体的时候被阻塞,因此线程决定睡眠,那么这时它可以进入 blocked 状态。当睡眠周期到期、预期输入到达,或者互斥体当前的拥有者将其释放并通知等候线程可以再次夺取互斥体时,阻塞的线程重新进入 runnable 状态。
当线程的 run() 方法完成时(或者正常返回,或者抛出 RuntimeException 这样的未检测到异常),线程将终止。这时,线程的状态是 dead。当线程死亡时,就不能通过再次调用它的 start() 方法来重新启动它,如果那么做,则会抛出 InvalidThreadStateException 异常。
四个常见缺陷
正如我已经展示过的,Java 语言中的多线程编程是通过语言支持的大量精心设计的构造实现的。另外,还设计了大量设计模式和指导原则,来帮助人们了解这种复杂性带来的许多缺陷。除此之外,多线程编程会很容易地在不经意间把细微的 bug 带进多线程代码,而且更重要的是,这类问题分析和调试起来非常困难。接下来要介绍的是用 Java 语言进行多线程编程时将会遇到(或者可能已经遇到过)的最常见问题的一个列表。
争用条件
据说 争用条件 存在于这样的系统中:多个线程之间存在对共享资源的竞争,而胜出者决定系统的行为。Allen Holub 在他撰写的文章 “programming Java threads in the real world” 提供了一个带有这样 bug 的简单的多线程程序示例。在冲突的访问请求之间进行不正确同步的另一个更可怕的后果是 数据崩溃,此时,共享的数据结构有一部分由一个线程更新,而另一部分由另一个线程更新。在这种情况下,系统的行为不是按照胜出线程的意图进行,系统根本不按照任何一个线程的意图行动,所以两个线程最后都将以失败告终。
死锁
死锁 的情况是指:线程由于等候某种条件变成真(例如资源可以使用),但是它等候的条件无法变成真,因为能够让条件变成真的线程在等候第一个线程“做某件事”。这样,两个线程都在等候对方先采取第一步,所以都无法做事。
活动锁
活动锁 与 死锁 不同,它是在线程实际工作的时候发生的,但这时还没有完成工作。这通常是在两个线程交叉工作的时候发生,所以第一个线程做的工作被另一个线程取消。一个简单的示例就是:每个线程已经拥有了一个对象,同时需要另外一个线程拥有的另外一个对象。可以想像这样的情况:每个线程放下自己拥有的对象,捡起另外一个线程放下的对象。显然,这两个线程会永远都运行在上锁这一步操作上,结果是什么都做不成。(常见的真实示例就是,两个人在狭窄的走廊相遇。每个人都礼貌地让到另一边让对方先行,但却在相同的时间都让到同一边了,所以两个人还都没法通过。这种情况会持续一些时间,然后两个人都从这边闪到那边,结果还是一点进展也没有。)
资源耗尽
资源耗尽,又称为 线程耗尽,是 Java 语言的 wait/notify 原语无法保证 live-ness 的后果。Java 强制这些方法要拥有它们等候或通知的对象的锁。在某个线程上调用的 wait() 方法在开始等候之前必须释放监视器锁,然后在从方法返回并获得通知之后,必须再次重新获得锁。因此,Java 语言规范在锁本身之外,还描述了一套与每个对象相关的 等候集(wait set)。一旦线程释放了对象上的锁(在 wait 的调用之后),线程就会放在这个等候集上。
多数 JVM 实现把等候线程放在队列中。所以,如果在通知发生的时候,还有其他线程在等候监视器,那么就会把一个新线程放在队列尾部,而它并不是下一个获得锁的线程。所以,等到被通知线程实际得到监视器的时候,通知该线程的条件可能已经不再为真,所以它不得不再次 wait。这种情况可能无限持续下去,从而造成运算工作上浪费(因为要反复把该线程放入等候集和从中取出)和线程耗尽。
贪心哲学家的寓言
演示这种行为的原型示例是 Peter Welch 教授描述的“聪明人没有鸡肉”。在这个场景中考虑的系统是一所由五位哲学家、一位厨师和一个食堂组成的学院。所有的哲学家(除了一位)都要想想(在代码示例中,考虑的时间是 3 秒)之后才去食堂取饭。而“贪心的”哲学家则不想把时间浪费在思考上 —— 相反,他一次又一次地回到食堂,企图拿到鸡肉来吃。
厨师按照一批四份的定量准备鸡肉,每准备好一批,就送到食堂。贪心的哲学家不断地去厨房,但他总是错过食物!事情是这样的:他第一次到的时候,时间太早,厨师还没开火。因此贪心的哲学家只好干等着(通过 wait() 方法调用)。在开饭的时候(通过 notify() 方法调用),贪心的哲学家再一次回到食堂排队等候。但是这次,在他前来等候的时候,他的四位同事已经到了,所以他在食堂队列中的位置在他们后面。他的同事们把厨房送来的一批四份鸡肉全部拿走了,所以贪心的哲学家又要在一边等着了。 可怜(也可能是公平的) ,他永远处在这个循环之外。
验证的问题
一般来说,很难按照普通的规范对 Java 编程的多线程程序进行验证。同样,开发自动化工具对于常见的并发问题(例如死锁、活动锁和资源耗尽)进行完整而简单的分析也不太容易——特别是在任意 Java 程序中或者在缺乏并发的正式模型的时候。
更糟的是,并发性问题出了名的变化多端、难于跟踪。每个 Java 开发人员都曾经听说过(或者亲自编写过)这样的 Java 程序:经过严格分析,而且正常运行了相当一段时间,没有表现出潜在的死锁。然后突然有一天,问题发生了,结果弄得开发团队经历许多的不眠之夜来试图发现并修补根本原因。
一方面,多线程 Java 程序容易发生的错误非常不明显,有可能在任意什么时候发生。另一方面,完全有可能这些 bug 在程序中从不出现。问题取决于一些不可知的因素。多线程程序的复杂本质,使得人们很难有效地对其进行验证。没有一套现成的规则可以找出多线程代码中的这类问题,也无法确切地证明这些问题不存在,这些导致许多 Java 开发人员完全避开多线程应用程序的设计和开发,即使用并发和并行的方式对系统进行建模会非常棒,他们也不使用多线程。
确实想进行多线程编程的开发人员通常准备好了以下一个或两个解决方案(至少是一部分):
长时间艰苦地测试代码,找出所有出现的并发性问题,诚心地希望到应用程序真正运行地时候已经发现并修复了所有这类问题。
大量运行设计模式和为多线程编程建立的指导原则。但是,这类指导原则只在整个系统都按照它们的规范设计的时候才有效,没有设计规则能够覆盖所有类型的系统。
虽然知道的人不多,但是对于编写(然后验证)正确的多线程应用程序这一问题,还有第三个选项。使用称为通信顺序进程( Communicating Sequential Processes,CSP)的精确的线程同步的数学理论,可以在设计时最好地处理死锁和活动锁之类的问题。CSP 由 C.A.R. Hoare 与 20 世纪 70 年代后期设计,CSP 提供了有效的方法,证明用它的构造和工具构建的系统可以免除并发的常见问题。
结束语
在这份面向 Java 程序员的 CSP 全面介绍中,我把重点放在克服多线程应用程序开发常见问题的第一步上,即了解这些问题。我介绍了 Java 平台上目前支持的多线程编程构造,解释了它们的起源,讨论了这类程序可能会有的问题。我还解释了用正式理论在任意的、大型的和复杂的应用程序中清除这些问题(即竞争冒险、死锁、活动锁和资源耗尽)或者证明这些问题不存在的困难。
楼上说的有些道理,但是个人感觉并非全对
JAVA为了夸平台不得不舍弃很多东西,对于目前的游戏开发只针对于Windows进行大跨步的发展,JAVA想要在这方面赶超C++等属于微软的语言难上加难。
原因很多,例如游戏开发中大部分的引擎是C++语言开发的,原因是为了更快速的速度而大量使用的底层API函数去操作显卡,而JAVA调用API现在来讲不是不可以,但始终不会有很好的效率,就像你用自己的东西只管用而你的邻居要用肯定还要问你借一样~甚至还要问你怎么用~
语言目前存在瓶颈不代表JAVA不会进步,加油吧,JAVA的明天是美好的~