Java 多线程,创建线程的3种方式、设计模式-静态代理模式、Lamda表达式
概念
- 线程就是独立的执行路径;在程序运行时,即使没有自己创建程,后台也会有多个线程,如主线程,gc线程(垃圾回收线程)
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
创建线程
创建线程主要有3种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承thread类
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
如上图,run方法就是普通的方法调用,调用start方法是开启线程。
线程开启不一定立即执行,是由CPU调度执行。
//自定义线程类继承自Thread
private static class threadTest extends Thread{
@Override
public void run() {
super.run();
//重写run()方法,编写线程执行体
for (int i = 0; i < 20; i++) {
System.out.println("多线程");
}
}
public static void main(String[] args) {
//创建一个线程对象
threadTest tt = new threadTest();
//调用start方法开启线程
tt.start();
for (int i = 0; i < 2000; i++) {
System.out.println("主线程"+i);
}
}
}
实现Runnable接口
定义MyRunnable类,实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
//定义MyRunnable类实现Runnable接口
private static class threadTest implements Runnable{
//实现run()方法,编写线程执行体
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("多线程");
}
}
public static void main(String[] args) {
//创建线程对象,调用start()方法启动线程
threadTest tt = new threadTest();
//正常的写法
// Thread thread = new Thread(tt);
// thread.start();
new Thread(tt).start();//简化的写法
}
}
更推荐使用实现Runnable接口来创建线程,因为Thread也是实现了Runnable接口,使用Runnable可以避免单继承局限性,灵活方便。
而且方便同一个对象被多个线程使用,下面是一个例子:
//创建线程对象,调用start()方法启动线程
threadTest tt = new threadTest();
new Thread(tt,"黄牛1").start();//不同的线程,同一个资源
new Thread(tt,"黄牛2").start();//不同的线程,同一个资源
new Thread(tt,"黄牛3").start();//不同的线程,同一个资源
为什么Runnable要通过Thread来启动线程呢,这就要了解一下静态代理模式了。
设计模式-静态代理模式
真实对象和代理对象都要实现同一个接口。
代理要代理真实角色,代理对象可以做真实对象做不了的事情,真实对象专注做自己的事情。
就像下面这个例子,婚礼新人You和婚礼代理公司WeddingCompany都继承了结婚marry这个接口,各自实现了HappyMarry()抽象方法,最后使用的时候,是将You对象传入WeddingCompany对象中,通过WeddingCompany的HappyMarry()方法完成的。
public static void main(String [] args){
//三对情侣结婚
new WeddingCompany(new You("A")).HappyMarry();
new WeddingCompany(new You("B")).HappyMarry();
new WeddingCompany(new You("C")).HappyMarry();
}
//结婚这件事情
private interface marry{
void HappyMarry();
}
//个人的结婚(穿戴好、执行婚礼仪式)
private static class You implements marry{
private String name;
public You(String n){
this.name = n;
}
@Override
public void HappyMarry() {
System.out.println(this.name+"新人结婚");
}
}
//婚礼代理公司的结婚(代理新人布置婚礼)
private static class WeddingCompany implements marry{
private marry target;
public WeddingCompany(marry target){
this.target = target;
}
@Override
public void HappyMarry() {
System.out.println("收钱");
System.out.println("布置婚礼现场,主持婚礼");
this.target.HappyMarry();
System.out.println("收尾款\n");
}
}
运行结果:
我们再看回Runnable,Thread和下面的threadTest都是实现了接口Runnable的类,Thread对象就是作为了threadTest的代理对象来使用的,有了这个代理对象,我们定义一个实现Runnable的类(比如说threadTest)的时候只要考虑最重要的事情(线程中要执行什么)来重写run方法,其他的事情用Thread代理对象来帮我们做。
public static void main(String[] args) {
//创建线程对象,调用start()方法启动线程
threadTest tt = new threadTest();
//正常的写法
// Thread thread = new Thread(tt);
// thread.start();
new Thread(tt).start();//简化的写法
}
Runnable还有更简化的写法,需要用到Lamda表达式:
Lamda表达式
Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
例子:
最通常的使用接口的方法:
public class Test {
public static void main(String [] args) {
LamdaTest t = new Test1();//创建外部类对象(父类引用指向子类对象)
t.Lamda();//调用方法
}
}
//实现类
class Test1 implements LamdaTest{
@Override
public void Lamda() {
System.out.println("我是外部类");
}
}
//定义一个函数式接口(只有一个抽象方法)
interface LamdaTest{
void Lamda();
}
也可以放到类里,作为静态内部类:
public class Test {
public static void main(String [] args) {
LamdaTest t = new Test1();//创建外部类对象(父类引用指向子类对象)
t.Lamda();//调用方法
}
//实现类(静态内部类)
private static class Test1 implements LamdaTest{
@Override
public void Lamda() {
System.out.println("我是静态内部类");
}
}
}
//定义一个函数式接口
interface LamdaTest{
void Lamda();
}
也可以把类实现在方法内部,变成局部内部类:
public static void main(String [] args) {
//实现类(局部内部类)
class Test1 implements LamdaTest{
@Override
public void Lamda() {
System.out.println("我是局部内部类");
}
}
LamdaTest t = new Test1();//创建外部类对象(父类引用指向子类对象)
t.Lamda();//调用方法
}
//定义一个函数式接口
interface LamdaTest{
void Lamda();
}
也可以直接new接口然后实现方法,没有类名,变成匿名内部类:
public static void main(String [] args) {
//实现类(匿名内部类)
LamdaTest t = new LamdaTest(){
@Override
public void Lamda() {
System.out.println("我是匿名内部类");
}
};
t.Lamda();//调用方法
}
//定义一个函数式接口
interface LamdaTest{
void Lamda();
}
还可以更简化,使用Lamda表达式,是Java8的新特性:
public static void main(String [] args) {
//实现类
LamdaTest t = ()->{
System.out.println("我是Lamda表达式");
};
t.Lamda();//调用方法
}
//定义一个函数式接口
interface LamdaTest{
void Lamda();
}
如果要实现的方法中需要传参,括号中可以省略类型名:
public static void main(String [] args) {
//实现类(匿名内部类)
LamdaTest t = (a,b)->{
System.out.println("我是匿名内部类"+a+b);
};
t.Lamda(1,2);//调用方法
}
//定义一个函数式接口
interface LamdaTest{
void Lamda(int a,int b);
}
Runnable也是一个函数式接口,所以我们可以用Lambda 表达式来简化代码:
new Thread(()->{
System.out.println("Runnable线程运行");
}).start();
实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
public static void main(String [] args) throws ExecutionException, InterruptedException {
CallableTest t1 = new CallableTest();
CallableTest t2 = new CallableTest();
CallableTest t3 = new CallableTest();
//创建执行服务,线程池
ExecutorService es = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = es.submit(t1);
Future<Boolean> r2 = es.submit(t2);
Future<Boolean> r3 = es.submit(t3);
//获取结果(call方法的返回值)
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
//关闭服务
es.shutdown();
}
private static class CallableTest implements Callable<Boolean>{
private int ticketNums = 10;
//实现call()方法,需要抛出异常
@Override
public Boolean call() throws Exception {
while (true){
if (ticketNums<=0){
break;
}
Thread.sleep(200);
System.out.println(Thread.currentThread().getName()+"抢到票"+ticketNums--);
}
return true;
}
}