个人笔记--多线程(安全和通信)

Posted kz2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了个人笔记--多线程(安全和通信)相关的知识,希望对你有一定的参考价值。

线程安全问题:

由于多个线程在操作共享数据,且多条语句对共享数据进行运算,所以产生了多线程安全问题,从而有了同步代码块。

同步的前提:

至少两个线程。

必须保证是使用同一个锁。

补充:多次start()同一个线程是非法的。

 

SimpleDateFormat是不是线程安全的?

SimpleDateFormat不是线程安全的。用户应为每个线程创建一个单独的实例。格式化同一个时间,如果格式化出来的结果不一致的或者抛出了异常,就证明SimpleDateFormat确实是线程不安全的。

例子:

public class SimpleDateFormatTest { 
public static void main(String[] args) { 
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
String dateTime = "2016-12-30 15:35:34"; 
for (int i = 0; i < 5; i++) { 
new Thread(new Runnable() { 
@Override public void run() { 
for (int i = 0; i < 5; i++) {
 try { 
System.out.println( Thread.currentThread().getName() + "\t" + dateFormat.parse(dateTime)); 
} catch (ParseException e) { 
e.printStackTrace(); 
} 
} 
} 
}).start(); 
} 
} 
}

运行结果(略)

结论:

可以发现,不光是抛出了异常,而且打印出来的结果存在不一致的现象。看来,SimpleDateFormat果然是线程不安全的。

在SimpleDateFormat的父类DateFormat中可以看到一个成员变量,注释说,日历是用于格式和解析时用的。

另外,因为日历作为一个成员变量,在多线程场景下,会发生资源共享造成前后不一致的问题。这就是SimpleDateFormat线程不安全的原因。

 

如何避免:

使用ThreadLocal来存放SimpleDateFormat。ThreadLocal的特性决定了每个线程操作ThreadLocal中的值,不会影响到别的线程。

ThreadLocal是一个关于创建线程局部变量的类。

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

 

同步代码块和同步函数的特点?

同步代码块使用的锁可以是任意对象(但不能用匿名对象)。

同步函数(在方法上加synchronized修饰就行)使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

 

线程间的通信:

思路:多个线程在操作同一个资源,但是操作的动作却不一样。

将资源封装成对象。  

将线程执行的任务(任务其实就是run方法)也封装成对象。

 

等待唤醒机制:

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程。

 

wait和sleep区别:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

sleep:线程会释放执行权,但是不释放锁。

 

问:在静态方法上使用同步时会发生什么事?

答:同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,

线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。

 

问:当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?

答:可以,一个非同步方法总是可以被调用而不会有任何问题。

 

例子1:

/**
* 三个老师,发80份卷子,要求每个老师轮流发.
*/
public class Test04 {
    public static void main(String[] args) {
        final Paper p = new Paper();
        new Thread("张老师") {
            public void run() {
                try {
                    p.send1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
        
        new Thread("龚老师") {
            public void run() {
                try {
                    p.send2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
        
        new Thread("刘老师") {
            public void run() {
                try {
                    p.send3();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }
}

class Paper {
    private int paper = 80;
    private int flag = 1;
    public void send1() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (flag != 1) {
                    this.wait();
                }
                if (paper == 0) {
                    flag = 2;
                    this.notifyAll();
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "发了第"
                        + paper-- + "份卷子");
                flag = 2;
                this.notifyAll();
            }
        }
    }

    public void send2() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (flag != 2) {
                    this.wait();
                }
                if (paper == 0) {
                    flag = 3;
                    this.notifyAll();
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "发了第"
                        + paper-- + "份卷子");
                flag = 3;
                this.notifyAll();
            }
        }
    }

    public void send3() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (flag != 3) {
                    this.wait();
                }
                if (paper == 0) {
                    flag = 1;
                    this.notifyAll();
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "发了第"
                        + paper-- + "份卷子");
                flag = 1;
                this.notifyAll();
            }
        }
    }
}

 

例子2:

/**总和 = 从1加到10的和  +  从11加到20的和
*编写10个线程, 第一个线程从1加到10, 第二个线程从11加到20, ...,
*第十个线程从91加到100, 最后再把10个线程的结果相加输出到控制台上
*/
public class Test05 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            XiangJia xj = new XiangJia();
            xj.setName("线程" + (i + 1));
            xj.setInit(i * 10);
            xj.start();
        }
        Thread.sleep(10);
    }
}

class XiangJia extends Thread {
    static int total;
    int inti;
    public void setInit(int inti) {
        this.inti = inti;
    }
    @Override
    public void run() {
        synchronized (XiangJia.class) {
            int sum = 0;
            for (int i = 1; i <= 10; i++) {
                sum += inti + i;
            }
            total += sum;
            System.out.println(getName() + "...total=" + total);
        }
    }
}

 

例子3:

/**
* 某包子店铺生意火爆,现开5个窗口模拟售卖100个包子,每次每个窗口随机卖出1-5个包子,
        卖完最后一个包子后提示”包子已售完”(必须全部卖出),程序结束.
*/
public class Test06 {
    public static void main(String[] args) {
        Baozi bz1 = new Baozi("窗口1");
        Baozi bz2 = new Baozi("窗口2");
        Baozi bz3 = new Baozi("窗口3");
        Baozi bz4 = new Baozi("窗口4");
        Baozi bz5 = new Baozi("窗口5");
        bz1.start();
        bz2.start();
        bz3.start();
        bz4.start();
        bz5.start();
    }
}

class Baozi extends Thread {
    Baozi(String str) {
        super(str);
    }

    // 包子总数
    static int baozi = 100;
    Random r = new Random();
    @Override
    public void run() {
        while (true) {
            synchronized (Baozi.class) {
                if (baozi <= 0) {
                    break;
                }
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int nextInt = r.nextInt(5) + 1;
                if (baozi <= nextInt) {// 此时包子已经卖到不足5个
                    nextInt = baozi;
                    System.out.println(this.getName() + "卖了" + nextInt + "个");
                    System.out.println("包子已经卖完!!!!");
                } else {
                    // 此时包子是大于5个的
                    System.out.println(this.getName() + "卖了" + nextInt + "个");
                }
                baozi -= nextInt;
                System.out.println(baozi);
            }
        }
    }
}

 

以上是关于个人笔记--多线程(安全和通信)的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程 —— 线程安全线程同步线程间通信(含面试题集)

Java并发编程之线程安全线程通信

多线程笔记一篇

尚硅谷JUC高并发编程学习笔记线程通信与集合线程安全

尚硅谷JUC高并发编程学习笔记线程通信与集合线程安全

尚硅谷JUC高并发编程学习笔记线程通信与集合线程安全