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