一:创建多线程的三种方法:
1.继承Thread类,重写run方法,调用start方法。
这个方法是多线程中最简单的,但也是最普通的。
package cn.com.Thread;
/*
创建多线程
继承Thread类,重写run方法,调用start方法
*/
public class FirstThread extends Thread{
private int i;
public void run(){
for (i = 0;i<100;i++){
System.out.println(getName()+" " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i<100; i++){
System.out.println(Thread.currentThread().getName()+ " "+ i);//主线程
if(i == 20){
new FirstThread().start();
new FirstThread().start();
}
}
}
}
2.实现runnable接口,重写run方法,创建runnable实现类的实例st,以st作为Thread的target来创建thread的对象,这时的对象才是真的线程对象。
package cn.com.Thread;
public class SecondThread implements Runnable{
private int i;
public void run(){
for(i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for (int i = 0;i<30;i++){
System.out.println(Thread.currentThread().getName()+" "+ i );
if(i == 20){
SecondThread st = new SecondThread();
new Thread(st,"新线程1").start();
new Thread(st,"新线程2").start();
}
}
}
}
3.使用Callable和Future创建线程
创建了callable的实现类,并实现了call方法,可以通过实现类来创建callable对象,也可以用lambda表达式创建callable对象。
用FutureTask类来包装Callable对象,同时也包装了它的call方法和返回值。
用FutureTask的对象作为target,创建并启动。
用FutureTask的get方法获得返回值。
package cn.com.Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Thirdthread {
public static void main(String[] args) {
Thirdthread rt = new Thirdthread();
FutureTask<Integer> task = new FutureTask<>((Callable<Integer>)()->{
int i = 0;
for (;i<30;i++)
{
System.out.println(Thread.currentThread().getName()+"主线程" +" "+i);
}
return i;//有返回值
});
for (int i = 0;i <30;i++){
System.out.println(Thread.currentThread().getName()+"次线程"+ " "+i);
if (i == 20)
{
new Thread(task,"有返回值的线程").start();
}
}
try {
System.out.println("子线程返回值"+ task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
方法三有什么好处呢?答:可以抛出异常,有返回值。
总结:
法一:优点:简单,好用。
缺点:只能继承一个类,不能再继承其他父类了。
法二三:优点:只实现了接口,还可以继承其他父类。共享target,适合多线程共享同一个资源的情况。
缺点:编程稍稍复杂一点,如果访问当前线程,则使用Thread.currentThread()方法。
二:并发编程工具类:
CountDownLatch
CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。
我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。
注意:java中还有一个同步工具类叫做CyclicBarrier,他的作用和CountDownLatch类似。同样是等待其他线程都完成了,才可以进行下一步操作,我们再举一个例子,在打王者的时候,在开局前所有人都必须要加载到100%才可以进入。否则所有玩家都相互等待。
我们看一下区别:
CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。关键点其实就在于那N个线程
(1)CountDownLatch里面N个线程就是学生,学生做完了试卷就可以走了,不用等待其他的学生是否完成
(2)CyclicBarrier 里面N个线程就是所有的游戏玩家,一个游戏玩家加载到100%还不可以,必须要等到其他的游戏玩家都加载到100%才可以开局
现在应该理解CountDownLatch的含义了吧,下面我们使用一个代码案例来解释。
我们使用学生考试的案例来进行演示:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(2);
System.out.println("全班同学开始考试,一共2人");
new Thread(() -> {
System.out.println("第1个人交卷");
countDownLatch.countDown();
}).start();
new Thread(() -> {
System.out.println("第2个人交卷");
countDownLatch.countDown();
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("老师开始清点,必须所有人交卷后,老师才能走。");
}
输出:
全班同学开始考试,一共2人
第1个人交卷
第2个人交卷
老师开始清点,必须所有人交卷后,老师才能走。
可以看出,主线程确实是随着子线程都结束后,才可以执行的
如果我们去掉
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
结果会变为下面,也就是主线程和子线程是乱序的了。
全班同学开始考试,一共2人
第1个人交卷
老师开始清点,必须所有人交卷后,老师才能走。
第2个人交卷