多线程详解
文章目录
一、线程简介
进程与线程:
二、线程基础知识
普通方法调用和多线程:
1. 线程的创建
创建线程的三种方式:
- Thread.class:继承Thread类。
- Runable 接口:实现Runnable 接口。
- Callable 接口:实现Callable 接口。
继承 Thread.class 实现多线程(不建议使用,以为受单继承的局限性):
- 自定义线程类继承Thread类
- 重写 run() 方法,编写线程执行体
- 创建线程对象,调用start() 方法启动线程
注意:线程启动后并不一定立即执行,而由CPU 安排调度。
package demo01;
/**
*
1. 创建线程方式一:继承Thread类,重写 run() 方法,调用start 开启线程
2. 3. 总结: 注意,线程开启不一定立即执行,由CPU调度执行。
4. */
public class TestThread1 extends Thread{
@Override
public void run() {
// run 方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码---"+1);
}
}
public static void main(String[] args){
/**
*
* main线程,主线程
*
* 创建一个线程对象
*/
TestThread1 testThread1 = new TestThread1();
//调用start() 方法开启线程。
testThread1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程--");
}
}
}
测试发现他们是各自执行。
实现 Runnable 接口实现线程(推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用):
- 定义 MyRunnable 类实现 Runnable 接口
- 实现 run() 方法,编写线程执行体
- 创建线程对象,调用start() 方法启动线程
package demo01;
/**
*
* 实现 Runnable 接口,重写 run 方法,执行线程需要丢入 runnable 接口实现类,调用start方法。
*
*/
public class TestThread2 implements Runnable {
@Override
public void run() {
// run 方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码---"+1);
}
}
public static void main(String[] args){
/**
*
* 创建 runnable 接口的实现类对象
*
* 创建线程对象,通过线程对象来开启我们的线程,代理
*/
TestThread2 testThread2 = new TestThread2();
// Thread thread = new Thread(testThread2);
//
// thread.start();
// 简写
new Thread(testThread2).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程--");
}
}
}
通过Callable接口实现多线程
allable接口介绍:
- java.util.concurrent.Callable是一个泛型接口,只有一个call()方法
- all()方法抛出异常Exception异常,且返回一个指定的泛型类对象
Callable接口实现多线程的应用场景
- 当父线程想要获取子线程的运行结果时
使用Callable接口实现多线程的步骤
- 创建Callable子类的实例化对象
- 创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中
- 实例化Thread对象,并在构造方法中传入FurureTask对象
- 启动线程
利用Callable接口实现线程:
package demo01;
import java.util.concurrent.Callable;
public class TestThread4 implements Callable<String> {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
System.out.println("=====");
return "9999";
}
}
package demo01;
import java.util.concurrent.FutureTask;
public class TestThread5 {
public static void main(String[] args) {
FutureTask<String> ft = new FutureTask<>(new TestThread4());
new Thread(ft).start();
}
}
匿名类部类实现Callable接口创建子线程
package demo01;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestThread3 {
public static void main(String[] args) {
Callable<String> cl = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "正在行军~~~");
System.out.println(Thread.currentThread().getName() + "遭遇敌军~~~");
System.out.println(Thread.currentThread().getName() + "奋勇杀敌!!!!");
return "战斗胜利,俘虏敌军50000人";
}
};
FutureTask<String> ft = new FutureTask(cl);
new Thread(ft, "李存孝部队").start();
try {
Thread.currentThread().setName("李存勖部队");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "休整3000ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "整顿完毕,等待友军消息...");
try {
String str = ft.get();
System.out.println("李存勖部队得知友军消息为:" + str);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
2. 线程的状态
- 创建状态:Thread t = new Thread() 线程对象一旦创建就进入到了新生状态。
- 就绪状态:当调用start( ) 方法,线程立即进入就绪状态,但不意味着立即调度执行。
- 运行状态:到就绪状态的线程被cpu调度,则线程进入了运行状态,这时候线程才会执行线程体的代码快。
- 阻塞状态:当调用sleep,wait或同步锁时,线程进入阻塞状态,阻塞状态接触后,线程重新就如就绪状态,等待cpu调度执行。
- 死亡状态:线程中断或者结束,一旦进入死亡状态,就不能再次启动了。
3. 线程的方法
4. 线程的停止
不推荐使用JDK提供的 stop()、destroy()方法(已废弃)。
推荐线程自己停止下来,建议使用一个标志位进行终止变量。当 flag = false,则终止线程运行。
栗子:
package state;
/**
* 测试stop
* 1.建议线程正常停止-->利用次数,不建议死循环
* 2.建议使用标志位-->设置一个标志位
* 3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
*/
public class TestStop implements Runnable {
// 1. 设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run...Thread" + i++);
}
}
// 2. 设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop stop = new TestStop();
new Thread(stop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main..." + i);
if (i == 900) {
//调用stop()切换标志位,让线程终止
stop.stop();
System.out.println("该线程停止了");
}
}
}
}
5. 线程的休眠
- sleep (时间)指定当前线程阻塞的毫秒数;
- sleep 存在异常 InterruptedException;
- sleep 时间达到后线程进入就绪状态;
- sleep 可以模拟网络延时,倒计时等;
- 每一个对象都有一个锁,sleep 不会释放锁;
6. 线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞;
- 将线程从运行状态转为就绪状态;
- 让cpu重新调度,礼让不一定成功!kan CPU 的调度;
栗子:
package state;
/**
* 测试礼让线程
* 礼让不一定成功,看cpu心情
*/
public class TestYield {
public static void main(String[] args) {
MyYeild myYeild = new MyYeild();
new Thread(myYeild, "a").start();
new Thread(myYeild, "b").start();
}
}
class MyYeild implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
7. 线程强制执行
Join 合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞,可以想象成插队。
栗子:
package state;
/**
* 测试join
* 插队
*/
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程vip" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动我们的线程
TestJoin joinThread = new TestJoin();
Thread thread = new Thread(joinThread);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if (i == 200) {
thread.join();//插队
}
System.out.println("main" + i);
}
}
}
8. 线程的优先级
栗子:
package state;
public class ThreadPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority);
Thread thread2 = new Thread(myPriority);
Thread thread3 = new Thread(myPriority);
Thread thread4 = new Thread(myPriority);
Thread thread5 = new Thread(myPriority);
//先设置优先级,再启动
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(4);
thread3.start();
thread4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
thread4.start();
thread5.setPriority(8);
thread5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
9. 守护(Daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志,监控内存,垃圾回收
栗子:
package state;
public class DaemonThread {public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
//默认false表示是用户线程,正常的线程都是用户线程...
thread.setDaemon(true);
//上帝守护线程启动
thread.start();
//你 用户线程启动
new Thread(you).start();
}
}
/**
* 上帝
*/
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你");
}
}
}
/**
* 你
*/
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你一生都开心的活着");
}
System.out.println("====goodbye!world====");
}
}
三、线程同步
多个线程操作同一个资源:
线程同步:
不安全的线程案例:
买票:
package state;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "张三").start();
new Thread(buyTicket, "李四").start();
new Thread(buyTicket, "王五").start();
}
}
class BuyTicket implements Runnable {
//票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//买票
private void buy() {
//判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
//延迟
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
银行取钱:
package state;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "展堂");
Drawing girlfriend = new Drawing(account, 100, "sad");
you.start();
girlfriend.start();
}
}
//账户
class Account {
int money;//余额
String cardName;//卡名
public Account(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
//银行:模拟取款
class Drawing extends Thread {
Account account;//账户
int drawingMoney;//取金额
int nowMoney;//你手里的钱
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断是否有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足,不能进行取钱");
return;
}
try {
Thread.sleep(1000);//放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内金额 = 余额-你的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.cardName + "余额为:" + account.money);
//this.getName()==Thread.currentThread().getName()
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
线程不安全的集合:
ublic class Demo26_UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
同步方法
弊端:
同步块:
使用 synchronized 解决上述案例:
买票问题:
package state;
public class UnsafeBuyTicket2 {
public static void main(String[] args) {
BuyTicket1 buyTicket = new BuyTicket1();
new Thread(buyTicket, "张三").start();
new Thread(buyTicket, "李四").start();
new Thread(buyTicket, "王五").start();
}
}
class BuyTicket1 implements Runnable {
//票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法,锁的是this
private synchronized void buy() {
//判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
//延迟
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
银行问题:
package state;
public class UnsafeBank2 {
public static void main(String[] args) {
Account1 account = new Account1(100, "结婚基金");
Drawing1 you = new Drawing1(account, 50, "展堂");
Drawing1 girlfriend = new Drawing1(account, 100, "sad");
you.start();
girlfriend.start();
}
}
//账户
class Account1 {
int money;//余额
String cardName;//卡名
public Account1(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
//银行:模拟取款
class Drawing1 extends Thread {
Account1 account;//账户
int drawingMoney;//取金额
int nowMoney;//你手里的钱
public Drawing1(Account1 account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//锁的对象就是变量的量,需要增删改查的对象
synchronized (account) {
//判断是否有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足,不能进行取钱");
return;
}
try {
Thread.sleep(1000);//放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内金额 = 余额-你的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.cardName + "余额为:" + account.money);
//this.getName()==Thread.currentThread().getName()
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
}
集合问题:
//线程安全的集合 同步块
public class Demo29_SafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
四、死锁
如何避免死锁:
五、Lock 锁
synchronized 与 Lock 的区别:
栗子:
package state;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
int ticketNums = 10;
//定义Lock锁
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
reentrantLock.lock(); //加锁
if(ticketNums > 0 ){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "购买了第" + ticketNums-- + "张票");
} else {
break;
}
} finally {
//解锁
reentrantLock.unlock();
}
}
}
}
六、线程通信
应用场景:生产者和消费者的问题
Java 提供了几个方法解决线程之间的通信问题:
解决方式:
- 并发协作模式"生产者/消费者模式" ===>管程法
- 生产者: 负责生产数据的模块(可能是方法,对象,线程,数组)
- 消费者: 负责处理数据的模块(可能是方法,对象,线程,数组)
- 缓冲区: 消费者不能直接使用生产者的数据,他们之间有个缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//测试生产者消费者模型 --->管程法
//产品
public class Product {
private int id;
public Product(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
//消费者
public class ConsumerThread extends Thread {
private SyncContainer syncContainer;
public ConsumerThread(SyncContainer syncContainer) {
this.syncContainer = syncContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Product pop = syncContainer.pop();
System.out.println("消费了第"+ pop.getId() + "号产品");
}
}
}
//生产者
public class ProviderThread extends Thread {
//创建好的缓冲区
private SyncContainer syncContainer;
public ProviderThread(SyncContainer syncContainer) {
this.syncContainer = syncContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "只鸡!");
syncContainer.push(new Product(i));
}
}
}
//容器
public class SyncContainer {
//容器大小
Product[] products = new Product[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Product product){
//如果容器满了,就要等待消费者
//通知消费者消费,生产者等待
if(count == products.length) {
try {
this.wait();
} catch (InterruptedException e){
e.printStackTrace();
}
}
//如果容器没有满,就要生产产品
products[count] = product;
count++;
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Product pop(){
//判断容器是否为空
if(count == 0){
//通知生产者生产.消费者等待
try {
this.wait();
} catch (InterruptedException e){
e.printStackTrace();
}
}
//如果可以消费
count--;
Product product = products[count];
//通知生产者生产
this.notifyAll();
return product;
}
}
public class Main {
public static void main(String[] args) {
SyncContainer container = new SyncContainer();
new ProviderThread(container).start();
new ConsumerThread(container).start();
}
}
- 并发协作模式"生产者/消费者模式" ===>信号灯法
//产品-->节目
public class TV {
//演员表演,观众等待
//观众观看,演员等待
String voice; //表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice) {
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了" + voice);
//通知观众观看
this.voice = voice;
this.notifyAll();
this.flag = !flag;
}
//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了" + voice);
//通知演员表演
this.notifyAll();
this.flag = !flag;
}
}
public class Player extends Thread {
private TV tv = null;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.play("表演了" + i + "号节目");
}
}
}
public class Watcher extends Thread {
private TV tv = null;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.watch();
}
}
}
//测试生产者消费者问题:信号灯法,标志位解决
public class Main {
public static void main(String[] args) {
TV tv = new TV();
new Watcher(tv).start();
new Player(tv).start();
}
}
使用线程池: