一、基本概念
进程:资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。
线程:是程序执行时的最小单位,它是进程的一个执行流。
进程与线程的区别:
- 进程是资源分配的最小单位,线程是程序执行的最小单位;
- 进程拥有独立的地址空间,线程共享进程中的数据,使用同一个地址空间;
- 进程间的通信比较复杂,线程间共享同一进程下的数据;
多进程与多线程的优劣:
- 创建进程比创建线程的开销大;
- 进程间的通信比线程间的要慢;
- 多进程比多线程稳定,多线程中的任何一个线程的崩溃都会导致整个进程的崩溃;
二、使用多线程
1.创建新线程
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
//线程启动后执行指定的代码,方法一:从Thread派生一个自定义类,然后重写run()方法
Thread t = new MyThread();
//start()方法会在内部自动调用run()方法
t.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
System.out.println("This is my Tread!");
}
}
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
//线程启动后执行指定的代码,方法二:创建Thread实例时,传入一个Runnable实例
Thread t = new Thread(new MyRunnable());
t.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("This is my Tread!");
}
}
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
//线程启动后执行指定的代码,方法三:使用lambda表达式
Thread t = new Thread(()->{
System.out.println("This is my Thread!");
});
t.start();
}
}
- 线程本身由操作系统调度,程序本身无法确定线程的调度顺序。
- Thread.setPriority(int n) // 1~10,默认值5,优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
System.out.println("This is the first thread start!");
//线程启动后执行指定的代码,方法三:使用lambda表达式
Thread t = new Thread(()->{
System.out.println("This is the second thread start!");
System.out.println("This is the second thread end!");
});
t.start();
try {
//可以在线程中调用Thread.sleep()强迫当前线程暂停一段时间
Thread.sleep(10);
}catch (InterruptedException e){}
System.out.println("This is the first thread end!");
}
}
2.线程状态
线程的五种状态:
- New:新创建的线程,尚未执行;
- Runnable:运行中的线程,正在执行
run()
方法的Java代码; - Blocked:运行中的线程,因为某些操作被阻塞而挂起;
- Waiting:运行中的线程,因为某些操作在等待中;
- Timed Waiting:运行中的线程,因为执行
sleep()
方法正在计时等待; - Terminated:线程已终止,因为
run()
方法执行完毕。
线程终止的原因:
- 线程正常终止:
run()
方法执行到return
语句返回; - 线程意外终止:
run()
方法因为未捕获的异常导致线程终止; - 对某个线程的
Thread
实例调用stop()
方法强制终止。
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
System.out.println("This is the first thread start!");
Thread t = new Thread(()->{
System.out.println("This is the second thread start!");
System.out.println("This is the second thread end!");
});
t.start();
//个线程可以等待另一个线程直到其运行结束
try {
t.join();
}catch (InterruptedException e){}
System.out.println("This is the first thread end!");
}
}
3.中断线程
- 线程一给线程二发送中断请求
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
System.out.println("This is the first thread start!");
Thread t = new MyThread();
t.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
//给t线程发送中断请求
t.interrupt();
try {
t.join();
}catch (InterruptedException e){}
System.out.println("This is the first thread end!");
}
}
class MyThread extends Thread {
@Override
public void run(){
int n = 0;
while (!interrupted()) {
n++;
System.out.println(n);
}
}
}
- 线程一给正在等待线程三中断的线程二发送中断请求
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
System.out.println("This is the first thread start!");
Thread t = new MyThread();
t.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
//给t线程发送中断请求
t.interrupt();
try {
t.join();
}catch (InterruptedException e){}
System.out.println("This is the first thread end!");
}
}
class MyThread extends Thread {
@Override
public void run(){
Thread helloThread = new HelloThread();
helloThread.start();
try {
helloThread.join();
}catch (InterruptedException e){
System.out.println("Interrupt");
}
helloThread.interrupt();
}
}
class HelloThread extends Thread {
@Override
public void run(){
int n = 0;
while (!interrupted()) {
n++;
System.out.println(n);
}
}
}
- 使用running标志位来标识线程是否应该继续运行。
- volatile关键字是为了告诉虚拟机:每次访问变量时,总是获取主内存的最新值;每次修改变量后,立刻回写到主内存。
- 在Java虚拟机中,变量的值保存在主内存中,但是,当线程访问变量时,它会先获取一个副本,并保存在自己的工作内存中。如果线程修改了变量的值,虚拟机会在某个时刻把修改后的值回写到主内存,但是,这个时间是不确定的。
- volatile关键字解决的是可见性问题:当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值。
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
System.out.println("This is the first thread start!");
MyThread t = new MyThread();
t.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
t.running = false;
try {
t.join();
}catch (InterruptedException e){}
System.out.println("This is the first thread end!");
}
}
class MyThread extends Thread {
public volatile boolean running = true;
@Override
public void run(){
System.out.println("start!");
int n = 0;
while (running) {
n++;
System.out.println(n);
}
System.out.println("end!");
}
}
4.守护线程
- 守护线程是为其他线程服务的线程;
- 所有的非守护线程都执行完毕后,虚拟机退出;
- 守护线程不能持有需要关闭的资源。
package Thread_demo;
public class CreateThread {
public static void main(String[] args) {
System.out.println("This is the first thread start!");
MyThread t = new MyThread();
//标记t为守护线程
t.setDaemon(true);
t.start();
System.out.println("This is the first thread end!");
}
}
class MyThread extends Thread {
@Override
public void run(){
System.out.println("start!");
int n = 0;
while (true) {
try {
Thread.sleep(1000);
n++;
System.out.println(n);
} catch (InterruptedException e) {
break;
}
}
}
}
5.线程同步
- 多线程同时读写共享变量时,会造成逻辑错误,因此需要通过
synchronized
同步; - 同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码,加锁对象必须是同一个实例;
- 对JVM定义的单个原子操作不需要同步。
package threadDemo;
public class demo {
public static void main(String[] args) {
Thread add = new AddThread();
Thread dec = new DecThread();
add.start();
dec.start();
try {
add.join();
} catch (InterruptedException e) {}
try {
dec.join();
} catch (InterruptedException e) {}
System.out.println(Count.count);
}
}
class Count{
public static int count = 0;
//创建一个共享实例lock
//JVM只保证同一个锁在任意时刻只能被一个线程获取,但两个不同的锁在同一时刻可以被两个线程分别获取。
public static final Object lock = new Object();
}
class AddThread extends Thread{
public void run(){
for (int i = 0; i < 1000; i++)
//获得锁后加锁,代码块执行完毕自动释放锁
synchronized (Count.lock
){
Count.count++;
}
}
}
class DecThread extends Thread{
public void run(){
for (int i = 0; i < 1000; i++)
synchronized (Count.lock
){
Count.count--;
}
}
}
- 不需要加锁的操作(JVM规范定义了几种原子操作):
(1)基本类型(long和double除外)赋值;
(2)引用类型赋值;
6.同步方法
- 用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;
package threadDemo;
public class demo {
public static void main(String[] args) {
Count c1 = new Count();
Thread thread1 = new Thread(()->{
c1.add(3);
});
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(c1.get());
Thread thread2 = new Thread(()->{
c1.dec(3);
});
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(c1.get());
}
}
class Count{
private int count = 0;
public void add(int n){
synchronized (this){
count+=n;
}
}
//等价于add方法加锁
public synchronized void dec(int n){
count-=n;
}
public int get(){
return count;
}
}
- 一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的;
- 一个类没有特殊说明,默认不是thread-safe;
- 对static方法添加synchronized,锁住的是该类的Class实例。
7.死锁
- JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。
- Java的synchronized锁是可重入锁;
- 死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待;
- 避免死锁的方法是多线程获取锁的顺序要一致。
8.wait和notify用于多线程协调运行
package threadDemo;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class demo {
public static void main(String[] args) throws InterruptedException{
TaskQueue taskQueue = new TaskQueue();
ArrayList<Thread> task = new ArrayList();
//创建5个线程等待获取task
for (int i = 0; i < 5; i++) {
Thread t = new Thread() {
public void run() {
//执行task
while (true) {
try {
String s = taskQueue.getTask();
System.out.println("execute: "+ s);
} catch (InterruptedException e) {
return;
}
}
}
};
t.start();
task.add(t);
}
//创建一个线程不断添加10个添加task
Thread add = new Thread(()->{
for (int i = 0; i < 10; i++) {
String t = "t-" + Math.random();
System.out.println("add: " + t);
taskQueue.addTask(t);
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
});
add.start();
add.join();
Thread.sleep(100);
for (Thread t: task){
t.interrupt();
}
}
}
class TaskQueue {
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s) {
this.queue.add(s);
//唤醒所有等待线程
this.notifyAll();
}
public synchronized String getTask() throws InterruptedException{
while (queue.isEmpty()) {
//使线程进入等待状态
this.wait();
}
return queue.remove();
}
}
9.ReentrantLock
- ReentrantLock可以替代synchronized锁进行同步,但获取锁更加安全;
- 需要先获取到锁,再进入try{}代码块,最后在finally中释放锁;
- 可以使用tryLock()尝试获取锁。
//对比
class Count1 {
private int count;
public void add(int n) {
synchronized (this) {
count += n;
}
}
}
class Count2 {
private int count;
//创建一个锁
private final Lock lock = new ReentrantLock();
public void add(int n) throws InterruptedException{
//获取锁, 1s后没有获取到锁tryLock返回false
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
count += n;
} finally {
lock.unlock();
}
}
}
}
10.Condition
- 使用Condition对象来实现ReentrantLock的wait和notify;
class TaskQueue1 {
private final Lock lock = new ReentrantLock();
//必须从lock对象获取
private final Condition condition = lock.newCondition();
private Queue<String> queue = new LinkedList<>();
public void add(String s) {
lock.lock();
try {
queue.add(s);
//类似于notify
condition.signal();
}finally {
lock.unlock();
}
}
public String get() throws InterruptedException{
lock.lock();
try {
while (queue.isEmpty()) {
//类似于wait
condition.await();
}
return queue.remove();
}finally {
lock.unlock();
}
}
}
- await()可以在等待指定时间后,没有被signal()唤醒的话,自己醒来;
if (condition.await(1, TimeUnit.SECONDS)) {
//其他线程唤醒
}else {
//自己唤醒
};
11.ReadWriteLock
- ReadWriteLock只允许一个线程写入,允许多个线程在没有写入时同时读取,可以提高读取效率,适合读多写少的场景。
class Counter {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock rLock = readWriteLock.readLock();
private final Lock wLock = readWriteLock.writeLock();
private int[] counts = new int[10];
public void inc(int i) {
wLock.lock();
try {
counts[i]++;
}finally {
wLock.unlock();
}
}
public int[] get() {
rLock.lock();
try {
return Arrays.copyOf(counts, counts.length);
}finally {
rLock.unlock();
}
}
}
12.StampedLock
- 乐观锁:乐观的估计读的过程中大概率不会有写入;
- 悲观锁:读的过程中拒绝有写入;
- StampedLock是不可重入锁;
package threadDemo;
import java.util.concurrent.locks.StampedLock;
public class Point {
private final StampedLock stampedLock = new StampedLock();
private double x;
private double y;
public void move(double x_a, double y_a) {
//获取写锁
long stamp = stampedLock.writeLock();
try {
x = x_a;
y = y_a;
}finally {
//释放写锁
stampedLock.unlockWrite(stamp);
}
}
public double get() {
//获得一个乐观读锁
long stamp = stampedLock.tryOptimisticRead();
double current_x = x;
double current_y = y;
//检查乐观锁读锁后是否有其他写锁发生
if (!stampedLock.validate(stamp)) {
//获取一个悲观锁
stamp = stampedLock.readLock();
try {
current_x = x;
current_y = y;
}finally {
//释放一个悲观锁
stampedLock.unlock(stamp);
}
}
return current_x + current_y;
}
}
13.Concurrent集合
- java.util.concurrent包提供的线程安全的并发集合可以大大简化多线程编程。
14.Atomic
- java.util.concurrent.atomic包提供了一组原子操作的封装类。