第三次作业
第一题-问题描述 结果应该为: 线程 1: 1 线程 1: 2 线程 1: 3 线程 1: 4 线程 1: 5 线程 2: 6 线程 2: 7 线程 2: 8 线程 2: 9 线程 2: 10 ... 线程 3: 71 线程 3: 72 线程 3: 73 线程 3: 74 线程 3: 75 启动 3 个线程打印递增的数字, 线程 1 先打印 1,2,3,4,5, 然后是线程2 打印 6,7,8,9,10, 然后是 线程 3 打印 11,12,13,14,15。 接着再由线程 1 打印 16,17,18,19,20....以此类推, 直到打印到 75。 程序的输出如右方所示。 请用多种方法(synchronized、Lock 等)控制 线程,实现输出打印。 给出源代码(对关键代码进行说明)、编译 环境以及执行时间。 总结保证线程安全、同步的方法。 基本所有同学都能给出2-3种实现方法,并给出代码、编译环境和打印结果。
第一题-实现方法1 通过全局变量或标志位来 指定当前由哪一个线程打 印数据。 class OrderPrint extends Thread { private static int who = 1; // whose turn to print private static int number = 1; // number to print private int limit; // how many numbers I can print private int count; // how many numbers printed so far private int me; // self id number public OrderPrint(int total, int id) { limit = total; me = id; count = 0; } // indicates which thread to print numbers public int whoseTurn() { return who; } // set next thread to run public void setWho(int turn) { who = turn; } public void run() { while (true) { if (whoseTurn() == me) { // my turn to print numbers for (int i = 0; i < 5; i++) { System.out.println("线程" + me + ": " + number); count++; number++; } setWho(me+1 > 3 ? 1 : me+1); // set who to next one to print if (count >= limit) // enough number have been printed return ; public static void main(String[] args) { // each thread prints total of 75/3 numbers new OrderPrint(75/3, 1).start(); new OrderPrint(75/3, 2).start(); new OrderPrint(75/3, 3).start(); 通过全局变量或标志位来 指定当前由哪一个线程打 印数据。 简单清晰,不会发生资源 竞争(同一时间只有一个 线程修改变量值,其他线 程只是读取变量值)。 代码来自:SY1306230_余恒洋 代码来自:SY1306230_余恒洋
第一题-实现方法2 采用原始的synchronized, wait(), notify(), notifyAll()等方式控制线程。 特点 利用synchronized实现互斥变量的保护,如果互斥变量当前的标号不 等于线程的ID号就让他等待,否则进入访问区域,访问之后对互斥变 量的计数进行更新,以便下一个线程可以访问互斥变量,并通知所有 等待的线程互斥变量空闲。 特点 synchronized是在JVM层面实现的,不但可以通过一些监控工具监控 synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放 锁定。 在资源竞争不是很激烈的情况下,偶尔会有同步的情形下, synchronized很合适。因为编译程序通常会尽可能的进行优化 synchronize,另外可读性非常好。 基本所有同学都能给出2-3种实现方法,并给出代码、编译环境和打印结果。
第一题-实现方法2 采用原始的synchronized, wait(), notify(), notifyAll() 等方式控制线程。 基本所有同学都能给出2-3种实现方法,并给出代码、编译环境和打印结果。
第一题-实现方法3 采用JDK并发包提供的Lock, Condition等类的相关方 法控制线程。 特点 每次一个线程执行完五个打印任务时,指定将要唤醒 的下一个进程,而不是唤醒所有进程。 特点 lock是通过代码实现的,要保证锁定一定会被释放,就 必须将unLock()放到finally{}中 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock(如本题),但是在资源竞争很激烈的情况下则相反 Java线程同步的其他方法:volatile,CountDownLatch,CyclicBarrier,DelayQueue,PriorityBlockingQueue,ScheduledExecutor,Semaphore,Exchanger
效率对比 1 安全性 synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。 2 效率 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock(如本题),但是在资源竞争很激烈的情况下则相反。
第一题-线程安全/同步总结1 保证线程安全的三种方法: 实现同步机制的方法: 不要跨线程访问共享变量; 使共享变量是final类型的; 将共享变量的操作加上同步。 实现同步机制的方法: 同步代码块 synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。 同步方法 public synchronized 数据返回类型 方法名(){} 在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized很合适。因为编译程序通常会尽可能的进行优化synchronize,另外可读性非常好。
第一题-线程安全/同步总结2 通过使用同步方法,可非常方便的将某类变成线程安全的类,线程安全特征如下: 该类的对象可以被多个线程安全的访问。 每个线程调用该对象的任意方法之后,都将得到正确的结果。 每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。 注意:不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。 基本所有同学都能给出2-3种实现方法,并给出代码、编译环境和打印结果。
对于同步方法而言,无需显示指定同步监视器,同步方法的同步监视器是 this 也就是该对象的本身(这里指的对象本身有点含糊,其实就是调用该同步方法的对象)。
2、对于一个账户类Account,有以下定义 class Account { int balance; synchronized void deposit(float amt) {//存款 balance += amt; } synchronized void withdraw(float amt) {//取款 if(balance < amt) throw new OutOfMoneyError(); balance -= amt; 如果在Account中添加以下方法 void transfer(AccountJ other, float amt) { other.withdraw(amt); this.deposit(amt); 试分析,调用该transfer方法时会出现什么问题,如果为该方法添加关键字 synchronized,调用时是否还有问题?
调用transfer方法会造成资源竞争,导致balance的和不一致。因为多个线程之间的other. withdraw(amt)和this 调用transfer方法会造成资源竞争,导致balance的和不一致。因为多个线程之间的other.withdraw(amt)和this.deposit(amt)操作顺序无法保证。 当为transfer()方法添加关键字synchronized后,会产生嵌套synchronized导致死锁。
面向对象编程与基于对象编程 根据javascript的基于对象编程思想实现以下功能并进行比较说明它们的实现机制的异同: public class ClassA { public int a = 1; public static int b = 2; public int c() { return 3; } ClassA obj = new ClassA(); System.out.println(obj.a); //输出:1 System.out.println(obj.b); //输出:2 System.out.println(obj.c()); //输出:3
function ClassA() { this.a = 1; //调用this指针 this.c = function() { return 3; }; } ClassA.b = 2; //定义“类变量”(本质是函数的变量),不能通过用new产生的实例调用类属性 var obj = new ClassA(); console.log(obj.a); //输出:1 console.log(ClassA.b); //输出:2 console.log(obj.c()); //输出:3
由于JavaScript是一种基于对象(object-based)的语言而不是严格意义上的面向对象(object-oriented)语言,在进行编程时则与Java有很大区别。 1.实例属性(变量)的定义与使用 Java: public class ClassA { public int a = 1; } ClassA obj = new ClassA(); System.out.println(obj.a); //输出:1 JavaScript: function ClassA() { this.a = 1; //第一种:调用this指针 }; ClassA.prototype.b = 2; //第二种:调用prototype(对象原型) var obj = new ClassA(); console.log(obj.a); //输出:1 console.log(obj.b); //输出:2 JavaScript中对象的属性定义有两种方法,其中调用this指针的方法与Java最为相似。
2.实例方法(函数)的定义与调用 Java: public class ClassA { public int c() { return 3; } ClassA obj = new ClassA(); System.out.println(obj.c()); //输出:3 JavaScript: function ClassA() { this.c = function() { }; var obj = new ClassA(); console.log(obj.a()); //输出:1; console.log(obj.b()); //输出:2; 方法和属性的定义、使用是非常相似的,没有太多变化。
3.类属性(静态属性)的定义与调用 Java: public class ClassA { public static int A = 1; } System.out.println(ClassA.A); //直接调用类,输出:1 ClassA obj = new ClassA(); System.out.println(obj.A); //调用由类产生的实例,输出:1 JavaScript: function ClassA() { } //首先初始化对象 ClassA.A = 1; //定义“类变量”(本质是函数的变量) console.log(Class.A); //直接调用类,输出:1 var obj = new ClassA(); console.log(obj.A); // 错误!不能通过用new产生的实例调用类属性 在Java中,可以类的实例调用静态属性,但JavaScript中不允许这样做,这和JavaScript中使用函数表示对象的设计有关。