在刚刚学线程的时候我们经常会碰到这么一个问题:模拟火车站售票窗口售票。代码如下:

package cn.blogs.com.isole;
/*
 模拟火车站售票窗口售票,假设有50张余票
 */

public class _synchronizeds {
    public static void main(String[] args) {
        //创建3个线程对象,分别代表售票的3个窗口
        Ticket t1 = new Ticket("窗口1");
        Ticket t2 = new Ticket("窗口2");
        Ticket t3 = new Ticket("窗口3");
        //开启线程,开始卖票
        t1.start();
        t2.start();
        t3.start();
    }
}
//售票
class Ticket extends Thread{
    //定义剩余票,静态的
    static int num=50;
    //构造方法为线程重新命名
    public Ticket(String name){
        super(name);
    }
    //重写run()方法,将自定义线程代码写入
    @Override
    public void run() {
        while(true){
            if(num>0){
                num--;
                System.out.println(Thread.currentThread().getName()+"卖出了"+num+"号票");
            }else{
                System.out.println("票售罄了");
                break;
            }
        }
    }
}

多次启动线程测试发现问题如下图:

这个就是线程的安全问题了!

在什么情况下才可能出现线程安全问题:
  1.必须存在多个线程对象,而且线程之间共享着一个资源
  2.必须存在多个语句操作了共享资源,如下方if内的2句代码
线程安全的解决方案:
  思路:在共享资源里设定在一个时间片只允许一个线程完全操作完毕之后才允许其他线程操作
  sun提供了线程同步机制让我们解决这类线程安全问题的:
  方式1.同步代码块:
  同步代码块的格式:
  synchronized(锁对象){
    需要被同步的代码...
  }
要注意的事项:

  1.任意的一个对象都可以作为锁对象(凡是对象 内部都维护了一个状态的,例如state = 1 开 0关
    java的同步机制就是使用了对象中的状态作为了锁的标识)
  2.在同步代码块中调用了sleep方法并不会释放锁对象的,而是暂停执行一段时间再继续执行
  3.只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率 因为每次都得判断锁的状态是开还是关
  4.多线程操作的锁对象必须是唯一共享的,否则无效(static)因为不同的话只能锁自己的锁

方式2.同步函数:就是使用synchronized修饰函数

  同步函数要注意的事项:
  1.如果是一个非静态的同步函数的锁 对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)
  2.同步函数的锁对象是固定的,不能被指定
针对以上2种方案推荐使用:同步代码块
原因.同步代码块的锁对象可以由我们随意指定,方便控制

以下代码即使用同步代码块解决上述的问题:

package cn.blogs.com.isole;
/*
 模拟火车站售票窗口售票,假设有50张余票
 */

public class _synchronizeds {
    public static void main(String[] args) {
        //创建3个线程对象,分别代表售票的3个窗口
        Ticket t1 = new Ticket("窗口1");
        Ticket t2 = new Ticket("窗口2");
        Ticket t3 = new Ticket("窗口3");
        //开启线程,开始卖票
        t1.start();
        t2.start();
        t3.start();
    }
}
//售票
class Ticket extends Thread{
    //定义剩余票,静态的
    static int num=50;
    //构造方法为线程重新命名
    public Ticket(String name){
        super(name);
    }
    //重写run()方法,将自定义线程代码写入
    @Override
    public void run() {
        while(true){
            synchronized("锁的对象可以是任意的,但是要共享"){//锁开始的位置,当线程执行到这里的时候,锁的状态值变成0代表不可进入
                if(num>0){
                    num--;
                    System.out.println(Thread.currentThread().getName()+"卖出了"+num+"号票");
                }else{
                    System.out.println("票售罄了");
                    break;
                }
            }//锁的结束位置,当线程执行到这里,锁的状态变成1代表可进入
        }
    }
}

  这个时候执行就不会出现线程安全的相关问题了

线程的通讯机制(模拟生产者与消费者之间的产品关系)代码如下:

