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

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. 6-3 bash脚本编程之五 字符串测试及for循环

    1. 字符测试 ==:等号两边要有空格,否则会被认为是赋值. !=:  测试是否相等,记住如果不等为真,等为假. -n string: 测试指定字符串是否为空,空位真,不空为假. -s string: ...

  2. 搭建SVN服务器

    系统环境:CentOS 6.6   首先查看服务器上是否已安装了svn # rpm -qa subversion 如果没有安装,则执行此命令 # yum list subversion         ...

  3. Android使用C++截屏并显示

    使用android底层自带的截屏源码进行修改后,将截取屏幕的内容再次显示在屏幕上,使屏幕呈现出暂停的效果. android自带的截屏代码在android\JB\frameworks\base\cmds ...

  4. Collections.shuffle

    1.Collections.shuffler 最近有个需求是生成十万级至百万级的所有随机数,最简单的思路是一个个生成,生成新的时候排重,但是这样时间复杂度是o(n^2),网上看了几个博客的解决方法都不 ...

  5. POJ 2125 Destroying the Graph 二分图最小点权覆盖

    Destroying The Graph Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 8198   Accepted: 2 ...

  6. [LeetCode] Peeking Iterator 顶端迭代器

    Given an Iterator class interface with methods: next() and hasNext(), design and implement a Peeking ...

  7. [LeetCode] Minimum Path Sum 最小路径和

    Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which ...

  8. Spring Aspectj切入点语法定义

    在使用spring框架配置AOP的时候,pointcut"切入点" 例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..)) ...

  9. vector迭代器用法

    #include<iostream> #include<vector> using namespace std; int main() { vector<int> ...

  10. Mysql 修改字段默认值

    环境:MySQL 5.7.13 问题描述:建表的时候,users_info表的role_id字段没有默认值,后期发现注册的时候,需要提供给用户一个默认角色,也就是给role_id字段一个默认值. 当前 ...