本文发表于本人博客

今天我来说说关于JAVA多线程知识,有错误请指出。大家都知道JAVA在服务端上处理也有很大优势,很多公司也有在服务器跑JAVA进程,这说明JAVA在处理这个多线程以及并发下也有一定有优点的(这说法有点坑了)。下面来看看

那java中,不具备直接操作像操作系统一样的PV信号,然而它提供了synchronized来实现同步机制,可能这样说不够严谨。JAVA的基类Object中有以下几个方法:

public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;

凡是继承实现Object对象的都有这样的方法,都有一个内存锁:当有线程获取改内存锁之后其它线程就无法访问改内存转入等待,知道占用改内存锁的线程释放内存锁才可以进入,这里网上好多地方也只是简单的说了下,我也是现在才比较了解原来:第一次当有线程获得了改对象的内存锁时此线程就进入锁池,其它需要使用该对象的线程进入等待池(即时调用了wait()).知道占用改对象内存锁的线程调用notify()或者调用了notifyAll()释放了,这里好像还有一个超时条件现在不知道回头再看看!!那么系统就会从等待池中随意挑选一个线程占用该对象的内存锁进入锁池,这里还要注意的是:Class也是一种对象,不是我们普通认为的new出来的才是对象,在这里它也属于对象所以就出现了对整个对象加锁、对整个class加锁的情况了!

上面的3个方法必须与synchronized关键字一起使用,因为wait()以及notify()都是在已经获得对象、类锁的时候才可以操作,才能够进入等待池以及释放锁。

春节将至,下面来个抢火车票的例子简单说明一下,先看Train类:

class Train{
/**
* 头等高级坐席
*/
private int Tickets = 10;
/**
* 购买火车票
* @throws Exception
*/
public void Purchase(String name) throws Exception{
// 模拟获取Tickets数据操作
Thread.sleep(50);
if(Tickets > 0){
// 模拟获取订单数据等操作
Thread.sleep(10);
int temp = Tickets--;
System.out.println(name + "抢到:" + temp);
// 模拟生成订单等操作
Thread.sleep(10);
}
else{
System.out.println(name + "抢票失败!");
}
}
}

TrainThread类:

