一、线程和进程
1、进程
- 进程指的是计算机内存中开辟的一块空间。例如:QQ、360等程序
2、线程
- 线程是进程中的一个执行流程
3、多线程
- 两个或两个以上并发的执行流程
4、多线程的原理
- 单cpu单内核在某一时刻只能执行一个线程(执行流程/任务),多个线程之间是通过轮询执行的。宏观上是同时执行了多个任务,但实际上只执行了一个任务。
5、主线程和子线程
- 主线程是最先启动的线程。在main()方法中,默认线程名叫main。
- 子线程是在主线程中创建出来的。默认线程名叫Thread-N,N代表序号,从0开始。
二、创建子线程的三种方式
1、继承Thread类
package com.gaj.day03;
/**
* 多线程 创建子线程
* 创建子线程方案一:继承Thread类
* 特点:继承类
* @author Jan
*
*/
class MyThread01 extends Thread{
// 默认子线程名为Thread-0, 修改子线程名为myThread
public MyThread01(String name){
this.setName(name);
}
@Override
public void run() {
for(int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread() + ":" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 新建线程
MyThread01 myThread = new MyThread01("myThread");
// 调用start() 处于就绪状态
myThread.start(); // start()方法只能启动一次
// 不能直接调用run()方法 这是主线程在执行
// myThread.run();
// 若优先级相同,先执行主线程 和代码顺序无关
for(int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread() + ":" + i);
}
}
}
2、实现Runnable接口
package com.gaj.day03;
/**
* 多线程 创建子线程
* 创建子线程方案二:实现runnable接口,重写run()方法
* 特点:函数式接口
* @author Jan
*
*/
class MyThread02 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class Demo2 {
/**
* 争夺时间片
* 不是按顺序执行的,(优先级相同的情况)只是靠前的代码执行几率要更大些
* @param args
*/
public static void main(String[] args) {
// 方式一:普通创建 适合使用多次
MyThread02 myThread = new MyThread02();
Thread thread = new Thread(myThread, "subThread1");
thread.start();
// 方式二:匿名内部类 适合使用一次
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}, "subThread2").start();
// 方式三:Lambda表达式 对匿名内部类的简化
new Thread(() -> {for(int i = 0; i < 10 ; i ++) System.out.println(Thread.currentThread().getName() + ":" + i);}, "subThread3").start();
}
}
3、实现Callable接口
package com.gaj.day03;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 多线程 创建子线程
* 创建子线程方案三:实现callable<T>接口,重写call()方法
* 特点:函数式接口,有返回值,call()能抛异常
* @author Jan
*
*/
class MyThread03 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
return sum;
}
}
public class Demo3{
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 方式一:
MyThread03 myThread = new MyThread03();
FutureTask<Integer> futureTask = new FutureTask<>(myThread);
Thread thread = new Thread(futureTask, "subThread01");
thread.start();
// 通过FutureTask.get()方法获取返回值
System.out.println("返回值:" + futureTask.get());
// 方式二:匿名内部类,拿不到返回值结果
new Thread(new FutureTask<List<Integer>>(new Callable<List<Integer>>() {
List<Integer> list = new ArrayList<>();
@Override
public List<Integer> call() throws Exception {
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
list.add(i);
}
return list;
}
}), "subThread02").start();
//方式三:将FutureTask抽取出来拿结果
FutureTask<List<Integer>> ft = new FutureTask<List<Integer>>(new Callable<List<Integer>>() {
List<Integer> list = new ArrayList<>();
@Override
public List<Integer> call() throws Exception {
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
list.add(i);
}
return list;
}
});
new Thread(ft, "subThread03").start();
System.out.println(ft.get());
}
}
注意:需要通过FutureTask的get()方法去接收返回值。
4、三种方式的区别
- 继承Thread类:当前的子类本身就是线程类,不利于资源(对象)的共享。
- 实现Runnable和Callable接口:
共同点:
有利于资源(资源)共享、都是函数式接口
不同点:
Runnable | Callable | |
---|---|---|
实现的方法 | run() | call() |
返回值 | 没有返回值 | 有返回值 |
异常 | 没有声明异常 | 必须声明异常 |
三、线程的状态
线程共五种状态:新建、就绪、运行、阻塞和死亡。
1. 新建(New)
创建一个Thread对象。 new Thread();
2. 就绪(Runnable)
就绪处于等待CPU调用执行状态。调用start()方法进入就绪状态。
3. 运行(Running)
运行状态是指CPU调用了某个线程,执行了线程的任务,子线程中run()方法或call()方法。
4. 阻塞(Blocked)
运行状态的线程由于某种原因暂停CPU的调用执行。
5. 死亡(Dead)
死亡状态是指线程的结束。
四、blocked的一些方法
1、sleep()、join()、interrupt()、setDaemon()
package com.gaj.day03;
/**
* sleep()和join() 睡眠暂停/优先加入
* interrupt() 线程中断
* Daemon 守护线程
* @author Jan
*
*/
class SubThread04 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 10; i++){
if(i == 3){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "进入了异常处理阶段");
}
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new SubThread04(), "1号子线程");
Thread thread2 = new Thread(new SubThread04(), "2号子线程");
// thread1.setDaemon(true); // 将thread1设置为守护线程
thread1.start();
thread2.start();
thread1.interrupt(); // 程序中断,thread1进入异常处理阶段 前提是该线程处于sleep或join状态
// 如何让main程序最后执行?
// 方式一:
// Thread.sleep(1000); // 暂停,睡眠 单位:毫秒
// 方式二:
// thread1.join(); // 让该线程先执行
// thread2.join();
// System.out.println(Thread.currentThread().getName() + "线程结束");
// 方式三:
while(!thread1.isAlive() && !thread2.isAlive()){ // 判断线程是否存活,存活为True
System.out.println(Thread.currentThread().getName() + "线程结束");
System.exit(0);
}
}
}
sleep(毫秒):线程A等待sleep了,那么线程A就处于了阻塞状态,当时间到了,恢复到就绪状态,等待cpu调用执行。
join():让其它线程先执行完。 在线程A中调用了线程B的join方法,那么线程A等待线程B执行完后才能恢复到就绪状态,等待cpu调用执行。
interrupt():线程中断。当执行了A.interrupt()方法,那么线程A被中断,进入到异常处理阶段。
Daemon 守护线程 只要所有的被守护线程结束了,那么所有的守护线程都会结束。
2、sleep()、wait()、notify()、notifyAll()
package com.gaj.day03;
/**
* wait()和notify()、notifyAll() sleep()
*
* @author Jan
*
*/
class SubThread05 implements Runnable {
int i = 10;
@Override
synchronized public void run() {
for (; i >= 0; i--) {
if (i == 5 && Thread.currentThread().getName().equals("1号子线程")) {
try {
// Thread.sleep(1000); // 没有释放锁,另一个线程无法执行
// wait(1000); // 等待1秒后,释放锁了
wait(); // 不手动唤醒,一直等待;没有带参数的需要notify()/notifyAll()唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i == 2){
notify(); // 唤醒等待池中的任意一个线程
// notifyAll(); // 唤醒等待池中所有线程
System.out.println("已唤醒"); //会输出一个-1才结束
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class Demo5 {
public static void main(String[] args) {
SubThread05 subThread = new SubThread05();
new Thread(subThread, "1号子线程").start();
new Thread(subThread, "2号子线程").start();
}
}
不同点:
sleep | wait |
---|---|
sleep(参数 毫秒) | wait(毫秒);wait() |
没有放弃锁 | 放弃了锁 |
3、yield()、setPriority()
package com.gaj.day03;
/**
* yield() 线程让步* Priority 优先级 1-10 优先级越高 被优先执行的几率越高 当然还有很多其他阻塞因素影响
* 由于cpu执行速度极快 有些情况下即使让步以及设置优先级也看不出来
* @author Jan
*
*/
class SubThread06 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 10; i++){
if(i == 7 && Thread.currentThread().getName().equals("1号子线程")){
Thread.yield(); // 让步,回到就绪状态
System.out.println("1号子线程让步了");
}
System.out.println(Thread.currentThread()+ ":" + i);
}
}
}
public class Demo6 {
public static void main(String[] args) {
SubThread06 subThread = new SubThread06();
Thread thread1 = new Thread(subThread, "1号子线程");
Thread thread2 = new Thread(subThread, "2号子线程");
// 设置优先级
// public final static int NORM_PRIORITY = 5; 默认为5
thread1.setPriority(Thread.MIN_PRIORITY); // 1
thread2.setPriority(Thread.MAX_PRIORITY); // 10
thread1.start();
thread2.start();
}
}
yield() 线程让步 让正在运行的线程回到就绪状态。
Priority优先级:理论上,等优先级线程有同等的权利使用CPU,优先级高的线程比优先级低的线程获得更多的CPU时间;但实际上,线程获得的CPU时间通常由包括优先级在内的多个因素决定。
五、synchronized和ReentrantLock
1、synchronized
同步:当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步。
当一个线程需要锁定,它必须进入管程(管程:使用资源独占的一种权力)
package com.gaj.day03;
/**
* synchronization关键字 同步锁
* 访问同步代码块/同步方法时 需要争夺锁,争夺锁成功的线程进入锁定池,代码块/方法执行完成后释放锁
* @author Jan
*
*/
// 两人向同一账户存钱,每人存500
class SubThread07 implements Runnable{
private int money = 0;
// 同步方法 锁this对象
// synchronized public void setMoney(){
public void setMoney(){
this.money += 100;
System.out.println(Thread.currentThread().getName() + "存款:100,余额:" + money);
}
@Override
public void run() {
// 同步代码块
synchronized (this) {
for(int i = 0; i < 5; i++){
setMoney();
}
}
}
}
public class Demo7 {
public static void main(String[] args) {
SubThread07 subThread = new SubThread07();
new Thread(subThread, "赵四").start();
new Thread(subThread, "王五").start();
}
}
-
获得锁:
当线程 执行 同步代码块或同步方法时,需要获得同步锁。当线程A获得同步锁成功了,那么其它线程就不能再访问此对象的同步代码块或同步方法了,只有线程A释放了锁,那么其它线程才可以继续争夺锁。
-
释放锁:
1.同步代码块或同步方法的代码都执行完了就会释放锁;
2.同步代码块或同步方法中有没处理的Exception或 遇到了Error那么或自动释放锁;
3.同步代码块或同步方法中break; return ;结束了方法或代码块,那么会自动释放锁;
4.同步代码块或同步方法中wait()那么也会释放锁。
2、ReentrantLock
公平锁(可重入锁)
try{ 加锁lock } finally{ 释放锁unlock }
package com.gaj.day03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock -> ReentrantLock 公平锁
* 通过使用lock()和unlock()方法进行加锁和解锁
* @author Jan
*
*/
// 两人向同一账户存钱,每人存500
class SubThread08 implements Runnable {
private int money = 0;
Lock lock = new ReentrantLock(true);
public void setMoney() {
try {
lock.lock();
this.money += 100;
System.out.println(Thread.currentThread().getName() + "存款:100,余额:" + money);
} catch (Exception e) {
e.printStackTrace();
}finally{
// 一定要释放
lock.unlock();
}
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
setMoney();
}
}
}
public class Demo8 {
public static void main(String[] args) {
SubThread08 subThread = new SubThread08();
new Thread(subThread, "赵四").start();
new Thread(subThread, "王五").start();
}
}
试图获得锁:tryLock() boolen 获得锁成功 true
试图获得锁: tryLock(long,TimeUnit) 再指定的时间内获得锁,获得成功返回true
package com.gaj.day03;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock(true) 释放锁后让等得更久的线程先执行
* @author Jan
*
*/
class SubThread09 implements Runnable{
Lock lock = new ReentrantLock(true);
@Override
public void run() {
boolean tag = false;
try {
tag = lock.tryLock(1, TimeUnit.SECONDS); // 在一定时间内试图获得锁 返回True
// tag = lock.tryLock(); // 不给时间可能会冲突 有一个线程直接走else不执行了
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tag){
System.out.println(Thread.currentThread().getName() + "获得锁成功!");
try {
for(int i = 0; i < 10; i++ ){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}finally{
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了锁");
}
}else{
System.out.println(Thread.currentThread().getName() + "获得锁失败!");
}
}
}
public class Demo9 {
public static void main(String[] args) {
SubThread09 subThread = new SubThread09();
new Thread(subThread, "1号子线程").start();
new Thread(subThread, "2号子线程").start();
}
}
不同点:
synchronized | Lock |
---|---|
简单 | 创建对象 |
不能显示的获得了所和释放锁 | 可以显示的获得锁和释放锁 |
关键字(同步对象) | 提供更多的功能。例如:可以获得锁的状态tryLock() |
六、多线程下的单例模式
package com.gaj.day04;
/**
* 单例模式
* 懒汉式
* 饿汉式
* 最佳线程安全单例模式
*/
// 懒汉式 线程不安全 加上synchronized -> 线程安全
class LazySingle{
private static LazySingle lazySingle; // null;
private LazySingle(){}
synchronized public static LazySingle getInstance(){
if(lazySingle == null){
lazySingle = new LazySingle();
}
return lazySingle;
}
}
// 饿汉式 线程安全
// 但是Class.forName() 也会触发类加载
class HungrySingle{
private static HungrySingle hungrySingle = new HungrySingle(); // 初始化类时加载一次
public static HungrySingle getInstance(){
return hungrySingle;
}
}
// 单例模式线程安全的最佳方案
class PreferredSingle{
// 静态内部类
private static class SingleInstance{
private static PreferredSingle preferredSingle = new PreferredSingle();
}
private PreferredSingle(){}
public static PreferredSingle getInstance(){
return SingleInstance.preferredSingle;
}
}
public class Demo1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("懒汉式" + LazySingle.getInstance());
System.out.println("饿汉式" + HungrySingle.getInstance());
System.out.println("最佳方案" + PreferredSingle.getInstance());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("懒汉式" + LazySingle.getInstance());
System.out.println("饿汉式" + HungrySingle.getInstance());
System.out.println("最佳方案" + PreferredSingle.getInstance());
}
}).start();
}
}
七、死锁以及解决方案
- 死锁发生在当多个线程进入到了循环等待状态;死锁是很难调试的错误,我们在编写多线程并含有同步方法调用的程序中更格外小心,避免死锁的发生。
package com.gaj.day04;
class ZhangSan {
public void say() {
System.out.println("张三:你给我书,我就给你画");
}
public void get() {
System.out.println("张三获得了书");
}
}
class LiSi {
public void say() {
System.out.println("李四:你给我画,我就给你书");
}
public void get() {
System.out.println("李四获得了画");
}
}
class SubThread01 implements Runnable {
private static ZhangSan zhangSan = new ZhangSan();
private static LiSi liSi = new LiSi();
boolean tag = false;
@Override
public void run() {
if(tag){ // 张三
synchronized (zhangSan){
zhangSan.say();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(liSi){
zhangSan.get();
}
}
}else{ // 李四
// 解决方案:让李四多等一会
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (liSi){
liSi.say();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(zhangSan){
liSi.get();
}
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
SubThread01 subThread1 = new SubThread01();
subThread1.tag = true;
SubThread01 subThread2 = new SubThread01();
subThread2.tag = false;
new Thread(subThread1).start();
new Thread(subThread2).start();
}
}
八、BlockingQueue
阻塞队列:生产和消费模式的缓冲区
用Queue作为缓冲区
package com.gaj.day04;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 生产消费者模式
* BlockingQueue 阻塞队列 指定队列大小
* @author Jan
*
*/
// 生产者
class Producer implements Runnable{
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue){
this.queue = queue;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
try {
Thread.sleep(100);
queue.put(i); // 阻塞方法:只要队列满了,那么会一致阻塞,直到队列有空位
// queue.add(i); // 非阻塞方法:添加失败会引发异常
System.out.println("生产了:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者
class Consumer implements Runnable{
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue){
this.queue = queue;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
try {
Thread.sleep(1000);
queue.take(); // 阻塞:队列为空会一直阻塞,直到队列添加进入元素
// queue.remove(); // 非阻塞:移除失败会引发异常
System.out.println("消费了:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3); // 指定队列大小为3
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
put()和take()方法,阻塞了会等待。
add()和remove()方法,阻塞了则抛出异常。