class Product{
    String name;//名字
    double price;//价格
    boolean flag = false;//产品生产的标识
}
//生产者
class Producer extends Thread{
    Product p ; //产品
public Producer(Product p){
    this.p = p;
}
    public void run(){
        int i = 0;
        while(true){
            synchronized(p) {
            if(p.flag == false){//如果没有生产
                if(i%2==0){
                p.name = "苹果";
                p.price = 6.5;
                }else{
                    p.name = "香蕉";
                    p.price = 1.1;
                    }
            System.out.println("生产者生产出了:"+p.name+"价格是:"+p.price);
            i++;
            p.flag=true;
            p.notify();//唤醒消费者去消费
            }else{
                //生产者生产完毕,等待消费者消费
                try {
                    p.wait();//由锁对象调用
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
                }
            }//锁结束
        }//循环结束
    }
}
//消费者
class Customer extends Thread{
    Product p;
    public Customer(Product p){
    this.p = p;
    }
    public void run(){
        while(true){
            synchronized(p){
            if(p.flag == true){//如果有生产完毕的产品
                System.out.println("消费者消费了:"+p.name+"价格:"+p.price);
                p.flag = false;
                p.notify();//唤醒生产者生产
            }else{
                //产品还没有生产,应该等待生产者先生产
                try {
                    p.wait();//消费者等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }//锁结束
            }
    }
}

public class _8Demo_Thread4 {
    public static void main(String[] args) {
        Product p = new Product();//产品
        Producer p1 = new Producer(p);//生产者
        Customer p2 = new Customer(p);//消费者
        p1.start();
        p2.start();
    }
}

线程的通讯:一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务

生产者-消费者 生产者的产品给消费者使用 产品是共享的

问题1:出现了线程安全问题(价格错乱了)
  加synchronized锁住 且锁的对象使用p
问题2:生产者生产完产品之后才能被消费者使用
  线程通信的2个方法

  wait(); //等待 如果执行则该线程进入等待的状态 必须要其他线程调用notify()才能唤醒

  notify(); 唤醒等待的线程
wait()与notify()方法要注意的事项:
1.这2个方法是属于Object对象的 原因:锁的对象是我们自己定义的,而不是Thread定义的,所以调用这两个方法的对象可能是任意的 而任意的类都是Object类的子类
2.wait方法与notify方法必须在同步代码块或者是同步函数中才能使用
3.wait方法与notify必须要由锁对象调用
wait:一个线程如果执行了wait方法,那么该线程就会进去一个以"锁对象"为标识符的线程池中等待 一旦执行wait会释放锁
notify();如果一个线程执行notify方法,那么就会唤醒以锁对象为标识符的线程池中等待线程中其中一个
notifyAll(); //唤醒线程池中所有等待的线程

附:死锁问题

java的同步机制解决了线程安全问题,但是也同时引发死锁的现象
死锁现象存在的根本原因:
  1.存在2个或2个以上的线程
  2.存在的共享资源个数大于等于2个
死锁现象的解决方案:没有方案,只能尽量的避免发生而已...

附:守护线程

1.守护线程:就是main同生共死,随着main的结束而结束,而普通线程是在任务代码执行结束才停止。

2.用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。

例如:我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。

Java多线程--线程安全问题的相关研究的更多相关文章

  1. Java多线程——线程安全问题

    一.什么情况下会产生线程安全问题? 同时满足以下两个条件时: 1,多个线程在操作共享的数据.2,操作共享数据的线程代码有多条. 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导 ...

  2. Java多线程--线程及相关的Java API

    Java多线程--线程及相关的Java API 线程与进程 进程是线程的容器,程序是指令.数据的组织形式,进程是程序的实体. 一个进程中可以容纳若干个线程,线程是轻量级的进程,是程序执行的最小单位.我 ...

  3. Java多线程——线程之间的协作

    Java多线程——线程之间的协作 摘要:本文主要学习多线程之间是如何协作的,以及如何使用wait()方法与notify()/notifyAll()方法. 部分内容来自以下博客: https://www ...

  4. Java多线程——线程之间的同步

    Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...

  5. java 多线程—— 线程让步

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  6. java 多线程—— 线程等待与唤醒

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  7. Java基础-线程安全问题汇总

    Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...

  8. Java多线程-线程的同步(同步方法)

    线程的同步是保证多线程安全访问竞争资源的一种手段.线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些 ...

  9. Java多线程——线程的优先级和生命周期

    Java多线程——线程的优先级和生命周期 摘要:本文主要介绍了线程的优先级以及线程有哪些生命周期. 部分内容来自以下博客: https://www.cnblogs.com/sunddenly/p/41 ...

随机推荐

  1. WPF 自定义BarChartControl(可左右滑动的柱状图)

    自定义可左右滑动.拖拽滑动的平面柱状图 在做这种样式控件之前,可先浏览我之前预研的控件: A.自定义左右滑动ScrollViewer(可拖动滑动) B.自定义Bar柱状图 OK,现在说下控件具体设计过 ...

  2. Linux 查看命令源码

    一.简介 有时候想看看ls.cat.more等命令的源代码,本文介绍相应查看方法. 二.方法 参考: http://blog.csdn.net/silentpebble/article/details ...

  3. [转]C# 使用Nlog记录日志到数据库

    本文转自:http://www.cnblogs.com/weixing/archive/2013/04/26/3044422.html 摘要]Nlog是一个很不错的.NET日志记录组件,它可以将日志输 ...

  4. Common Bugs in C Programming

    There are some Common Bugs in C Programming. Most of the contents are directly from or modified from ...

  5. C语言中 指向函数的指针 简介

    引子:在学习CPrimerPlus的第十四章的14.13节中,遇到了如下三行文字,是有关指向函数的指针的,把我搞晕了. char * fump(); //返回指向char的指针的函数 char (* ...

  6. JavaScript OOP 之「创建对象」

    工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题. function createPers ...

  7. ping环回地址和ping主机地址的区别

    ping127.0.0.1和ping本机的过程是不一样的ip输出函数先检查地址是不是环回地址1.如果是环回地址 直接交给环回驱动程序处理 返回ip输入函数2.如果不是环回地址 检查是不是广播或者多播地 ...

  8. 判断是pc端还是手机端,并跳转到相应页面

    <!-- 判断浏览器是否为手机端 -->  <script>     // class     ! function(navigator) {         var user ...

  9. KMP算法实现

    链接:http://blog.csdn.net/joylnwang/article/details/6778316 KMP算法是一种很经典的字符串匹配算法,链接中的讲解已经是很明确得了,自己按照其讲解 ...

  10. MySQL备忘

    Access denied for user 'root'@'localhost' >> 执行以下语句 GRANT ALL ON dbname.* TO 'root'@'localhost ...