1. 程序、进程与线程
- 程序是为完成特定任务、用某种语言编写的一组指令的集合。
- 进程是正在运行的一个程序,是操作系统调度和资源分配的基本单位。
- 线程是一个进程内部的一条执行路径,是CPU调度和资源分配的基本单位。
2. 多线程的优点
- 提高应用程序的响应速度,可增强用户体验。
- 提高计算机系统CPU的利用率。
- 改善程序结构。
3.并行与并发
- 并行是指多个CPU在同一时刻执行多个任务。
- 并发是一个CPU在一个时间段内执行多个任务。
4.创建线程
4.1继承Thread,重写run方法,调用start动线程
/**
* 继承Thread,重写run方法,调用start启动线程
*/
public class CusThread extends Thread{
/**
* main方法的线程是:main
* run方法的线程是:Thread-0
* 线程执行体
*/
@Override
public void run() {
System.out.println("run方法的线程是:" + Thread.currentThread().getName());
System.out.println("线程执行体");
}
public static void main(String[] args) {
System.out.println("main方法的线程是:" + Thread.currentThread().getName());
CusThread thread = new CusThread();
thread.start();
}
}
4.1.1 能否调用run方法代替start方法?
不能,调用run方法只是单纯的执行run方法体,而start方法会创建一个新的线程去执行run方法体。
4.1.2 已经调用start方法的线程,能否再去调用start方法?
不能,会抛出 IllegalThreadStateException 的异常。
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
/**
* 省略代码...
*/
}
4.2 实现Runnable接口, 重写run方法,调用start启动线程
/**
* 实现Runnable接口, 重写run方法,调用start启动线程
*/
public class CusThread implements Runnable{
/**
* main方法的线程是:main
* run方法的线程是:Thread-0
* 线程执行体
*/
@Override
public void run() {
System.out.println("run方法的线程是:" + Thread.currentThread().getName());
System.out.println("线程执行体");
}
public static void main(String[] args) {
System.out.println("main方法的线程是:" + Thread.currentThread().getName());
Runnable runnable = new CusThread();
Thread thread = new Thread(runnable);
thread.start();
}
}
Thead类实际上也是Runnable接口的实现类
public class Thread implements Runnable {
private Runnable target;
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
/**
* 省略代码...
*/
this.target = target;
/**
* 省略代码...
*/
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
4.3 实现Callable接口, 重写run方法,调用start启动线程
调用get方法获取返回值会阻塞当前线程的执行,等到分线程执行完毕。
/**
* 实现Callable接口, 重写run方法,调用start启动线程
*/
public class CusThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 2<<3;
}
public static void main(String[] args){
CusThread cusThread = new CusThread();
FutureTask<Integer> futureTask = new FutureTask(cusThread);
Thread thread = new Thread(futureTask);
thread.start();
Integer result = null;
try {
result = futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
}
5.常用方法
5.1 启动线程
public synchronized void start(){}
5.2 返回当前线程
public static native Thread currentThread();
5.3 休眠一段时间(不会放弃锁)
public static native void sleep(long millis) throws InterruptedException;
5.4 线程让步
public static native void yield();
5.5 阻塞线程,直到join() 方法加入的join 线程执行完为止
public final void join() throws InterruptedException {}
5.6 是否存活
public final native boolean isAlive();
5.7.获取线程优先级
public final int getPriority(){}
5.7.1Thread内部声明的三个优先级常量
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;//default
public static final int MAX_PRIORITY = 10;
6. 线程的生命周期
6.1 JDK5线程的生命周期
6.2 JDK17线程的生命周期
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
7.多线程安全问题
例如卖票超卖。
/**
* 多线程出现的问题
*/
public class SaleTicket implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
ticket--;
}else {
break;
}
}
}
public static void main(String[] args) {
Runnable saleTicket = new SaleTicket();
Thread window1 = new Thread(saleTicket);
Thread window2 = new Thread(saleTicket);
Thread window3 = new Thread(saleTicket);
window1.start();
window2.start();
window3.start();
}
}
结果:导致重复卖票
Thread-0卖了第100张票
Thread-1卖了第100张票
Thread-2卖了第100张票
Thread-1卖了第98张票
Thread-0卖了第99张票
原因:操作不是原子操作
解决办法:java同步机制
8.java同步机制
同步代码块:
synchronized (锁){ // 需要被同步的代码; }
锁可以使用this或者类.class,但必须保证多个线程使用同一把锁。
需要被同步的代码即是对共享资源的操作。
使用同步代码块解决问题:
public class SyncSaleTicket implements Runnable {
private static int ticket = 100;
@Override
public void run() {
while (true){
synchronized (this) {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
break;
}
}
}
}
public static void main(String[] args) {
Runnable saleTicket = new SyncSaleTicket();
Thread window1 = new Thread(saleTicket);
Thread window2 = new Thread(saleTicket);
Thread window3 = new Thread(saleTicket);
window1.start();
window2.start();
window3.start();
}
}
结果:没有超卖现象
Thread-0卖了第100张票
Thread-2卖了第99张票
Thread-1卖了第98张票
Thread-2卖了第97张票
使用同步方法:
非静态同步方法的锁是this,静态同步方法的锁是当前类.class,考虑方法是否非静态来保证多个线程使用同一把锁。
public class SyncMethodSaleTicket implements Runnable {
private static int ticket = 100;
private static boolean flag = true;
@Override
public void run() {
while (flag){
sale();
}
}
public synchronized void sale() {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
flag = false;
}
}
public static void main(String[] args) {
Runnable saleTicket = new SyncMethodSaleTicket();
Thread window1 = new Thread(saleTicket);
Thread window2 = new Thread(saleTicket);
Thread window3 = new Thread(saleTicket);
window1.start();
window2.start();
window3.start();
}
}
synchronized的好处和弊端
synchronized解决了线程的安全问题,但是synchronized是串行化执行的,相对无锁结构效率比较低。
9.单例之饿汉式
public class Singleton {
//避免指令重拍
private static volatile Singleton singleton ;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
10.线程死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。
死锁的案例:
public class DeathLock {
private static final Object obj1 = new Object();
private static final Object obj2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
synchronized (obj1) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "持有资源obj1,希望得到资源obj2");
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + "得到资源obj2");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
synchronized (obj2) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "持有资源obj2,希望得到资源obj1");
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "得到资源obj1");
}
}
}
});
thread1.start();
thread2.start();
System.out.println("方法结束.....");
}
}
结果:可以看出Thread-1得不到obj1,Thread-0得不到obj2,处在一个僵持的状态。
方法结束.....
Thread-1开始执行
Thread-0开始执行
Thread-1持有资源obj2,希望得到资源obj1
Thread-0持有资源obj1,希望得到资源obj2
诱发死锁的原因:
互斥条件、占用且等待、不可抢夺、循环等待。
解决死锁:
- 互斥条件无法被破坏,因为线程需要互斥解决安全问题。
- 可以考虑一次性申请所需要的资源,这样就不存在等待问题。
- 占用资源的线程在申请其他资源的时候,如果申请不到,就考虑放弃已经占有的资源。
- 可以将资源改为线性顺序,申请资源的时候先申请序号较小的,这样避免循环等待的问题。
11. Lock
从JDK 5.0开始,Java提供的显式定义同步锁,在java.util.concurrent.locks包下的接口,需要确保多线程使用同一个Lock,需要声明为static final。
package java.util.concurrent.locks;
public interface Lock {}
public class LockSaleTicket implements Runnable {
private static int ticket = 100;
private static boolean flag = true;
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
while (flag){
sale();
}
}
public void sale() {
lock.lock();
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
flag = false;
}
lock.unlock();
}
public static void main(String[] args) {
Runnable saleTicket = new LockSaleTicket();
Thread window1 = new Thread(saleTicket);
Thread window2 = new Thread(saleTicket);
Thread window3 = new Thread(saleTicket);
window1.start();
window2.start();
window3.start();
}
}
12.synchronized 与Lock 的对比
优先使用顺序:
Lock——>同步代码块(已经进入了方法体,分配了相应资源)——> 同步方法 (在方法体之外)。
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)
13.线程通信
线程通信是指线程利用wait() 与notify() 和notifyAll()进行一个协调合作,以上都是object的方法,此三个方法都要在同步代码块中使用,调用者而必须是同步监视器。
例如交替打印1-100。
public class PrintNumber implements Runnable {
private static int num = 1;
@Override
public void run() {
while (true){
synchronized (this) {
this.notifyAll();
if(num <= 100){
System.out.println(Thread.currentThread().getName() +"打印了" + num );
num++;
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
break;
}
}
}
}
public static void main(String[] args) {
Runnable saleTicket = new PrintNumber();
Thread window1 = new Thread(saleTicket);
Thread window2 = new Thread(saleTicket);
window1.start();
window2.start();
}
}
结果 :
Thread-0打印了1
Thread-1打印了2
Thread-0打印了3
Thread-1打印了4
Thread-0打印了5
Thread-1打印了6
14.sleep 和 wait 的区别
- sleep是Thread的一个静态方法,wait是Object的一个方法。
- sleep和wait调用后都会进入阻塞状态
- sleep不会释放锁资源,wait会释放锁资源进入等待池中。
15. 线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中.
好处:
- 提高响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
- 便于线程管理。
线程池的使用:
public class ThreadPool {
private static final ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("runnable .... 1");
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
System.out.println("runnable .... 2");
}
};
service.execute(runnable1);
service.execute(runnable2);
System.out.println("main ....");
service.shutdown();
}
}
15. 1线程池的核心创建
依次为核心线程数、最大线程数、存活时间、存活时间单位、队列、线程工厂、拒绝策略
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}