多线程
线程与进程
进程:
- 是指一个内存中的应用程序,每个进程都有一个独立的内存空间。
线程:
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程至少有一个线程。
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分为若干的线程
线程的调度
分时调度
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的是抢占式调度。
- CPU使用抢占式调度模式在多个线程中进行着告诉切换。对于一个CPU而言,某个时候,只能执行一个线程,而CPU在多个线程间切换速度相对我们的感觉要快,看上去就像在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但是能够提高程序运行效率,让CPU的使用率更高。
同步与异步
同步:排队执行,效率低但安全。
异步:同步执行,效率高但数据不安全。
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一个时刻发生(同时发生)
继承Thread
实现多线程的一种方式
实例:
public class Demo4 {
public static void main(String[] args) {
//Thread
MyThread m = new MyThread();
m.start();
for (int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
class MyThread extends Thread{
/**
* run方法就是线程要执行的人物方法
*/
@Override
public void run() {
//这里的代码就是一条新的执行路径。
//这个执行路径的触发方式,不是调用run方法,而是通过Thread对象的start()来启动任务
for (int i=0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
两条线程所执行的时序图:
实现Runnable
第二种实现多线程的方法
实例:
public class Demo5 {
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
Thread t = new Thread(m);
t.start();
for (int i=0;i<5;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println("锄禾日当午"+i);
}
}
}
实现Runnable接口与继承Thread相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程执行相同的任务。
- 可以避免单继承所带来的局限性。
- 任务与线程本身是分离的,提高了程序的健壮性。
- 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类型的任务。
Thread也有优势比如可以很简单的实现方式,通过匿名内部类的方法开启一个线程。
例:
public class Demo5 {
public static void main(String[] args) {
//匿名内部类方法开启新的线程
new Thread(){
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println("锄禾日当午"+i);
}
}
}.start();
for (int i=0;i<5;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
Thread类
常用构造方法:
构造器 | 描述 |
---|---|
Thread() | 分配新的 Thread对象。 |
Thread(Runnable target) | 分配新的 Thread对象。 |
Thread(Runnable target, String name) | 分配新的 Thread对象。 |
Thread(String name) | 分配新的 Thread对象。 |
常用的方法:
变量和类型 | 方法 | 描述 |
---|---|---|
long | getId() | 返回此Thread的标识符。 |
String | getName() | 返回此线程的名称。 |
int | getPriority() | 返回此线程的优先级。 |
void | setPriority(int newPriority) | 更改此线程的优先级。 |
static Thread | currentThread() | 返回对当前正在执行的线程对象的引用。 |
void | start() | 导致此线程开始执行; Java虚拟机调用此线程的run方法。 |
static void | sleep(long millis) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 |
static void | sleep(long millis, int nanos) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。 |
void | setDaemon(boolean on) | 将此线程标记为 daemon线程或用户线程。 |
daemon(守护线程)是依附于用户线程的。
面试题:
如何将一个线程停止:
不能使用stop()方法,因为stop()方法极有可能导致操作在没有执行完的时候就被终止导致资源没有被释放,从而占用资源。
正确的方法是使用标记的方法,在线程执行过程中不断观察,一旦标记改变,return返回run方法就可以了。
线程阻塞(耗时操作)
简单的可以理解为所有需要消耗时间的操作。例:文件读取,接受用户输入
线程中断
概述:
一个线程是一个独立的执行路径,他是否应该结束,应该由其自身决定。
使用interrupt()方法进行中断操作。
守护线程
用于守护用户线程。
用户线程:当一个进程不包含任何存活的用户线程时,进程结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动结束。
如何设置:
//只需要在线程开始前将其设置为守护线程即可。
Thread t1 = new Thread(new MyRunnable());
//这样就将t1设置为了守护线程。
t1.setDaemon(true);
线程安全问题
线程不安全 案例:
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
@Override
public void run() {
while(count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,剩余票数为:"+count);
}
}
}
卖票(因为多个线程去争抢同一个数据count),所以有可能出现count=-1或者-2的情况,因为一旦在还没减到0的时候其他的线程也可能会抢到时间片导致线程并没有结束,所以并不安全。
线程安全
线程同步synchronized
**线程不安全解决方案1:**同代码块
**格式:**synchronized(锁对象){}
注:每一个线程要看同一把锁,这样才有会实现线程安全,如果每个人都看自己的锁,那是没有意义的。
例:
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
//对象要new在run()的外部,这样可以保证三个线程看的是同把锁,如果new在run()中,那每个线程在调用run()的时候都会自己生成一把锁,这样同步代码块就失去了意义。
private Object o = new Object();
@Override
public void run() {
while(true){
//在while循环里保证将卖票这个操作上锁
synchronized (o){
if (count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余票数为:"+count);
}
}
}
}
}
**线程不安全解决方案2:**同代方法
用方法来实现
例:
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
@Override
public void run() {
while(true){
boolean flag = sale();
if (!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余票数为:"+count);
return true;
}
return false;
}
}
**注:**如果一个类中有很多个同步方法,那么他们用的锁都是同一个this,当其中一个方法执行,其他的方法也需要等待。
**线程不安全解决方案3:**显示锁Lock
例:
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
//显示锁
private Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
l.lock();
if (count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余票数为:"+count);
}else {
break;
}
l.unlock();
}
}
}
公平锁
java中默认的都是不公平的锁(同步代码块、同步方法、显示锁)。
Lock实现公平锁,显示锁的构造方法可以传入一个布尔类型的值默认为false,设置为true则将Lock更改为公平锁。
线程死锁
案例:
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c, Police p) {
this.c = c;
this.p = p;
}
@Override
public void run() {
p.say(c);
}
}
static class Culprit{
public synchronized void say(Police p){
System.out.println("罪犯:你放了我,我放了人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被放了,警察也救到了人质");
}
}
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我放过你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察就到了人质,罪犯被放走了");
}
}
多线程通讯问题
线程的六种状态
NEW:尚未启动的线程处于此状态。
RUNNABLE:在Java虚拟机中执行的线程处于此状态。
BLOCKED:被阻塞等待监视器锁定的线程处于此状态。
WAITING:无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING:正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED:已退出的线程处于此状态
带返回值的线程Callable
接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
使用步骤:
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start();
Runnable 与Callable
相同点:都是接口、都可以编写多线程程序、都采用Thread.start()启动线程。
不同点:
1.Runnable没有返回值,Callable可以返回执行结果。
2.Callable的cal()允许抛出异常,Runnable的run()不能抛出异常。
获取返回值:Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的优点:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
缓存线程池
例:
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
}
定长线程池
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名称:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名称:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名称:"+Thread.currentThread().getName());
}
});
}
但线程线程池
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
}
周期定长线程池
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(“线程名称:”+Thread.currentThread().getName());
}
},5,TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(“线程名称:”+Thread.currentThread().getName());
}
},3,1, TimeUnit.SECONDS);
}
Lambda表达式
函数式编程思想。
注:想要实现Lambda表达式的接口必须只有一个静态方法。因为Lambda表达式是为了实现这个方法所以才可以替换为一个函数表达式
例:
public static void main(String[] args) {
print((int x, int y) -> {
return x+y;
},100,200);
}
public static void print(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}
static interface MyMath{
int sum(int x,int y);
}