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

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. 基于TCP的网络编程

    HTTP协议,FTP协议等很多广泛应用的协议均基于TCP协议.TCP编程主要为C/S模式,客户端和服务器之间的程序设计存在较大差异. TCP编程框图 服务器调用socket().bind().list ...

  2. 《Note --- Unreal 4 --- PersonaHowToMovement》

    https://docs.unrealengine.com/latest/CHN/Gameplay/HowTo/CharacterMovement/index.html 这里的demo是按照一些per ...

  3. Struts2中的Ognl

    OGNL(Object-Graph Navigation Language)全称为对象图导航语言,是一种功能强大的表达式语言,它通过简单一致的语法,可以任意存取对象的属性或者调用对象的方法,能够遍历整 ...

  4. 第10章 Shell编程(3)_字符处理命令和条件判断

    3. 字符处理命令 3.1 排序命令:sort (1)sort命令:#sort [选项] 文件名 选项 作用 -f 忽略大小写 -n 以数值型进行排序,默认使用字符串型排序 -r 反向排序 -t 指定 ...

  5. November 1st 2016 Week 45th Tuesday

    Difficult circumstances serve as a textbook of life for people. 艰难坎坷是人们的生活教科书. It would be better if ...

  6. [LeetCode] Shuffle an Array 数组洗牌

    Shuffle a set of numbers without duplicates. Example: // Init an array with set 1, 2, and 3. int[] n ...

  7. [LeetCode] Palindrome Partitioning II 拆分回文串之二

    Given a string s, partition s such that every substring of the partition is a palindrome. Return the ...

  8. 调用altera IP核的仿真流程—上

    调用altera IP核的仿真流程—上 在学习本节内容之后,请详细阅读<基于modelsim-SE的简单仿真流程>,因为本节是基于<基于modelsim-SE的简单仿真流程>的 ...

  9. chrome中获取元素的样式

    以获取背景颜色为例 html部分 <div id="test">abcd</div> css部分 #test { background-color: rgb ...

  10. 1001 数组中和等于K的数对 1090 3个数和为0

    二分查找.对数组每个V[i],在其中查找K-V[i],查找完成后修改v[i]避免重复输出 #include<iostream> #include<algorithm> #inc ...