文章目录
一、实现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 + "张票");
}
}
}