Java并发编程(一)--- 多线程入门
Java并发编程(一)--- 多线程入门
1、基本概念
1.1、进程和线程
- 进程:就是一个程序,系统运行一个程序就是一个进程从创建到消亡的过程。
- 线程:一个进程在执行的过程中可以产生多个线程,比如运行360这个软件就是一个人进程在运行,如果360一遍清理垃圾,一遍杀毒,就相当于多个线程执行不同的任务(只是为了便于理解打这个比方)。
- 多线程:多个线程交替执行,如果是单核CPU多个线程会顺序执行,多核CPU的话就会根据每个CPU自己的运算器在多个CPU中同时执行。
1.2、同步和异步
-
同步:方法调用一旦开始必须等到方法调用返回后,才能继续后续的行为
-
异步:方法调用一开始方法调用就会立即返回,调用者可以立马继续后面的操作,不必等到方法返回。
关于异步比较经典以及常用的实现方式就是消息队列:在不使用消息队列的时候,用户请求数据直接写入数据库,在高并发的情况下会导致数据库压力剧增,使响应速度变慢。在使用消息队列后,用户请求数据发送消息队列后立即返回,再由消息队列的消费者进程从消息队列中获取数据异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列比数据库有更好的伸缩性),因此效应速度得到大幅度改善。
1.3、并发和并行
-
并发:表示支持同时处理多任务的能力,但是多任务并不一定同时执行。
-
并行:表示可以同时执行多个任务。
比如你吃饭的同时突然来了一个电话,你吃饭吃一半放下筷子拿起电话接电话,接完电话后继续吃剩下的饭,这是并发,可以同时处理多个任务,但是只能同时执行一个,要么接电话,要么吃饭,如果你可以一边接电话一边吃饭,那么这就是并行。
1.4、高并发
高并发是互联网分布式系统架构设计必须要考虑的问题,通常指的是系统能够并行的处理很多请求。
一般指标有:响应时间,吞吐量,每秒查询率QPS,并发用户数等。
1.5、阻塞和非阻塞
- 阻塞:不能得到结果前会阻塞当前线程。
- 非阻塞:与阻塞相反。
1.6、临界区
一般用来表示共享区域可以被多个线程同时使用,但是每一次只有一个线程可以使用它,一旦临界区被占用,其他线程必须等待。
2、线程的创建方式
2.1、继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
//测试
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
System.out.println("end");
}
}
运行结果:
end
MyThread
2.2、匿名内部类
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
super.run();
System.out.println("thread_01");
}
};
t.start();
System.out.println("end");
}
运行结果:
end
thread_01
2.3、实现Runnable接口
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("thread");
}
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();
System.out.println("end");
}
}
执行结果:
end
thread
2.4、线程池
使用线程池也是最推荐的一种方式,后面会细说
3、线程的生命周期
3.1、New(新建)
使用new关键字创建一个线程时,该线程为新建状态。
3.2、Runnable(就绪)
调用start()方法后,但是没有分配到CPU,等待系统为其分配CPU,当系统选定一个等待执行的线程后,他就会从就绪状态变成运行状态,这称作CPU的调度。
3.3、Running(运行)
执行run方法里面的代码时候。
3.4、Blocked(阻塞)
这种情况下是不能被分配CPU的,被synchronized修饰的方法或者代码块统一时刻只能被一个线程执行,竞争到锁的线程就变成Runnable状态,而其他竞争锁的线程就从Runnable状态变成Blocked状态。并发包中的lock会让线程处于等待状态而不是阻塞,只有synchronized时阻塞
3.5、Waiting(无限制等待)
这种情况同样时不能被分配CPU的,有三种情况会使线程从Runnable状态变成Waiting状态。
- 调用无参的Object.wait()方法,等到notify()或者notifyAll()唤醒时就会回到Runnable状态。
- 调用无参的Thread.join()方法。在A线程中调用B.join()方法,A线程会等到B线程执行完了才会执行,这时候Ac 处于等待状态。
- 调用LockSupport.park()方法。LockSupport是Java6引入的一个工具类,Java并发包中的锁都是基于他实现的,再调用LockSupport.unpark(Thread thread)方法就会让线程处于Runnable状态。
3.6、Time_Waiting(有限制时间等待)
- Object.wait(long timeout);
- Thread.join(long millis);
- Thread.sleep(long millis)
- LockSupport.parkNanos(Object blocked,long deadline)
- LockSupport.parkUntil(long deadline)
3.7、Dead(终止)
在我们线程执行run方法结束后,或者执行一半抛异常就是进入了终止状态。
3.7.1、如何强制终止线程
Thread.stop()方法已经被废弃,不推荐使用,因为如果你这个线程得到了锁,这个时候被stop了,这个锁也没有了,那么其他线程也就得不到这个锁了。
/**
* 使用interrupt来停止线程
*/
public class MyThread06 extends Thread {
@Override
public void run() {
for(int i = 0 ; i < 100000 ; i ++){
if(this.isInterrupted()){
System.out.println("end");
return;
}
System.out.println(Thread.currentThread().getName()+" : i = " + i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread06();
t.start();
Thread.sleep(100);
t.interrupt();
}
}
4、实例变量和线程安全
线程类中的实例变量针对其他线程有共享和不共享之分
4.1、不共享变量
/**
* 不共享变量
*/
public class MyThread01 extends Thread {
private int count = 5;
public MyThread01(){}
/**
* 构造器,
* @param name
*/
public MyThread01(String name) {
this.setName(name);
}
@Override
public void run() {
while (count> 0 ){
count--;
System.out.println(this.currentThread().getName()+" : "+count);
}
}
public static void main(String[] args) {
Thread t1 = new MyThread01("A");
Thread t2 = new MyThread01("B");
Thread t3 = new MyThread01("C");
t1.start();
t2.start();
t3.start();
}
}
执行结果:
A : 4
C : 4
C : 3
C : 2
C : 1
C : 0
B : 4
A : 3
B : 3
A : 2
A : 1
A : 0
B : 2
B : 1
B : 0
通过执行结果可以知道,每个线程有个单独的实例变量count,他们互不影响。
4.2、共享变量
/**
* 共享变量
*/
public class MyThread02 extends Thread {
private int count = 5;
@Override
public void run() {
while (count> 0 ){
count--;
System.out.println(this.currentThread().getName()+" : "+count);
}
}
public static void main(String[] args) {
MyThread02 t = new MyThread02();
Thread t1 = new Thread(t,"A");
Thread t2 = new Thread(t,"B");
Thread t3 = new Thread(t,"C");
t1.start();
t2.start();
t3.start();
}
}
执行结果:
A : 3
A : 1
A : 0
B : 3
C : 2
这我们可以看到已经错误了,我们想要的是一次递减,因为在大多数jvm中count--是分三步操作:
- 查询count值;
- 计算count-1的值;
- 给count赋新值;
这样当多个线程操作同一个共享变量的时候就会出现线程安全的问题。
解决方式:
- 使用synchronized 关键字。
- 使用AtomicInteger原子类。
这种方式不能使用volatile关键字,因为volatile只能保证可见性和防止重排序,不能保证原子性。
这些后面都会细说。
5、一些常用方法
5.1、简单方法
/**
* 简单方法
*/
public class MyThread03 extends Thread {
@Override
public void run() {
//获取当前线程对象的引用
System.out.println(this.currentThread());
//获取线程 名称
System.out.println(this.currentThread().getName());//A
this.currentThread().setName("B");
System.out.println(this.currentThread().getName());//B
//获取线程 Id标识符
System.out.println(this.currentThread().getId());//12
//获取线程的 优先级 默认为5 最低优先级1,最高优先级10
System.out.println(this.currentThread().getPriority());//5
try {
//休眠1秒
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断当前线程是否存活
System.out.println(this.currentThread().isAlive());//ture
}
public static void main(String[] args) {
Thread t = new Thread(new MyThread03(),"A");
t.start();
}
}
5.2、setDaemon(boolean on)
5.2.1、定义
守护线程也叫做服务线程,专门服务于其他用户线程的线程,如果其他用户线程都执行完毕,连mian也执行完毕,那么JVM就会停止运行,此时JVM停止运行了,守护线程也就停止执行了。
5.2.2、设置
通过setDaemon(ture)
来设置守护线程,setDaemon(ture)要放在start之前
/**
* 守护线程
*/
public class MyThread04 extends Thread {
@Override
public void run() {
System.out.println(this.currentThread().isDaemon());
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyThread04");
}
public static void main(String[] args) {
Thread t = new MyThread04();
//设置为守护线程,其他线程执行完毕,守护线程也会立马推出
t.setDaemon(true);
t.start();
System.out.println("main");
}
}
5.2.3、应用
GC线程
5.3、join()
5.3.1、join方法的用途
主要目的是同步,它可以是线程之间并行编程串行,在A线程里调用B线程的join方法,表示只有当B线程执行完毕后A线程才能继续执行
示例:
/**
* join
*/
public class MyThread05 implements Runnable {
@Override
public void run() {
for(int i = 0 ; i < 50 ; i++){
System.out.println(Thread.currentThread().getName() +" : i = " + i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread05 t = new MyThread05();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t1.join();
/**
* 主线成执行到这个位置放弃执行资格,等到t1执行完毕后再执行
*/
t2.start();
for(int i = 0 ; i < 50 ; i++){
System.out.println(Thread.currentThread().getName() +" : i = " + i);
}
}
}
执行结果:t1执行完毕后,t2和main交替执行
另:join方法中可以加参数,表示等待多少毫秒,比如在A线程中调用b.join(100),表示A线程会等待B线程100毫秒,然后AB交替执行。join(0),表示无限等待,join(0) 等价于join()。
先start()再join()
5.3.2、实现原理
源码:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
A线程中调用B线程的join()方法,相当于A线程调用B线程的wait()方法,当B线程执行完毕(或到达了等待时间),B线程会自动调用自己的notifyAll()方法。