class TrainThread extends Thread{
private Train train;
public TrainThread(Train train){
this.train = train;
}
@Override
public void run() {
try {
this.train.Purchase(Thread.currentThread().getName());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

main函数:

    public static void main(String[] args) {
//春节抢购火车票例子,模拟100人抢购10张票
Train train = new Train();
TrainThread[] threads = new TrainThread[100];
for (int i = 0; i < 100; i++) {
TrainThread th1 = new TrainThread(train);
threads[i] = th1;
}
for (int i = 100 - 1; i >= 0; i--) {
threads[i].start();
}
}

执行结果输出:

Thread-99抢到:10
Thread-97抢到:9
Thread-81抢到:1
Thread-79抢到:0
Thread-25抢票失败!
Thread-27抢票失败!
Thread-38抢票失败!
Thread-98抢到:-1
Thread-96抢到:-2
Thread-40抢票失败!
Thread-84抢到:-3

不行啊,连-1的票都出来了完全没实现要求,那分析下:应该是在if(Tickets > 0)里面模拟睡眠了10毫秒引起的,因为在睡眠10毫秒的时候很多线程也进来已经判断了if(Tickets > 0),那既然分析了原因有什么方法解决啊,当然有,我们可以把这个代码块锁起来,当一个线程正在判断操作Tickets变量的时候不让其它线程去访问,那Java里面有个关键字synchronized,我们再来看看代码:

    public void Purchase(String name) throws Exception{
// 模拟获取Tickets数据操作
Thread.sleep(50);
synchronized(this){
if(Tickets > 0){
// 模拟获取订单数据等操作
Thread.sleep(10);
int temp = Tickets--;
System.out.println(name + "抢到:" + temp);
// 模拟生成订单等操作
Thread.sleep(10);
}
else{
// System.out.println(name + "抢票失败!");
}
}
}
}

现在再来看输出:

Thread-93抢到:10
Thread-5抢到:9
Thread-3抢到:8
Thread-1抢到:7
Thread-4抢到:6
Thread-2抢到:5
Thread-0抢到:4
Thread-13抢到:3
Thread-18抢到:2
Thread-16抢到:1

多运行几次发现没有重复也不会出现负数的情况了,正常!再来分析下,代码用到synchronized(this):synchronized语句块结束后会自动释放锁让等待池的其中一个线程获得该锁。这还需要注意一下sleep以及notify方法的区别:它们2个最重要的区别是sleep释放CPU控制权但是并为释放内存锁,而notify还释放内存锁。其实还有一个用法synchronized锁住对象的方法,可以把代码修改成如下:

    public synchronized void Purchase(String name) throws Exception{
// 模拟获取Tickets数据操作
Thread.sleep(50);
if(Tickets > 0){
// 模拟获取订单数据等操作
Thread.sleep(10);
int temp = Tickets--;
System.out.println(name + "抢到:" + temp);
// 模拟生成订单等操作
Thread.sleep(10);
}
else{
// System.out.println(name + "抢票失败!");
}
}

看到这里大家都会觉得这样使用更简单啊,是这样是简单了但是可能要考虑到的是性能问题,如果这个对象还是static的话那么久相当于synchronized(class)了,大大的降低系统运行速度,这个上面也说了,不单是对象有内存锁,连类也是存在内存锁的!

不是说实现多线程有2种方法,一是继承Thread;二是实现接口Runnable。是的下面我们把Train改成TrainRun并实现接口Runnable,代码如下:

class TrainRun implements Runnable{
/**
* 头等高级坐席
*/
private int Tickets = 10;
private Object obj = new Object();
/**
* 购买火车票
* @throws Exception
*/
public synchronized void Purchase(String name) throws Exception{
Thread.sleep(50);
if(Tickets > 0){
Thread.sleep(10);
int temp = Tickets--;
System.out.println(name + "抢到:" + temp);
Thread.sleep(10);
}
else{}
} @Override
public void run() {
try {
this.Purchase(Thread.currentThread().getName());
} catch (Exception e) {e.printStackTrace();}
}
}

main函数:

        TrainRun train = new TrainRun();
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
Thread th1 = new Thread(train);
threads[i] = th1;
}
for (int i = 100 - 1; i >= 0; i--) {
threads[i].start();
}

输出结果也是一样达到要求:

Thread-99抢到:10
Thread-0抢到:9
Thread-2抢到:8
Thread-4抢到:7
Thread-6抢到:6
Thread-8抢到:5
Thread-10抢到:4
Thread-12抢到:3
Thread-14抢到:2
Thread-16抢到:1

上面不是说了使用wait以及notify方法吗,不错说了,这3个主要用于线程间互相唤醒的功能,上面的例子中没有这个需求互相唤醒共同协调完成一件事所以也就没用,不过看下面的例子:

一个阿姨负责传递碗筷,每次传递一个,一个阿姨负责洗碗,每次洗一个,盆能最多容纳5个碗,用代码描述这2个阿姨的工作过程,先分析下,这里一个阿姨负责传递一个阿姨负责洗碗,要用到2个线程,而且是盆里的碗是共享使用的,传递一个增加一个,洗一个就减一个,洗碗的时候如果盆里没碗则等待传递完成,如果传递的时候盆里的碗数量超过5则等待洗碗完成。看代码:

Conf配置类:

class Conf{
public static int passCount = 2;//阿姨传碗筷次数
public static int washCount = 2;//阿姨洗碗筷次数
public static int couter = 5; //盆做大容量5个
}

工作Work类:

class Work{
public int count = 0;
/**
* 洗碗筷
*/
public synchronized void Wash(){
if (count < 1){
try { wait(); } catch (InterruptedException e) {e.printStackTrace();}
}
System.out.println("\t已经有碗筷:" + count + " 个");
System.out.println("\t\t正在洗碗筷:第" + count + "个");
count--;
System.out.println("\t\t还剩下碗筷:" + count + "个");
notify();
}
/**
* 传碗筷
*/
public synchronized void Pass(){
if(count >= Conf.couter){
try { wait(); } catch (InterruptedException e) {e.printStackTrace();}
}
System.out.println("\t已经有碗筷:" + count + " 个");
System.out.println("\t\t再传一个碗筷");
count++;
System.out.println("\t\t传送完毕");
notify();
}
}

传送阿姨PassAunt类:

class PassAunt extends Thread{
private Work work ;
public PassAunt(Work work){
this.work = work;
}
@Override
public void run() {
for (int i = 0; i < Conf.passCount; i++) {
this.work.Pass();
try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();}
}
}
}

