1.多线程的几种创建方式
(1)继承Thread类(也可用匿名内部类),重写 run() 方法
(2)实现Runnable接口,重写 run() 方法
(3)实现Callable接口
注意:实现Runnable和Callable接口的类只能当做一个 可以在线程中运行的任务,任务是通过线程驱动从而执行的,不是真正意义上的线程。最后还是通过Thread来调用,与Runnable相比,Callable有返回值,返回值通过Futuretask封装。
2.守护线程和用户线程
Java中有两种线程——用户线程、守护线程
用户线程(非守护线程):用户创建的线程,主线程停止用户线程不会停止(主死用户不是)
守护线程:和main相关,比如gc线程,当进程不存在或主线程停止,守护线程也会被停止,使用setDaemon(true)方法设置为守护线程(主死守护也死)
3.多线程的几种分类
用户线程、守护线程
主线程(一个进程中一定有主线程)、子线程、GC线程
4.多线程运行状态
(1)新建状态:当用户new操作符创建一个线程时,线程还没开始运行
(2)就绪状态:一个新创建的线程不会自动开始运行,要执行线程必须调用start()方法。当调用start()方法时就启动了线程,创建了线程运行的 系统资源,并调度线程运行run()方法。当调用start方法返回后,线程就处于就绪状态。
注意:处于就绪状态的线程不一定立即运行run()方法,线程必须同其他线程竞争CPU时间,只有获取到CPU时间才可以运行线程。所以可能有多个线程处于就绪状态
(3)运行状态:当线程获得CPU时间后进入运行状态,真正开始执行run()方法
(4)阻塞状态:线程运行过程中,可能由于各种原因进入阻塞状态:,比如:
1.线程通过调用sleep方法进入睡眠状态
2.线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会反悔到他的调用者
3.线程视图得到一个锁,而该锁正被其他线程持有
4.线程在等待某个触发条件
(5)死亡状态(2个原因):
1.run方法正常退出而自然死亡
2.一个未捕获的异常终止了run方法而使得线程猝死
问题1:如何确定当前线程是否活着(要么可运行、要么阻塞)?
答案:使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false。
(1)产生死锁条件必须满足以下4个条件:
互斥条件:该资源任意时刻只有一个线程占用请求与保持条件:一个进程因请求资源阻塞时,对已经获得的资源保持不变不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完才释放资源循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(2)如何避免线程死锁
破坏产生死锁4个条件之一即可。
破坏 互斥条件:无法破坏,锁本来就用来互斥的破坏 请求与保持条件:可以一次性申请所有资源破坏 不剥夺条件:占用部分资源的线程进一步申请其它资源时如果申请不到,可以主动释放它本来就占有的资源破坏 循环等待条件:靠按顺序申请资源来预防。按某一顺序申请资源,反序释放资源。同步:当前程序执行完才能执行后面的程序,程序执行时按照顺序执行。多线程下,并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用
异步:程序没有等到上一步程序执行完才执行下一步,而是直接往下执行(比如多线程)
阻塞:调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回
阻塞同步:线程阻塞除了线程主动调用休眠外常见的就是遇到同步代码块,同一时间不能并行执行。当有多个请求出现线程等待的情况即为阻塞。
非阻塞:调用结果返回之前,当前线程不会被挂起,该函数不会阻塞当前线程,而会立刻返回
非阻塞同步:并发条件下,如果没有多个线程争用共享资源则操作成功;如果存在数据的争用冲突才去补救,比如重试机制。这种乐观的策略不需要把线程挂起,就是非阻塞同步。
注意:阻塞同步和非阻塞同步都是实现线程安全的两个保障手段
当在主线程中执行到t.join()时,就认为主线程应该把执行权让给t。任何地方当调用了t.join(),就必须要等待线程t执行完毕后,才能继续执行其他线程。
底层原理:
t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒,底层用了Java中最顶级对象Object提供的方法wait(),是一个同步方法,直到t线程执行完毕,再调用notify()(或者notifyAll())唤醒当前正在运行的线程。
思考:现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
所有实例都有一个 等待队列(虚拟的概念),它是在实例wait()方法执行后停止操作的线程的队列,相当于线程休息室,当如下任意一个情况发生线程会退出等待队列:
有其他线程的notify()方法来唤醒线程有其他线程的notifyAll()方法来唤醒线程有其他线程的interrupt()方法来唤醒线程wait()方法超时使用规则:要执行wait()方法线程必须持有锁,如果线程进入等待队列,便会释放其实例的锁。
思考1:wait()和sleep()的区别?
答案:sleep()方法没有释放锁,而wait()释放了锁;两者都可以暂停程序的执行,但wait()更倾向于线程将交互;wait()方法被调用后不会自动苏醒而sleep()会自动苏醒,除非wait()设置超时时间才能自动苏醒。
思考2:wait()和join()的区别?
wait()需要锁唤醒,而join()不用;wait()需要用在synchronized,join()不用;属于不同的包;wait()方法的作用是让当前线程进入等待状态,join()则主要等待线程结束完成其执行,起了同步作用,把线程执行从“并行”变“串行”。
使用规则:同wait一样,要执行notify()方法线程必须持有锁,如果线程进入等待队列,便会释放其实例的锁;
notify()唤醒的线程并不会立刻重新运行,因为在执行notify()那一瞬间,执行notify()的那个线程还持有着锁,所以其他线程还无法获取这个实例的锁,只能等执行这个notify()的线程释放锁。
使用规则:同wait和notify()一样,要执行notifyAll()方法线程必须持有锁,如果线程进入等待队列,便会释放其实例的锁;
刚被唤醒的所有线程会去获取其他线程进入wait状态时释放的锁,但现在的锁在执行notifyAll()的线程持有着锁。因此,唤醒的所有线程都退出了等待队列,但都等待着获取锁,全部处于阻塞状态。只有在执行notifyAll()的线程释放了锁以后,其中一个幸运儿才会获取锁。
注意:wait()和notify()、notifyAll()必须在synchronized进行执行,持有同一把锁
使用规则:java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调, 可以在 Condition 上用await() 方法——使线程等待,signal() 或 signalAll() 方法——唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
1.多线程几种实现方式
(1)继承Thread类
继承Thread,重写run方法,run方法中需要线程执行代码
class CreateThread extends Thread { // run方法中编写 多线程需要执行的代码 public void run() { for (inti = 0; i< 10; i++) { System.out.println("i:" + i); } } } public class ThreadDemo { public static void main(String[] args) { // 1.创建一个线程 CreateThread createThread = new CreateThread(); // 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法 createThread.start(); } }单线程特点:代码从上往下执行,顺序执行
同步概念:代码从上往下进行执行(比如:Http请求、单线程)
异步概念:新的一条执行路径,不会影响其他线程(比如:多线程、mq异步加载)
(2)实现Runable接口
class CreateRunnable implements Runnable { @Override public void run() { for (inti = 0; i< 10; i++) { System.out.println("i:" + i); } } } public class ThreadDemo2 { public static void main(String[] args) { // 1.创建一个线程 CreateRunnable createThread = new CreateRunnable(); Thread thread = new Thread(createThread); thread.start(); } }(3)使用匿名内部类
public class ThreadDemo3 { public static void main(String[] args) { Thread thread = new Thread(new Runnable(){ public void run() { for (inti = 0; i< 10; i++) { System.out.println("i:" + i); } } }); thread.start(); } }(4)实现 Callable 接口
public class MyCallable implements Callable<Integer> { public Integer call() { return 123; } } public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable(); FutureTask<Integer> ft = new FutureTask<>(mc); Thread thread = new Thread(ft); thread.start(); System.out.println(ft.get()); }2.join()使用案例
创建一个线程,子线程执行完毕后,主线程才能执行
Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println("子线程"+Thread.currentThread().getName() + "i:" + i); } } }); t1.start(); // 本来输出结果是交换输出的,当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1 t1.join(); for (int i = 0; i < 10; i++) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println("主线程" + "i:" + i); }3.使用 Lock 来获取一个 Condition 对象
public class AwaitSignalExample { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void before() { lock.lock(); try { System.out.println("before"); condition.signalAll();//在先执行了after()后,执行到这,线程被唤醒 } finally { lock.unlock(); } } public void after() { lock.lock(); try { condition.await();//执行到这时,线程进入等待 System.out.println("after"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); AwaitSignalExample example = new AwaitSignalExample(); executorService.execute(() -> example.after());//先执行after()方法 executorService.execute(() -> example.before()); } } //输出: before after### 若对你有帮助的话,欢迎点赞!评论!转发!谢谢!
下一篇:锁
参考资料:《Java多线程编程实战指南-核心篇》.黄文海.豆瓣.8.7分
参考资料:https://github.com/Snailclimb/JavaGuide