Java如何实现(多)线程?
-
继承Thread类,重写run()方法
- 首先定义一个类来继承 Thread 类,重写 run 方法。
- 然后创建这个子类对象,并调用 start 方法启动线程。
public class MyThread extends Thread { public void run() { System.out.println("thread run..."); } public static void main(String[] args) { new MyThread().start(); } } -
实现Runnable接口,重写run()方法
- 首先定义一个类实现 Runnable 接口,并实现 run 方法。
- 然后创建 Runnable 实现类对象,并把它作为 target 传入 Thread 的构造函数中
- 最后调用 start 方法启动线程。
public class MyThread implements Runnable { public void run() { System.out.println("thread run..."); } public static void main(String[] args) { new Thread(new MyThread()).start(); } } -
实现Callable接口,重写call方法
- 首先定义一个类实现 Callable 接口,并实现 call 方法。call 方法是带返回值的。
- 然后通过 FutureTask 的构造方法,把这个 Callable 实现类传进去。
- 把 FutureTask 作为 Thread 类的 target ,创建 Thread 线程对象。
- 通过 FutureTask 的 get 方法获取线程的执行结果。
class MyThread implements Callable<Integer> { @Override public Integer call() throws Exception { return new Random().nextInt(100); } } public class TestFuture { public static void main(String[] args) throws Exception { FutureTask<Integer> task = new FutureTask<>(new MyThread()); new Thread(task).start(); Integer result = task.get(); // 获取线程得执行结果,阻塞式 System.out.println(result); } }
方法1与方法2相比更推荐后者,因为java只支持单继承,尽量少用extend。但java支持多实现(接口)
方法2与方法3相比,区别是后者有返回值。
什么时候使用多线程?
高并发场景,或者IO需要很长时间的场景没必要一直等它。
线程中start和run的区别
-
run()就和普通的成员方法一样,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码,没有达到多线程的目的。
-
start()方法用来启动新线程,使其进入就绪状态,新线程一旦得到cpu时间片,就开始执行run()方法。调用一次start()就可以自动实现线程切换,真正实现了多线程
Java线程同步和线程调度的相关方法
- wait():调用后线程进入无限等待状态,并释放所持对象的锁
- sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,到了时间,继续执行任务。
- notify():唤醒一个处于等待状态的线程,至于唤醒哪个,与优先级有关。
- notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。
sleep、wait、suspend 、yield之间有什么区别?
-
sleep()是Thread类中的的静态方法,可以使用在任何代码块。线程调用后让出cpu,但不释放锁,进入阻塞状态。到了时间,继续执行任务。
-
wait() 是 Object类中的普通成员方法,必须在同步方法或同步代码块执行。线程调用后让出cpu,同时释放锁,进入阻塞状态。wait有两种形式,一种是包含固定时长参数,另一种不包含固定时长参数。包含固定时长参数时在等待时长超出或者调用notify(或notifyAll)都会使线程恢复;而不包含固定时长参数的只有调用notify(或notifyAll)才会恢复。恢复后要重新申请对象锁,然后继续执行任务。
-
suspend() 使当前线程阻塞,且不会自动恢复。只有调用resume()才会使当前的线程恢复可执行状态。
-
yield() 会让当前线程结束,放弃cpu使用权,重新进入就绪队列争夺cpu
www.nowcoder.com/questionTer… blog.nowcoder.net/n/8d7e62e45…
你是如何调用wait()方法的,使用if还是循环
用循环。处在等待状态的线程可能会收到错误警告或伪唤醒,如果不在循环中检查等待条件,程序可能会在没有满足条件的时候退出。
如何暂停一条线程?
答:两种方式暂停一条线程,一个是采取Thread类的sleep()方法,一个是在同步代码中使用wait()方法.
如何停止一个正在运行的线程?
- 使用stop方法终止,但是这个方法已经过期,不被推荐使用。
- 使用interrupt方法终止线程
- run方法执行结束,正常退出
在一个对象上两个线程可以调用两个不同的同步实例方法么?
答:不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。
线程同步和线程互斥的区别
线程同步:当一个线程对共享数据进行操作的时候,在没有完成相关操作时,不允许其它的线程来打断它,否则就会破坏数据的完整性,必然会引起错误信息,这就是线程同步。 线程互斥: 而线程互斥是站在共享资源的角度上看问题,例如某个共享资源规定,在某个时刻只能一个线程来访问我,其它线程只能等待,直到占有的资源者释放该资源,线程互斥可以看作是一种特殊的线程同步。 实现线程同步的方法:
- 同步代码块:sychronized(对象){} 块
- 同步方法:sychronized修饰的方法
- 使用重入锁实现线程同步:reentrantlock类的锁又互斥功能,Lock lock = new ReentrantLock(); Lock对象的ock和unlock为其加锁
blog.nowcoder.net/n/55a59284f… zhuanlan.zhihu.com/p/346030596
synchronized 和volatile
-
sychronized 的作用是锁定当前变量/对象/代码块,只有当前线程可以访问,其他线程被阻塞。
-
volatile 本质是告诉 jvm 当前变量在寄存器中的变量是不安全的,需要从内存中读取。它有两个作用:保证内存可见性、禁止指令重排序。
二者区别:
-
synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
-
synchronized 线程阻塞,volatile 线程不阻塞。
在 Java 程序中怎么保证多线程的运行安全
线程的安全性问题:
-
原子性问题(线程切换导致):
- 含义:一个或者多个操作在 CPU 执行的过程中不被中断。
- 解决办法:JDK Atomic开头的原子类、synchronized、LOCK
-
可见性问题(缓存导致):
-
含义:一个线程对共享变量的修改后,另外一个线程能够立刻看到。缓存导致此问题。
-
解决办法:synchronized、volatile、LOCK
-
-
有序性问题(编译优化导致):
- 含义:程序执行的顺序按照代码的先后顺序执行。编译优化会导致此问题。
- 解决办法:Happens-Before 规则
ThreadLocal 是什么
我们知道线程同步机制是指多个线程共享同一个变量,而ThreadLocal为每个线程创建一个单独的变量副本,从而保证线程隔离。
ThreadLocal有一个特别重要的静态内部类ThreadLocalMap,该类才是实现线程隔离机制的关键。get()、set()、remove()都是基于该内部类进行操作,ThreadLocalMap用键值对方式存储每个线程变量的副本,key为当前的ThreadLocal对象,value为对应线程的变量副本。