JAVASE基础模块三十六( 多线程复制 线程创建的第二种第三种方法 synchronized与锁的问题)
多线程复制问题
-
多线程复制不同文件
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; public class FuZhi { public static void main(String[] args) { System.out.println("开始复制文件"); new c1().start(); new c2().start(); System.out.println("文件复制完毕"); } } class c1 extends Thread{ @Override public void run() { try { Files.copy(Paths.get("ccc.md"), Paths.get("C:\\Users\\yllch\\Desktop\\cc.md"),StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { e.printStackTrace(); } } } class c2 extends Thread{ @Override public void run() { try { Files.copy(Paths.get("xxx.doc"), Paths.get("C:\\Users\\yllch\\Desktop\\xx.doc"),StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { e.printStackTrace(); } } } 运行结果: 开始复制文件 文件复制完毕
-
多线程复制同一文件
import java.io.*; public class DuanDianFuZhi { public static void main(String[] args) { co1 co1 = new co1(); co2 co2 = new co2(); co3 co3 = new co3(); co1.start(); co2.start(); co3.start(); } } class co1 extends Thread { private File f1 = new File("yrdw.mp3"); @Override public void run() { RandomAccessFile rd = null; RandomAccessFile rt = null; try { rd = new RandomAccessFile((f1), "rw"); rt = new RandomAccessFile("dw.mp3", "rw"); int len = 0; byte[] bytes = new byte[1024*1024]; int i = 0; while ((len = rd.read(bytes)) != -1) { rt.write(bytes, 0, len); if (rt.length() ==(f1.length() / 3)) { break; } } rd.close(); rt.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } class co2 extends Thread { private File f1 = new File("yrdw.mp3"); @Override public void run() { RandomAccessFile rd = null; RandomAccessFile rt = null; try { rd = new RandomAccessFile((f1), "rw"); rt = new RandomAccessFile("dw.mp3", "rw"); rd.seek((f1.length() / 3) + 1); rt.seek((f1.length() / 3) + 1); int len = 0; byte[] bytes = new byte[1024*1024]; int i = 0; while ((len = rd.read(bytes)) != -1) { rt.write(bytes, 0, len); if (rt.length() ==(f1.length() / 3)) { break; } } rd.close(); rt.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } class co3 extends Thread { private File f1 = new File("yrdw.mp3"); @Override public void run() { RandomAccessFile rd = null; RandomAccessFile rt = null; try { rd = new RandomAccessFile((f1), "rw"); rt = new RandomAccessFile("dw.mp3", "rw"); rd.seek((f1.length() / 3 * 2) + 1); rt.seek((f1.length() / 3 * 2) + 1); int len = 0; byte[] bytes = new byte[1024*1024]; int i = 0; while ((len = rd.read(bytes)) != -1) { rt.write(bytes, 0, len); if (rt.length() ==(f1.length() / 3)) { break; } } rd.close(); rt.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
线程创建的第二种方式
-
概念
- 创建线程的另一种方法是声明实现 Runnable 接口的类
- 该类然后实现 run 方法
- 然后可以分配该类的实例
- 在创建 Thread 时作为一个参数来传递并启动
-
public class DiEr { public static void main(String[] args) { //创建任务 De de = new De(); //把任务传递进来 Thread t = new Thread(de,"sssss"); t.start(); } } class De implements Runnable { @Override public void run() { for (int i = 0; i < 2; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } } }
运行结果: sssss 0 sssss 1 进程已结束,退出代码0
线程创建的第三种方式
-
步骤
- 创建一个类实现Callable接口
- 创建一个FutureTask类将Callable接口的子类对象作为参数传递进去
- 创建Thread类 将FutureTask类传递进去
- 开启线程
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class San { public static void main(String[] args) { Sa sa = new Sa(100); FutureTask ft = new FutureTask<Integer>(sa); Thread ead = new Thread(ft); ead.start(); try { System.out.println(ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class Sa implements Callable<Integer> { private int num; public Sa(int num) { this.num = num; } @Override public Integer call() throws Exception { System.out.println("一树梨花压海棠"); int b = (1 + num) * num / 2; return b; } }
运行结果: 一树梨花压海棠 5050
区别
- Runnable 与 Callable 任务的区别
- Runnable 重写run方法,没有返回值,无法获取异步执行完之后的结果 run不允许抛出异常
- Callable重写call方法 有返回值 可以获取异步执行完之后的结果 call方法可以抛出异常
电影院买票问题
-
Thread 方法
public class Cinema { public static void main(String[] args) { Chuan c1 = new Chuan(); Chuan c2 = new Chuan(); Chuan c3 = new Chuan(); c1.setName("11111"); c2.setName("22222"); c3.setName("33333"); c1.start(); c2.start(); c3.start(); } } class Chuan extends Thread { static int p = 100; @Override public void run() { while (true) { System.out.println(this.getName() + "窗口正在出售: 还剩" + (p--) + "张票"); if (p == 0) { break; } } } }
-
Runnable方法
public class Cal { public static void main(String[] args) { Chu chu = new Chu(); Thread c1 = new Thread(chu); Thread c2 = new Thread(chu); Thread c3 = new Thread(chu); c1.setName("11111"); c2.setName("22222"); c3.setName("33333"); c1.start(); c2.start(); c3.start(); } } class Chu implements Runnable { int p = 100; @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + "窗口正在出售: 还剩" + (p--) + "张票"); if (p == 0) { break; } } } }
运行结果: 33333窗口正在出售: 还剩100张票 .......省略 11111窗口正在出售: 还剩2张票 进程已结束,退出代码0
-
模拟网络延迟
public class CellRunnable implements Runnable{ //共享数据,被多个线程所共享。 static int piao = 100; static Object obj=new Object(); int i=0; @Override public void run() { while (true) { if(i%2==0){ //就是最后一张。piao=1; synchronized (CellRunnable.class) { //加锁 try { //模拟真实情况中,网络延迟的现象。 Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } if (piao > 0) { System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票"); } } //释放锁 }else{ maiPiao2(); //释放锁 } i++; } } //方法上加上关键字synchronized 同步方法 //同步方法的默认锁对象是this public synchronized void maiPiao(){ //就是最后一张。piao=1; //加锁 try { //模拟真实情况中,网络延迟的现象。 Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } if (piao > 0) { System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票"); } } //静态同步方法,用的锁对象是当前类的字节码文件对象 public static synchronized void maiPiao2() { //就是最后一张。piao=1; //加锁 try { //模拟真实情况中,网络延迟的现象。 Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } if (piao > 0) { System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票"); } } }
线程安全性问题
-
当我们模拟了一下真实卖票的网络延迟时会出现一些不合理的数据
- 出现重复票的问题 是由于原子性所导致的 原子性 就是不可在分割 ++ –
- 出现负数票的问题 由于线程的随机性所导致的
-
出现线程安全方面问题的条件
- 多线程环境
- 多个线程在操作共享变量
- 有没有多条语句在操作这个共享变量 i++
-
解决方式
-
使用同步代码块解决线程安全方面的问题
-
锁 java中任意一个对象
synchronized (锁对象){ 出现线程安全问题的代码 }
-
加锁之后其他线程无法加入进来 维持了单线程环境
-
多个线程要共用一把锁对象 不然锁不住
-
缺点是 耗费资源
-
-
同步代码块的锁对象:
- 任意一个对象
- 同步方法的锁对象:是this
- 静态同步方法的锁对象:就是当前类对应的字节码文件对象
-
代码
public class Cal { public static void main(String[] args) { Chu chu = new Chu(); Thread c1 = new Thread(chu); Thread c2 = new Thread(chu); Thread c3 = new Thread(chu); c1.setName("11111"); c2.setName("22222"); c3.setName("33333"); c1.start(); c2.start(); c3.start(); } } class Chu implements Runnable { int p = 100; Object b = new Object(); @Override public void run() { while (true) { synchronized (b) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口正在出售: 还剩" + (p--) + "张票"); if (p == 0) { break; } } } } }
运行结果: 33333窗口正在出售: 还剩100张票 ........... 33333窗口正在出售: 还剩1张票 11111窗口正在出售: 还剩2张票
锁
- 在Java编程中 经常需要用到同步 而用的最多的是 synchronized关键字
- synchronized也可以用在方法上 称为同步方法
- 同步方法的默认锁对象是this
- 静态同步方法用的锁对象是当前类的字节码文件对象
- Java的内置锁 每个java对象都可以用做一个实现同步的锁 这些锁称为内置锁
- 线程进入同步代码块或方法时会自动获得该锁 在退出同步代码块或方法时会释放该锁
- 获得内置锁的唯一途径就是进入这个锁的同步代码块或方法
- java内置锁是一个互斥锁 这意味着最多只有一个线程能够获得该锁
- 当线程A尝试去获得线程B持有的内置锁时 线程A必须等待或者阻塞
- 直到线程B释放这个锁 如果B线程不释放这个锁 那么A线程将永远等待
- 区别
- 对象锁是用于对象实例方法 或者一个对象实例上的
- 类锁是用于类的静态方法或者一个类的class对象上的
- 类的对象实例可以有很多个 但是每个类只有一个类锁
- 但是 类锁并不是真实存在的 只是一个概念上的东西
- 只是用来帮助我们理解锁定实例方法和静态方法的区别