洗碗阿姨WashAunt类:

class WashAunt extends Thread{
private Work work ;
public WashAunt(Work work){
this.work = work;
}
@Override
public void run() {
for (int i = 0; i < Conf.washCount; i++) {
this.work.Wash();
try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();}
}
}
}

main函数:

    public static void main(String[] args) {
Work work = new Work();
PassAunt passThread = new PassAunt(work);
WashAunt washThread = new WashAunt(work);
passThread.start();
washThread.start();
}

结果如下:

    已经有碗筷:0 个
再传一个碗筷
传送完毕
已经有碗筷:1 个
正在洗碗筷:第1个
还剩下碗筷:0个
已经有碗筷:0 个
再传一个碗筷
传送完毕
已经有碗筷:1 个
正在洗碗筷:第1个
还剩下碗筷:0个

可以看到输出传递2个洗2个,满足条件。如果现在客人突然较多,老板娘也加入传递碗筷队伍来了,那现在看调用:

    public static void main(String[] args) {
Work work = new Work();
PassAunt passThread = new PassAunt(work);
PassAunt passThread01 = new PassAunt(work);
WashAunt washThread = new WashAunt(work);
passThread.start();
passThread01.start();
washThread.start();
} class Conf{
public static int passCount = 2;//阿姨传碗筷次数
public static int washCount = 4;//阿姨洗碗筷次数
public static int couter = 5; //盆做大容量5个
}

运行多次输出:

    已经有碗筷:2 个
正在洗碗筷:第2个
还剩下碗筷:1个
已经有碗筷:1 个
正在洗碗筷:第1个
还剩下碗筷:0个

从这里看看到,我们增加了一个传递的阿姨并修改洗碗的次数washCount结果是正常。那么我们再来修改下passCount以为4及washCount为2来看看多一个阿姨洗碗结果又是怎么样呢!

class Conf{
public static int passCount = 4;//阿姨传送碗筷次数
public static int washCount = 2;//阿姨传送碗筷次数
public static int couter = 5; //盆做大容量5个
}
    public static void main(String[] args) {
Work work = new Work();
PassAunt passThread = new PassAunt(work);
WashAunt washThread = new WashAunt(work);
WashAunt washThread01 = new WashAunt(work);
passThread.start();
washThread01.start();
washThread.start();
}

结果输出可能是:

    已经有碗筷:0 个
正在洗碗筷:第0个
还剩下碗筷:-1个
已经有碗筷:-1 个
再传一个碗筷
传送完毕

奇怪竟然出现-1个碗,显然是错误的,按道理1个传递碗筷的阿姨对应2个洗碗的阿姨数量是正确的。那现在看看这个洗碗的方法哪里有问题呢,当前线程调用了wait方法,再次醒来继续往下执行,这个时候没去判断这个盆里还有多少碗,修改下使用while:

    public synchronized void Wash(){
while (count < 1){
try { wait(); } catch (InterruptedException e) {e.printStackTrace();}
}
System.out.println("\t已经有碗筷:" + count + " 个");
System.out.println("\t\t正在洗碗筷:第" + count + "个");
count--;
System.out.println("\t\t还剩下碗筷:" + count + "个");
notify();
}

并把洗碗的次数随便设置:

public static int washCount = 2;//阿姨传送碗筷次数

再次运行看结果:

    已经有碗筷:0 个
再传一个碗筷
传送完毕
已经有碗筷:1 个
正在洗碗筷:第1个
还剩下碗筷:0个

运行多次没错,最后洗碗全部洗完了!那洗碗是搞定了,传递碗筷会不会出现错误的情况呢,接下来看看同时增加一个传递碗筷的阿姨以及洗碗的阿姨,看代码:

