1.Callable接口
该接口和runnable接口差不多,但是有返回值,能够返回run方法运行的结果值。
2.线程池Executor
该类的构造器为:ScheduledThreadPoolExecutor(int corePollSize);传入的值为可运行的最大线程数,如果大于这个值,则多余的线程会进入等待,直至该类中有线程运行结束才能继续进入运行。
主要方法:
(1)submit(); 该方法中可传入Runnable,Callable的实现类,可以有返回值;
(2)shutdown();在程序运行完毕后需要执行此方法,否则程序不会停止。
下面有使用代码列子:
public class Test1_1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = new ScheduledThreadPoolExecutor(1);
Future<Integer> f = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("1开始"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("1结束"+System.currentTimeMillis());
return 0;
}
});
Future<Integer> f2= es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("2开始"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("2结束"+System.currentTimeMillis());
return 0;
}
});
//这里需要结束es对象,否则程序会一直执行
es.shutdown();
}
}
打印:
1开始1586436212133
1结束1586436214134
2开始1586436214134
2结束1586436216134
当设置成1时,只能有一个线程运行。所以上述两个线程在执行开始时间会差2000左右。
2.死锁
死锁需要用到两个及以上的线程,和多把锁,比较简单,代码如下:
public class Test2_1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized ("a"){
System.out.println(Thread.currentThread().getName()+"获取到了a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在获取b");
synchronized ("b"){
System.out.println(Thread.currentThread().getName()+"获取到b");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized ("b"){
System.out.println(Thread.currentThread().getName()+"获取到了b");
System.out.println(Thread.currentThread().getName()+"正在获取a");
synchronized ("a"){
System.out.println(Thread.currentThread().getName()+"获取到a");
}
}
}
}).start();
}
}
上述代码在打印:
Thread-0获取到了a
Thread-1获取到了b
Thread-1正在获取a
Thread-0正在获取b
后一直运行,因为 a,b两个资源被两个不同的线程线程占用着,并且两个不同的线程又在同时等待着b,a资源。所以产生了死锁。
3.wait和sleep区别
wait是Object中的方法,sleep是Thread类中的方法, wait需要放在锁中,并且wait的时候释放锁对象,也就是其他线程可以访问当前锁的对象,sleep放在锁中不会释放当前锁对象,其他线程不能访问。
题目:
请用“等待唤醒”机制编写一个程序,要求:
第一个线程:遍历1–1000所有的数字,在遍历过程中,如果发现这个数字能同时被
2,3,5,7整除,立即wait()退出等待,让第二个线程进入。
第二个线程:运行后,将一个计数器 + 1,之后再唤醒等待的线程。
代码:
public class Test3_1 {
//创建一个计数器,初始值为0
static int count = 0;
//创建一个obj对象,用来作为锁的对象
static Object obj = new Object();
//定义一个boolean变量,用于判断遍历的线程是否结束,这样用于计数的线程就可以根据这个值来判断循环是否结束。
static volatile boolean isExit = false;
public static void main(String[] args) throws InterruptedException {
//定义一个用于计数的线程
Thread addThread = new Thread(new Runnable() {
@Override
public void run() {
//将obj对象锁住
synchronized (obj) {
//根据isExit的值判断循环是否可以继续进行,若isExit为true 则说明另一个线程已遍历结束,该循环可终止。
while (!isExit) {
//循环一次count值加一
count++;
try {
//唤醒遍历线程
obj.notify();
//该线程进入无限等待状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
//定义一个用于遍历的线程
Thread traverseThread = new Thread(new Runnable() {
@Override
public void run() {
//将obj对象锁住
synchronized (obj) {
//循环遍历1-1000
for (int i = 1; i <= 1000; i++) {
//如果当前i的值符合条件,则执行if语句中的内容
if (isOk(i)) {
try {
//唤醒计数线程
obj.notify();
//当前线程进入无限等待状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//循环结束,将isExit的值设置为true,这样计数线程可以根据这个值将循环结束
isExit = true;
//唤醒计数线程
obj.notify();
}
}
});
//开始遍历的线程
traverseThread.start();
//让主线程睡眠0.1秒,避免计数线程先拿到锁
Thread.sleep(100);
//开始计数线程
addThread.start();
//等待遍历线程的结束
traverseThread.join();
//等待计数线程的结束
addThread.join();
//打印count值
System.out.println(count);
}
//自定义方法,用于判断传入值 是否可以同时被2,3,5,7整除
public static boolean isOk(int x) {
return x % 2 == 0 && x % 3 == 0 && x % 5 == 0 && x % 7 == 0;
}
}
打印:
4