java入门篇(28) 多线程(补充一)

一、实现Callable接口创建多线程

1.步骤

  • 声明一个实现Callable接口的类a
  • 将a的实例作为FutureTask的参数创建FutureTask的对象b
  • 将b的作为Thread的参数创建线程

2.多线程分别实现0-9和0-19的求和,输出结果

思路:用实现Callable接口的方法创建多线程,FutureTask是Callable的子类,有一个get()方法用来获取线程执行完之后的结果。

public class callable_text1 implements Callable<Integer> {
    int num;

    public callable_text1(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < num; i++) {
            sum = sum + i;
            //System.out.println(Thread.currentThread().getName() + " " +i);
        }
        return sum;
    }
}

public class callable_main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        callable_text1 a = new callable_text1(10);
        callable_text1 b = new callable_text1(20);
        FutureTask<Integer> a1 = new FutureTask(a);
        FutureTask<Integer> b1 = new FutureTask(b);
        Thread a2 = new Thread(a1);
        Thread b2 = new Thread(b1);
        a2.setName("?");
        b2.setName("?");
        a2.start();
        System.out.println(a1.get());
        b2.start();
        System.out.println(b1.get());
    }
}

注意事项:
FutureTask的get()方法必须出现在线程的start()方法之后,如果出现在线程的start()方法之前,不会输出结果。

二、线程的实例

1.多线程数据安全问题

出现多线程安全问题的条件

  • 是否是多线程环境
  • 是否存在共享数据
  • 是否存在多个线程操作共享数据

2.多线程复制文件

思路:

  • 多线程复制文件是将文件均分成及等分,然后由每个线程单独去处理,剩余的文件再开启一个线程去处理即可。
  • 用随机读写流,通过设置文件指针位置,来控制每个线程复制固定位置的文件。
    @Override
    public void run() {
        try {
            RandomAccessFile read1 = new RandomAccessFile(file1, "rw");
            RandomAccessFile write1 = new RandomAccessFile(file2, "rw");
            read1.seek(start);
            byte[] a = new byte[1024];
            int len = 0;
            while ((len = read1.read(a)) != -1) {
                write1.write(a, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Thread_main2 {
    public static void main(String[] args) {
        File file = new File("d:\\01.15_数据类型概述和分类.avi");
        File file1 = new File("d:\\数据结构a.avi");
        int n = 5;
        long start = 0, end = 0;
        long len = file.length() / n;
        for (int i = 0; i < n; i++) {
            start = len*i;
            end = len*(i + 1);
            new Thread_text2(start, end, file, file1).start();
        }
        start = len*n;
        end = file.length();
        new Thread_text2(start, end, file, file1).start();
    }
}

注意:
一定要记得开启线程,不然不会出现任何的结果
目的文件的文件名不一定要和源文件的文件名一样

3.多线程买票问题

要解决的问题

  • 如何将票数作为共享数据即多个线程去买同一份票?
    将票数用static关键字修饰,这样票就会变成共享资源

  • 出现票数相同的情况?
    语句ticket–不具有原子性,即ticket–分两步来完成,先将ticket旧值做处理,再进行–操作,这样可能会发生–操作还未执行,先将ticket旧值输出的情况。

  • 票数为负数的情况?
    第一个线程得到当前票数,还没卖出的同时第二个线程也获得了当前的票数,当票数为1时,就会售卖多次,就会出现负数的情况,由线程获得cpu的随机性引起的。

解决的方法

  • 同步代码块的方法解决,同步代码块的形式: synchronized (obj) { 代码块 }
    obj是"锁对象",可以在函数中static Object obj=new
    Object()这样定义,必须是一个锁,所以用static关键字修饰,在本例中的作用是:将票资源修改为护持资源,当一个线程获得这个资源时,锁子就会锁住,其他线程无法获得该资源,卖完票后,cpu随机分配给所有线程。
public class seal_ticket_text extends Thread {
    static int ticket = 100;
    static Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + " " + "还剩下" + ticket + "张票");
                }
            }
        }
    }
}

public class seal_ticket_main {
    public static void main(String[] args) {
        seal_ticket_text a = new seal_ticket_text();
        seal_ticket_text b = new seal_ticket_text();
        a.start();
        b.start();
        a.setName("?");
        b.setName("?");
    }
}

三、同步方法

3.1 同步方法的概念

synchronized修饰的方法叫做同步方法。

3.2 特性

  • 非静态同步方法默认使用this作为锁对象
  • 静态同步方法使用的是类锁,即将类的字节码文件作为锁对象
  • 同步方法的执行效率较相对于正常的方法执行效率低

3.3 同步方法卖票

正确的案例:

public class seal_ticket_text implements Runnable {
    static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            seal_ticket();
        }
    }

    private synchronized void seal_ticket() {
        if (ticket > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName() + "剩下" + ticket + "张票");
        }
    }
}

public class seal_ticket_main {
    public static void main(String[] args) {
        seal_ticket_text a = new seal_ticket_text();
        Thread a1 = new Thread(a);
        Thread a2 = new Thread(a);
        a1.start();
        a2.start();
        a1.setName("?");
        a2.setName("?");
    }
}

错误的示范:

package thread;

public class seal_ticket_text extends Thread {
    static int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            seal_ticket();
        }
    }
    private synchronized  void seal_ticket() {
        if(ticket > 0) {
            try {
                sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName() + "剩下" + ticket + "张票");
        }
    }
}

public class seal_ticket_main {
    public static void main(String[] args) {
        seal_ticket_text a = new seal_ticket_text();
        seal_ticket_text b = new seal_ticket_text();
        a.start();
        b.start();
        a.setName("?");
        b.setName("?");
    }
}

同步方法的锁对象用的用的是this,但是必须是同一个对象的this,不然就达不到使票成为互斥资源的效果,如上面的错误的案例,就new了两个对象,这样同步方法里的锁对象就不是同一个对象,就会执行各自的锁方法,就会出错。

3.2 静态同步方法的锁对象是类的字节码文件

  • 说明
    静态方法随着类的加载而加载,所以静态同步方法的锁对象只能是类的字节码文件。
  • 类的字节码文件可以通过以下方法获得:
  • 类.class 每个类都有class属性,直接获取类的字节码文件
  • new class().getclass()
   private static synchronized  void seal_ticket() {
        if(ticket > 0) {
            try {
                sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName() + "剩下" + ticket + "张票");
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值