class Conf{
public static int passCount = 2;//阿姨传送碗筷次数
public static int washCount = 2;//阿姨传送碗筷次数
public static int couter = 5; //盆做大容量5个
}
    public static void main(String[] args) {
Work work = new Work();
PassAunt passThread = new PassAunt(work);
PassAunt passThread01 = new PassAunt(work);
WashAunt washThread = new WashAunt(work);
WashAunt washThread01 = new WashAunt(work);
passThread.start();
washThread01.start();
passThread01.start();
washThread.start();
}

输出结果:

    已经有碗筷:0 个
再传一个碗筷
传送完毕
已经有碗筷:1 个
正在洗碗筷:第1个
还剩下碗筷:0个

再次把passCount为4以及washCount为2,看结果:

    已经有碗筷:2 个
再传一个碗筷
传送完毕
已经有碗筷:3 个
再传一个碗筷
传送完毕

到最后还有碗筷并为洗完,分析一下这个是由于2个传递碗筷的阿姨总共传送了8个碗筷,而2个阿姨洗碗的的总次数总共才是4,所以留下4个碗筷未洗,分析验证结果留下4个是没错!那修改下其变量值:

    public static int passCount = 4;//阿姨传送碗筷次数
public static int washCount = 4;//阿姨传送碗筷次数
public static int couter = 5; //盆做大容量5个

再看结果可能是:

    已经有碗筷:2 个
正在洗碗筷:第2个
还剩下碗筷:1个
已经有碗筷:1 个
正在洗碗筷:第1个
还剩下碗筷:0个

多次运行最后洗碗,说明正确。到这里可能大家都发现了这个运行受到passCount以及washCount的影响,可以去掉适合洗碗阿姨有就洗,传送的阿姨有万就传递这样的需求不,当然可以,看下面代码:

修改配置Conf类

class Conf{
public static int couter = 5; //盆做大容量5个
}

再修改Work类:

class Work{
//上面跟之前一样省略
/**
* 传碗筷
*/
public synchronized void Pass(){
while(count >= Conf.couter){
try { wait(); } catch (InterruptedException e) {e.printStackTrace();}
}
System.out.println("\t已经有碗筷:" + count + " 个");
System.out.println("\t\t再传一个碗筷");
count++;
System.out.println("\t\t传送完毕");
notify();
}
}

再修改PassAunt类:

class PassAunt extends Thread{
//上面跟之前一样省略
@Override
public void run() {
while(true){
this.work.Pass();
try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();}
}
}
}

再修改WashAunt类:

class WashAunt extends Thread{
//上面跟之前一样省略
@Override
public void run() {
while (true) {
this.work.Wash();
try {Thread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();}
}
}
}

再修改下mian函数:

    public static void main(String[] args) {
Work work = new Work();
PassAunt passThread = new PassAunt(work);
PassAunt passThread01 = new PassAunt(work);
PassAunt passThread02 = new PassAunt(work);
PassAunt passThread03 = new PassAunt(work);
WashAunt washThread = new WashAunt(work);
WashAunt washThread01 = new WashAunt(work);
passThread.start();
washThread01.start();
passThread01.start();
passThread02.start();
passThread03.start();
washThread.start();
}

其实就是把if修改成while,把for循环修改成while,多次运行看结果,显示中不会出现负数以及超过5个碗筷的数量,完全正确。再次测试下,new多几个传送碗筷的阿姨或者洗碗的阿姨,查看结果也正常。

这次先到这里。坚持记录点点滴滴!

