一、创建线程的多种方式
1、继承Thread类
public class Demo1 extends Thread {
public Demo1(String name) {
super(name); // 给线程命名
}
@Override
public void run() {
while(!interrupted()) {
System.out.println(getName() + "线程执行了 .. ");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程
Demo1 d1 = new Demo1("first-thread");
Demo1 d2 = new Demo1("second-thread");
// 启动线程
d1.start();
d2.start();
// 守护线程:随着主线程的结束也会结束线程,不论线程是否执行完毕; true:守护线程;false:用户线程
//d1.setDaemon(true);
// 停止线程(方法已过期,不建议使用);
// 使用这种方式去停止线程的话,是一种不正常的中断,所以这个线程获取的锁和其他资源都不会释放
//d1.stop();
// 中断线程:把线程的中断标志改成 “已中断” ,然后在线程中通过判断 中断标志 来决定是否(正常)结束线程
d1.interrupt();
}
}
2、实现Runnable接口
public class Demo2 implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("thread running ...");
}
}
public static void main(String[] args) {
// 用 Tread 来包装 Runnable 线程对象来启动线程
// 被包装的 线程对象 作为target 参数传入 Thread 中
/*
Thread 中的方法
@Override
public void run() {
if (target != null) {
target.run();
}
}*/
Thread thread = new Thread(new Demo2());
thread.start();
}
}
3、匿名内部类的方式
public class Demo3 {
public static void main(String[] args) {
// 匿名内部类 启动线程 -- Thread方式
/*new Thread() {
public void run() {
System.out.println("thread start ..");
};
}.start();*/
// 匿名内部类 启动线程 -- Runnable方式
/*new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread start ..");
}
}).start();*/
// 当 Runnable 类型的匿名内部类 Thread和Runnable都有重写run方法时,执行的是 Thread 的run方法
/**
* class Thread implements Runnable {
*
* 可看出 Thread 是 Runnable 的实现类,即是子类
* 故调用方法时 是调用子类的方法
*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable");
}
}) {
public void run() {
System.out.println("sub"); // 执行 Thread 的 run()
};
}.start();
}
}
4、带返回值的线程
/**
* Callable 有返回值和异常的线程
*/
public class Demo4 implements Callable<Integer> {
public static void main(String[] args) throws Exception {
Demo4 d = new Demo4();
// 对线程任务的一个封装
FutureTask<Integer> task = new FutureTask<>(d);
Thread t = new Thread(task);
t.start();
System.out.println("我先干点别的。。。");
Integer result = task.get();
System.out.println("线程执行的结果为:" + result);
}
@Override
public Integer call() throws Exception {
System.out.println("正在进行紧张的计算....");
// 模拟计算时间
Thread.sleep(3000);
return 1;
}
}
5、定时器
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
// jdk 提供的Timer类定时器
Timer timer = new Timer();
// schedule(TimerTask task, long delay, long period)
// task 执行任务,delay 延时多长时间开始执行, period 每隔多长时间执行一次
// 这种方法的线程实现简单,但更难控制
timer.schedule(new TimerTask() {
@Override
public void run() {
// 实现定时任务
System.out.println("timertask is run");
}
}, 0, 1000);
while (true){
System.out.println("====");
Thread.sleep(1000);
}
}
}
6、线程池的实现
public class Demo6 {
public static void main(String[] args) {
//创建一个 10个容量的线程池
//ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 创建一个缓冲线程池,不断的在线程池中创建新的线程,当觉得线程数量够用的时候则不创建,否则创建新的
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
// 线程池用 execute(new Runnable(){ run(){xxx} }) 来启动线程
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
// 线程池销毁(停止线程池)
threadPool.shutdown();
}
}
7、Lambda表达式实现
/**
* Lambad 表达式
* 对并发的支持是非常好的,性能较高
* 函数式编程
*/
public class Demo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
int res = new Demo7().add(values);
System.out.println("计算的结果为:" + res);
}
public int add (List<Integer> values) {
// 并行的打印 values 的数据 forEach(对象::方法)
// values.parallelStream().forEach(System.out :: println);
// 先获取 “并发流”parallelStream() -- 调用 mapToInt() 方法 将 “流” 转成 int 类型的来进行运算
return values.parallelStream().mapToInt( i -> i * 2).sum(); // i -> i*2 表示 i = i*2(i为流中的每一个值)
}
}
8、Spring 实现多线程
在相应的方法上标注 @Async //开启异步调用
@EnableAsync // 开启支持异步任务
然后异步调用其方法即可实现 spring 多线程(异步调用 相当于 在后台运行该方法)
二、多线程带来的风险
1、线程安全性问题的前提
1.1、多线程环境下
1.2、多个线程共享同一资源
1.3、对资源进行非原子性操作
2、活跃性问题
2.1、死锁
--- 1 在等待 2 的资源,2 在等待 3 的资源,3 在等待 1 的资源;成为一个死循环,不可能得到资源的情况,则称为死锁;
2.2、饥饿
--- 排队时中的插队现象,一时插不上的队的那个则获取不到资源,但并不是 不可能得到资源的这种情况 ,则称为饥饿
2.2.1、饥饿与公平
2.2.1.1、高优先级吞噬所有低优先级的CPU时间片
Thread t1 = new Thread(new Target());
// 设置优先级 1-10,数值越大优先级越高
t1.setPriority(Thread.MIN_PRIORITY);
2.2.1.2、线程被永远堵塞在一个等待进入同步快的状态
2.2.1.3、等待的线程永远不被唤醒
2.2.2、如何尽量避免饥饿问题
2.2.2.1、设置合理的优先级
2.2.2.2、使用锁来代替synchronized
2.3、活锁
--- 特殊的排队时中的插队现象,始终插不上的队的那个则获取不到资源,但 是一种 不可能得到资源的这种情况 ,则称为活锁
3、性能问题
多线程执行一定会快吗? --- 不一定,线程的切换是非常消耗cpu的资源的;
有啥不正确的还望大佬指正,谢谢