线程通信的学习

先开始创建一个任务类,里面方法有任务A,任务B,任务C,同时会相应的创建三个线程ThereadA,ThreadB,ThreadC分别去调用前面三个任务方法,然后在主方法中,每过50ms就创建一个自动生成任务的线程存入集合中,同时开启三个线程来跑。

class Task {
    int num;
    boolean flagA; // 默认为false
    boolean flagB;
    boolean flagC;
    public void taskA() {
        if (!flagA) {
            num += 10;
            flagA = true;
        }
    }
    public void taskB() {
        if (flagA && !flagB) { // B为false和A为true
            num *= 20;
            flagB = true;
        }
    }
    public void taskC() {
        if (flagB && !flagC) { // C为false和B为true
            num *= num;
            flagC = true;
        }
    }
}
// 创建三个线程类去实现任务A 任务B 任务C方法
class ThreadA implements Runnable {

    private ArrayList<Task> tasks;

    public ThreadA(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("ThreadA");
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                if(task!=null) {
                    task.taskA();
                }
            }
        }
    }
}

class ThreadB implements Runnable {

    private ArrayList<Task> tasks;

    public ThreadB(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("ThreadB");
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                if(task!=null) {
                    task.taskB();
                }
            }
        }
    }
}

class ThreadC implements Runnable {

    private ArrayList<Task> tasks;

    public ThreadC(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("ThreadC");
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                if(task!=null) {
                    task.taskC();
                }
            }
        }
    }
}

public class test {
    // 设置一个存储线程的容器
    static ArrayList<Task> tasks = new ArrayList<>();
    public static void main(String[] args) {
        // 创建一个自动生成任务的线程
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 添加任务
                    tasks.add(new Task());
                    System.out.println("已添加:" + tasks.size());
                }
            }
        }.start();
        // 启动其他三个线程
        new Thread(new ThreadA(tasks)).start();
        new Thread(new ThreadB(tasks)).start();
        new Thread(new ThreadC(tasks)).start();
        // 再生一个监听 任务完成的线程
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int count = 0;
                    for (int i = 0; i < tasks.size(); i++) {
                        Task task = tasks.get(i);
                        if (task.num == 40000) {
                            count++;
                        }
                    }
                    System.err.println("已完成:" + count);
                }
            }
        }.start();
    }
}

上述结果输出中是可以输出 “已完成” 这块的,假如我在ThreadA,ThreadB,ThreadC三个线程中都去掉输出语句呢?会发生什么?

// 创建三个线程类去实现任务A 任务B 任务C方法
class ThreadA implements Runnable {
    private ArrayList<Task> tasks;
    public ThreadA(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                if(task!=null) {
                    task.taskA();
                }
            }
        }
    }
}

class ThreadB implements Runnable {
    private ArrayList<Task> tasks;
    public ThreadB(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                if(task!=null) {
                    task.taskB();
                }
            }
        }
    }
}

class ThreadC implements Runnable {
    private ArrayList<Task> tasks;
    public ThreadC(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }

    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < tasks.size(); i++) {
                Task task = tasks.get(i);
                if(task!=null) {
                    task.taskC();
                }
            }
        }
    }
}

// 这里out是打印输出流的常量对象 默认为null
public final static PrintStream out = null;
// 此时,再来看看PrintStream的源码
// 里面有一个方法 println
    public void println() {
        newLine();
    }
// 再追溯newLine()的源码
    private void newLine() {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.newLine();
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush)
                    out.flush();
            }
        }
    }
由刚才所学的,我们可以发现这原来有一个sychronized代码块,锁住的this就是PrintStream类对象。

所以我们可以大胆猜测一下?是不是因为这个锁使得各个线程停顿那么一些时间从而使得每个线程中的集合数据被更新了呢?答案就是这样的,如果没有加这个输出语句的时候,自动创建任务的线程中是虽然tasks.add(new Task()); 但是却没有“通知其他的线程” ,从而tasks就没办法及时更新。也就是上面ThreadA到ThreadC的死循环中的task.size() 没有更新。\n\n我们可以将输出语句换成sychronized(tasks) {} 代替看看是否能输出 “已完成” 这块的值。读者可以自行去试试。(答案是可以的)\n\n但是问题又来了,这样写输出语句和sychronized代码块在上面就很突兀。有没有什么可以代替呢?volatile关键字就来了。线程的可见性就来了!\n\n