Java基础知识陷阱(九)的更多相关文章

  1. Java基础知识陷阱系列

    Java基础知识陷阱系列 今天抽空把Java基础知识陷阱有关的文章汇总于此,便于大家查看. Java基础知识陷阱(一) Java基础知识陷阱(二) Java基础知识陷阱(三) Java基础知识陷阱(四 ...

  2. Java基础知识陷阱(十)

    本文发表于本人博客. 上个星期由于时间比较紧所以未能继续写下去,今天再接再厉,专心 + 坚持这样离目标就越来越近了!废话少说说正题,今天我们还是来说说java中比较基础的知识,大家知道编写java程序 ...

  3. Java基础知识陷阱(二)

    本文发表于本人博客. 上次说了一些关于字符串的知识,都是比较基础的,那这次也说下关于对象地址问题,比如传参.先看下面代码: public void changeInt(int a){ a = ; } ...

  4. Java基础知识陷阱(七)

    本文发表于本人博客. 上次说了下HashSet和HashMap之间的关系,其中HashMap这个内部有这么一句: static final float DEFAULT_LOAD_FACTOR = 0. ...

  5. Java基础知识陷阱(六)

    本文发表于本人博客. 上次说了下equals跟==的问题,今天再来认识一下这个equals()跟hasCode().上次的代码如下: class Person{ public String name; ...

  6. Java基础知识陷阱(四)

    本文发表于本人博客. 今天我们来说说关于java继承以及反射有关的问题,大家先看下下面代码,试问可以编译通过不,为什么具体说说原因? public class Test{ public static ...

  7. Java基础知识陷阱(三)

    本文发表于本人博客. 之前都讲了有关字符串的陷阱,那今天来说下关于静态这个东西,这分为静态变量.静态方法,先看下面的代码请问结果输出是什么?: class Person01{ private stat ...

  8. Java基础知识陷阱(一)

    本文发表于本人博客. 事隔好多年了,重新拿起来Java这门语言,看似熟悉其实还很陌生,想想应该梳理下顺便提高下自己.这次先来看看Java里面的String这个对象. 经典的先看下面一段代码,请问最终创 ...

  9. JAVA基础知识(九)Java 异常

    Throwable是Error和Exception的基类 Exception(异常) :是程序本身可以处理的异常. Error(错误): 是程序无法处理的错误.这些错误表示故障发生于虚拟机自身.或者发 ...

随机推荐

  1. SVN-001

    1.cd到指定目录: 2.执行:svn cleanup:

  2. windows10 专业版激活工具

    分享一个激活工具: 链接:https://pan.baidu.com/s/1HsdAKuxxsdvzZ282k7HtMg 提取码:tqe0

  3. poj_2286 IDA*

    题目大意 给定一个由数字组成的#字型网格,和一定的移动规则,问最少需要多少次移动才能达到要求的结果. 题目分析 要求最少需要几步到达结果,可以考虑广度优先搜索算法,或者迭代加深深度优先搜索(IDA*) ...

  4. 【ecshop】如何解决DEPRECATED: PREG_REPLACE()报错

    部署的ecshop  在高版本的PHP环境里边  ,访问 单个店铺时候会报错, 访问文件路径: http://www.test.com/supplier.php?suppId=5 类似这样的报错: D ...

  5. Mybatis——SQL语句构建器类

    SQL语句构建器类 问题 Java程序员面对的最痛苦的事情之一就是在Java代码中嵌入SQL语句.这么来做通常是由于SQL语句需要动态来生成-否则可以将它们放到外部文件或者存储过程中.正如你已经看到的 ...

  6. bzoj 3307 雨天的尾巴

    题目链接:传送门 题目大意:中文题,略 题目思路:网上有题解说是合并线段树的,但是太难蒟蒻不会,只能用树剖求解 如果不是树而是一维数组我们会怎么解? 当然是利用前缀和思想标记 (L) v+1,(R+1 ...

  7. 关于hql语句的一些问题

    1.student is not mapped问题: 在执行显示数据库数据的时候出错 大概提示说: errors: s.entr_Id student is not mapped 碰到这种情况一般是: ...

  8. linux如何设置用户权限

    linux与用户权限设置: 1.添加用户 首先用adduser命令添加一个普通用户,命令如下: #adduser tommy //添加一个名为tommy的用户 #passwd tommy //修改密码 ...

  9. IT公司常见的内网漏洞表格

    访问控制类漏洞与隐患 这一类漏洞与隐患属于访问控制与身份鉴别问题,一般有没有配置访问控制.访问控制弱(弱口令或者空口令),身份鉴别可以绕过等问题 漏洞协议组件 漏洞类型 漏洞评级 SSH 弱口令 严重 ...

  10. Egret资源管理解决方案

    关于egret开发H5页游,资源管理和加载的一点看法. 一 多json文件管理 二 资源归类和命名 三 exml文件编写规范 四 资源预加载.分步加载.偷载 五 资源文件group分组 六 ResUt ...