本文发表于本人博客

今天我来说说关于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. swift - UIAlertController 的用法

    ios 8 以后苹果官方建议使用UIAlertController这个类,所以专门去网上找资料,了解了下用法, 1.创建一个alertController let alertController = ...

  2. python远程登录服务器(paramiko模块安装和使用)

    转自:http://www.jb51.net/article/46285.htm 一:简介 由paramiko是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器 ...

  3. lodash(二)对象+循环遍历+排序

    前言: lodash(一)中只是研究了array中的多种方法,接下来就是经常用到的循环遍历问题 过程: 1._.forEach(collection, [iteratee=_.identity], [ ...

  4. js获取一个字符串中指定字符第n次出现的位置

    function nthIndexOf(str,c,n){ var x=str.indexOf(c); for(var i=0;i<num;i++){ x=str.indexOf(c,x+1); ...

  5. LNMP一键安装包phpMyAdmin无法正常登录,提示:您的Session已过期,请再次登录。

    找到文件: /usr/local/php/etc/php.ini 搜索: session.auto_start = 0 修改为 session.auto_start = 1 保存即可!

  6. Docker 使用指南 (三)—— 网络配置

    版权声明:本文由田飞雨原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/95 来源:腾云阁 https://www.qclou ...

  7. webpack打包jquery并引用

    一,引入webpack插件 //打包第三方 const CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlug ...

  8. mysql 之 group by 性能优化 查询与统计分离

    背景介绍 记录共128W条!   SELECT cpe_id, COUNT(*) restarts FROM business_log WHERE operate_time>='2012-12- ...

  9. 【BZOJ1645】[Usaco2007 Open]City Horizon 城市地平线 离散化+线段树

    [BZOJ1645][Usaco2007 Open]City Horizon 城市地平线 Description Farmer John has taken his cows on a trip to ...

  10. 【BZOJ4698】Sdoi2008 Sandy的卡片 后缀数组+RMQ

    [BZOJ4698]Sdoi2008 Sandy的卡片 Description Sandy和Sue的热衷于收集干脆面中的卡片.然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积攒卡 ...