java 多线程的学习(2)

本文详细介绍了线程同步的基本概念和技术实现,包括同步代码块的使用、线程状态的变化及线程间的通信机制。通过具体示例展示了如何解决线程安全问题,以及如何运用等待唤醒机制实现线程间的有序交互。

今天主要来学习一下线程同步,线程状态。以及线程通信

一、线程同步

package com.ticket2;
/*
 * 如何解决线程安全问题呢?
 * 
 * 要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
 * A:是否是多线程环境
 * B:是否有共享数据
 * C:是否有多条语句操作共享数据
 * 
 * 我们来回想一下我们的程序有没有上面的问题呢?
 * A:是否是多线程环境   是
 * B:是否有共享数据    是
 * C:是否有多条语句操作共享数据  是
 * 
 * 由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。
 * 接下来才是我们要想想如何解决问题呢?
 * A和B的问题我们改变不了,我们只能想办法去把C改变一下。
 * 思想:
 *      把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
 * 问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
 * 
 * 同步代码块:
 *      synchronized(对象){
 *          需要同步的代码;
 *      }
 * 
 *      A:对象是什么呢?
 *          我们可以随便创建一个对象试试。
 *      B:需要同步的代码是哪些呢?
 *          把多条语句操作共享数据的代码的部分给包起来
 * 
 *      注意:
 *          同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。多个线程必须是同一把锁。
 */
public class Test {

    public static void main(String[] args) {
        TicketRunnable runnable=new TicketRunnable();

        Thread t1=new Thread(runnable);
        t1.setName("111窗口");

        Thread t2=new Thread(runnable);
        t2.setName("222窗口");

        Thread t3=new Thread(runnable);
        t3.setName("333窗口");

        t1.start();

        t2.start();

        t3.start();
    }

}

package com.ticket2;
/**
 * synchronized(一个对象,代表锁){
 *    //代码块
 * }
     两个窗口卖100张票 加锁实现同步
 */
public class TicketRunnable implements Runnable{

    private int tickets=100;
    private Object lock=new Object();

    @Override
    public void run() {
        while(true) {
            synchronized (lock) {//锁
                if(tickets>0) {
                    try {
                        //1和2都在睡
                        Thread.sleep(100);//模拟售票阿姨休息
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在销售 "+(tickets--)+"张票");
                }
            }

        }
    }

    private synchronized void test() {

    }

}

注意:这里实现多线程 要采用实现Runnable接口的方式来实现,目的是为了实现资源的共享。

二:线程状态
1.新建状态:线程对象已经创建,还没有在其上调用start()方法。
2.可运行状态:当线程有资格运行,但调度程序还没有把他选定为运行线程时线程所处的状态。当start()调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待、或睡眠状态回来后,也返回到可运行状态。
3.运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程时所处的状态。这也是线程进入运行状态的唯一一种方式。
4.等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这三个状态组合为一种。其共同点是:线程仍旧是活的,但是当前没有条件运行,换句话说,他是可运行的。但是如果某件事情出现,他可能返回到可运行状态。
5.死亡状态:当线程的run()方法完成时,就认为它死去。这个线程对象也许是活的,但是,他已经不是单独执行的进程,线程一旦死亡,就不能复生。如果在一个死去的线程调用start()方法,会抛出java.lang.ILLegaIthreadStateException。

三:线程通信(利用wait,notify通信)

生产者–消费者模式

package www.cn.ftt;

public class Student {
    String name;
    int age;
    boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}





package www.cn.ftt;

public class SetThread implements Runnable {

    private Student s;
    private int x = 0;

    public SetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if(s.flag){
                    try {
                        s.wait(); //t1等着,释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (x % 2 == 0) {
                    s.name = "丽丽";
                    s.age = 27;
                } else {
                    s.name = "郜同学";
                    s.age = 30;
                }
                x++; 

                //修改标记
                s.flag = true;
                //唤醒线程
                s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
            }

        }
    }
}




package www.cn.ftt;

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if(!s.flag){
                    try {
                        s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //既然有,那我就消费!!消费掉
                System.out.println(s.name + "---" + s.age);


                //消费掉之后就没有了,所以我把标记改为false
                s.flag = false;
                //既然没有了,那么就唤醒线程(生产者)进行生产
                s.notify(); //唤醒t1
            }
        }
    }
}




package www.cn.ftt;

/*
 * 解决方案:
 *      加锁。
 *      注意:
 *          A:不同种类的线程都要加锁。
 *          B:不同种类的线程加的锁必须是同一把。
 * 
 * 想依次的一次一个输出。
 * 
 * 如何实现呢?
 *      通过Java提供的等待唤醒机制解决。
 * 
 * 思路:
 * 
 * 生产者:先看是否有数据,如果有就等待;没有就生产,生产完之后通知消费者来消费
 * 
 * 消费者:先看是否有数据,如果有就消费;如果没有就等待,通知生产者生产数据
 * 
 * 等待唤醒:
 *      Object类中提供了三个方法:
 *          wait():等待
 *          notify():唤醒单个线程
 *          notifyAll():唤醒所有线程
 *      为什么这些方法不定义在Thread类中呢?
 *          这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
 *          所以,这些方法必须定义在Object类中。
 * 
 * 小结:
 * wait()被调用后,会自动释放所持有的锁。
 * notify()方法被调用后,不会自动释放锁。
 */
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();

        //设置和获取的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}

这些知识点并不是太全面,只是自己在学习的时候做的一个总结。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值