多线程之间如何实现通讯\n系统要实现某个全局功能必定要需要各个子模块之间的协调和配合,就像一个团队要完成某项任务的时候需要团队各个成员之间密切配合一样。而对于系统中的各个子线程来说,如果要完成一个系统功能,同样需要各个线程的配合,这样就少不了线程之间的通信与协作。常见的线程之间通信方式有如下几种:\n\n通过共享资源进行忙等待(Busy Wait)\nwait和notify/notifyAll\nawait和signal/signalAll\nsleep/yield/join\nCyclicBarrier 栅栏\nCountDownLatch 闭锁\nSemaphore 信号量\nSocket套接字进行网络通信\n1、通过共享资源进行忙等待(Busy Wait)\n线程间发送信号的一个简单方式是直接在共享内存中操作对象。线程A在一个同步块里设置boolean型成员变量flag为true,线程B也在同步块里读取flag这个成员变量。\n\n准备处理数据的线程B正在等待数据变为可用。换句话说,它在等待线程A的一个信号,这个信号使getFlag()返回true。线程B运行在一个循环里,以等待这个信号。\n\n2、wait和notify/notifyAll\nwait和notify/notifyAll是Object的方法,任何一个对象都具有该方法。在使用的时候,首先需要设置一个全局锁对象,通过对该锁的释放和持有来控制该线程的运行和等待。因此在调用wait和notify的时候,该线程必须要已经持有该锁,然后才可调用,否则将会抛出IllegalMonitorStateException异常。\n确定要让哪个线程等待?让哪个线程等待就在哪个线程中调用锁对象的wait方法。调用wait等待的是当前线程,而不是被调用线程,并不是theread.wait()就可以让thread等待,而是让当前线程(实际执行wait方法的线程,而不是调用者的那个线程对象)进行等待。尽量不要把线程对象当做全局锁使用,以免混淆等待线程。\n\n\n3、await和signal/signalAll\nawait和signal是Condition的两个方法,其作用和wait和notify一样,目的都是让线程挂起等待,不同的是,这两种方法是属于Condition的两个方法,而Condition对象是由ReentrantLock调用newCondition()方法得到的。Condition对象就相当于前面所说的中介,在线程中调用contiton.await()和condition.signal()可以分别使线程等待和唤醒。\n\n4、sleep/yield/join\n对于sleep()方法应该很熟悉了,让当前线程睡眠一段时间。期间不会释放任何持有的锁。\n\n对于yield()方法可能使用的情况少一下。其作用主要是让当前线程从运行状态转变为就绪状态,由线程调度重新选择就绪状态的线程分配CPU资源。至于最终会选取哪个线程分配CPU资源就由调度策略来决定了,有可能还是该线程,有可能换为其它线程。\n\n对于join方法,作用是暂停当前线程,等待被调用线程指向结束之后再继续执行。\n\n使用join的时候需要注意:\n\n1、调用join的时候,当前线程不会释放掉锁,如果调用线程也需要该锁则就会导致死锁!\n\n2、join方法不会启动调用线程,所以,在调用join之前,该调用线程必须已经start启动,否则不会达到想要的效果。\n\njoin的底层实际是就是使用了一个自旋等待机制,判断调用线程是否死亡,如果没有则一直让当前线程wait。可以看一下底层实现源码:\n\n5、CyclicBarrier栅栏\nCyclicBarrier字面理解为线程屏障,当指定数量的线程执行到指定位置的时候,才能触发后续动作的进行。其最终目的是让所有线程同时开始后续的工作。\n\n例如:三个员工来公司开会,由于三人住的地方与公司距离不同,所以到会议室的时间也不同。而会议开始必须等待三者都到达会议室之后才能进行。\n\n6、CountDownLatch闭锁\n与CycliBarrier不同的是CountDownLatch是某一个线程等待其他线程执行到某一位置之后,该线程(调用countDownLatch.await();等待的线程)才会继续后续工作。而CycliBarrier是各个线程执行到某位置之后,然后所有线程一齐开始后续的工作。相同的是两者都属于线程计数器。\n\n使用示例如下: boss等待所有员工来开会,当所有人员都到齐之后,boss宣布开始会议!!!\n\n7、Semaphore 信号量\nSemaphore在线程协作方面主要用于控制同时访问临界区资源的线程个数。信号量是属于操作系统层面的概念,jdk提供了操作接口。\n\n根据结果可以看出只有当有线程释放资源之后,才会有新的线程获取到资源。即控制了同一时间访问临界区资源的线程数量。当Semaphore(1)设置为1的时候,此时可以当做锁来使用。\n\n8、Socket套接字\n利用Java的socket套接字的方式来实现,也就是常用的tcp和udp这些协议的封装,这种一般用于分布式系统,单机环境下线程通迅用这个就有点浪费和复杂了。\n\n多线程通信带来的问题\n多个线程同一时刻对同一份资源进行操作时,如果跟我们预期的结果不一样,就会产生线程不安全的问题。有可能出现多个线程先后更改数据造成所得到的数据是脏数据。\n同时线程安全的问题只会出现在多线程环境中。\n举个例子:现在有两个线程分别是线程1和线程2,他们同时对一个共享变量count直接进行多次count++的操作,我们知道cout++的操作是分为三个步骤的:\n\n读:线程读取count的值到自己的工作内存\n改:线程在工作内存中对count进行加一的操作\n写:线程将计算结果写入共享变量所在内存\n\n上图一看并没有什么问题,但如果在线程1进行读后,假如他现在看到的count值为6,此时线程2也开始读,它看到的也是6,随后线程2继续进行改和写的操作,此时共享内存中的count值变为了7,但是线程1还是在对6这个值进行操作,这就产生了线程安全的问题。\n\n\n多线程通信导致线程不安全的原因\n线程对共享变量的所有操作都在自己的工作内存中进行,不是直接从主内存中读写\n不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量的传递主要通过主内存来完成\n多线程下的代码执行顺序是不确定的\n怎样解决线程不安全\n对多线程下共享的资源加锁。使用 synchronized 包围对应代码块,保证多线程之间是互斥的。每次只能有一个线程访问其共享资源。如多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,而且其他的变量的值也和预期的是一样的,不存在执行程序时出现意外结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值