文章目录
前言
随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。
一、线程简介
多任务:边吃饭边玩手机,同时做多件事多线程:多车道,多条线路同时执行任务
二、线程实现
1.继承Thread类
不建议使用:为了避免OOP单继承局限性
Thread类本身实现了Runnable接口
实现步骤:
① 自定义线程类继承Thread类
② 重写run方法,编写程序执行体
③ 创建线程对象,调用start()方法启动线程
代码如下(示例):
/**
* 描述:继承Thread类创建线程
*/
public class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("子线程: " + i);
}
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
// thread1.run();//先执行run方法,再执行接下来的代码(没起到多线程的作用)
thread1.start();//主线程和子线程交替执行,且不可控,每次执行结果都不一样
for (int i = 0; i < 20; i++) {
System.out.println("主线程: " + i);
}
}
}
总结: 线程开启不一定立即执行,由CPU调度执行,直接调用run方法相当于调用普通方法,不会创建新的线程。
2.实现Runnable接口
Java 是单继承,推荐使用Runnable接口,方便同一个独享被多个线程使用
避免了单继承的局限性:即在Java中一个类只能使用extends继承一个父类.,如果继承多个父类,而父类有同名方法时就不知道调用哪一个方法了,另外会是两个类的耦合性增加,如果父类有改动时会直接影响子类。
实现步骤:
① 定义MyRunnable类实现Runnabke接口
② 实现Run()方法,编写程序执行体
③ 创建线程对象,调用start()方法启动线程
代码如下(示例):
/**
* 描述:实现Runnable接口
*/
public class Thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("子线程: " + i);
}
}
public static void main(String[] args) {
//创建Runnable接口实现类的对象
Thread2 thread2 = new Thread2();
//创建线程类对象,通过线程对象开启我们的线程(代理)
new Thread(thread2).start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程: " + i);
}
}
}
3.实现Callable接口
了解即可
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果: boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
三、线程状态
1.五种状态
创建、就绪、运行、阻塞、死亡
2.线程停止(标志位)
不推荐使用jdk提供的stop(),destory()方法,建议使用一个标志位进行终止变量,当 flag = false,则线程终止运行。
死亡之后的线程不能再次启动
代码如下(示例):
/**
* 描述:停止线程
*/
public class ThreadStop implements Runnable {
//1.线程中定义线程体使用的标识
private boolean flag = true;
@Override
public void run() {
int i = 0;
//2.线程体使用该标识
while (flag){
System.out.println("run.....Thread"+(i++));
}
}
//3.对外提供方法改变标识
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程: "+i);
if ( i == 900){ // 900时就停止子线程
threadStop.stop();
System.out.println("run线程停止了!");
}
}
}
}
3.线程休眠(sleep)
- sleep(时间) 指定当前线程阻塞的毫秒数
- sleep时间达到后线程进入就绪状态
- 每一个对象都有一个锁,sleep不会释放锁
- sleep可以模拟网络延时(放大问题的发生性,比如多线程卖票,一票多卖),倒计时等
代码如下(示例):
/**
* 描述:线程休眠
*/
public class ThreadSleep implements Runnable {
@Override
public void run() {
}
//模拟打印当前时间
public static void main(String[] args) throws InterruptedException {
testDown();
//当前系统时间
Date date = new Date(System.currentTimeMillis());
while (true){
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date)); // 格式化输出
date = new Date(System.currentTimeMillis()); // 更新当前系统时间
Thread.sleep(1000);
}
}
//模拟倒计时
public static void testDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000); // 1秒,毫秒单位
System.out.println(num--);
if (num <= 0){
break;
}
}
}
}
4.线程礼让(yield)
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功
代码如下(示例):
/**
* 描述:线程礼让
*/
public class ThreadYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程结束执行");
}
public static void main(String[] args) {
ThreadYield threadYield = new ThreadYield();
new Thread(threadYield,"a").start(); // 线程a
new Thread(threadYield,"b").start(); // 线程b
}
}
总结: 即使不进行礼让,也会出现礼让成功,因为是多线程,cpu调度来决定。
5.线程强制执行(join)
join合并线程,待此线程执行完毕之后,在执行其他线程,其他线程阻塞,可以想象成插队。
注意:join是通过 new Thread 对象来调用的,而sleep和yield是通过 Thread 直接调用。
代码如下(示例):
/**
* 描述:线程强制执行
*/
public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("join线程:" + i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin threadJoin = new ThreadJoin();
Thread thread= new Thread( threadJoin);
thread.start();
for (int i = 0; i < 50; i++) {
if( i == 25 ) {
thread.join(); // 插队,让join线程先执行完后到主线程
}
System.out.println("主线程"+i);
}
}
}
四、线程优先级
线程优先级高不一定优先执行,大多数时候,线程优先级高的线程会优先执行,先设置优先级,再start线程!
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10。
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
代码如下(示例):
/**
* 描述:线程优先级
*/
public class ThreadPriority {
public static void main(String[] args) {
//主线程设置默认优先级
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority,"t1");
Thread t2 = new Thread(myPriority,"t2");
Thread t3 = new Thread(myPriority,"t3");
Thread t4 = new Thread(myPriority,"t4");
Thread t5 = new Thread(myPriority,"t5");
//先设置线程优先级,再启动
t1.setPriority(1);
t1.start();
t2.setPriority(3);
t2.start();
t3.setPriority(6);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY); // 优先级=10
t4.start();
t5.setPriority(Thread.MIN_PRIORITY); // 优先级=1
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---线程被执行了!---" + Thread.currentThread().getPriority());
}
}
五、守护线程(daemon)
线程分为用户线程和守护线程
守护线程在用户线程执行完毕之后也会执行完毕,不过JVM需要一点时间
应用场景:后台记录操作日志,监控内存,垃圾回收等
首先:守护线程先执行,然后用户线程执行与守护线程一起执行,用户线程执行完毕后,守护线程执行完毕。
代码如下(示例):
/**
* 描述:守护线程
*/
public class ThreadDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); // 默认为false用户线程,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!Beautiful World!!!------");
}
}
六、线程同步
解决多个线程操作同一个对象的安全问题。
线程同步其实就是一种等待机制。多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
线程同步形成条件:队列+锁
锁的对象就是多个线程共享的对象,锁谁取决于共同操作的对象是谁(且这个对象是要改变的)
只读代码是不需要加锁的,只有需要进行修改的代码才需要加锁(所以产生了同步方法和同步代码块)
- 同步方法: public synchronized void method(int args)f
- 同步块: synchronized(Obj) {} 注意:监视的对象是需要增删改查的对象
线程同步案例
1.线程安全买票
如果没有同步,剩一张票的时候,3个人都可以买到,会出现负数,线程不安全。
代码如下(示例):
/**
* 描述:线程安全买票
*/
public class BuyTicket implements Runnable {
// 标志位
private boolean flag = true;
// 票数
private int ticketNums = 10;
@Override
public void run() {
while (true){
buy();
}
}
// 买票
// synchronized同步方法,锁的是this
private synchronized void buy(){
if (ticketNums <= 0){
flag = false;
return;
}
// 模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName() + "拿到"+ ticketNums--);
}
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"a").start();
new Thread(buyTicket,"b").start();
new Thread(buyTicket,"c").start();
}
}
2.银行取钱
因为synchronized默认所得独享是this,那么这里就是锁的银行,但是我们操作是对account进行操作的,银行是没有变的,所以我们需要synchronized同步块,锁account。
代码如下(示例):
/**
* 描述:银行取钱
*/
public class BankDraw {
public static void main(String[] args) {
//取钱首先得有账户
Account account = new Account(100,"结婚基金");
DrawMoney you = new DrawMoney(account,50,"张三");
DrawMoney girl = new DrawMoney(account,100,"李四");
you.start();
girl.start();
}
}
class Account{
int money; // 账户余额
String name; // 卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class DrawMoney extends Thread{
Account account; // 账户
int drawingMoney; // 取多少钱
int nowMoney; // 现在有多少钱
public DrawMoney(Account account, int drawingMoney, String name) {
super(name); // 调用父类的方法
this.account = account;
this.drawingMoney = drawingMoney;
// this.nowMoney = nowMoney;
}
@Override
public void run() {
synchronized (account){ // 锁账户
// 判断账户有没有钱
if (account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
// 模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额
account.money = account.money - drawingMoney;
// 现在手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
// this就是调用当前方法的对象,Drawing继承了Thread,所以this也是一个线程对象,可以调用Thread的getName方法来获取线程的名字
// Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
}
3.线程安全集合
如果没有锁住集合两个线程同一瞬间操作了同一个位置,导致覆盖,元素变少。
代码如下(示例):
/**
* 描述:线程集合
*/
public class Gather {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() ->{ // lamda 表达式
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
// 让主线程休眠一会再打印,电脑运行速度太快了。线程里add还没执行完就执行主线程的打印语句。会导致打印结果偏小。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
4.JUCC集合
JUCC是JAVA提供的线程安全集合。
代码如下(示例):
/**
* 描述:线程安全的集合
*/
public class SafeGather {
public static void main(String[] args) {
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
七、死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
应用场景: 相当于两台并行前进的车,左边的车想变道右边,右边的车想变道左边,谁也不让谁。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
代码如下(示例):
/**
* 描述:死锁
*/
public class DeadLock {
public static String obj1 = "A对象";
public static String obj2 = "B对象";
public static void main(String[] args) {
Thread a = new Thread(new Thread1());
Thread b = new Thread(new Thread2());
a.start();
b.start();
}
}
class Thread1 implements Runnable{
@Override
public void run() {
try {
System.out.println("线程1 Running");
while (true){
synchronized (DeadLock.obj1){
System.out.println("线程1 锁定 A对象");
Thread.sleep(3000); // 获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
synchronized (DeadLock.obj2){
System.out.println("线程1 锁定 B对象");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
try {
System.out.println("线程2 Running");
while (true){
synchronized (DeadLock.obj2){
System.out.println("线程2 锁定 B对象");
Thread.sleep(3000); // 获取obj2后先等一会儿,让Lock1有足够的时间锁住obj1
synchronized (DeadLock.obj1){
System.out.println("线程2 锁定 A对象");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
简单来说:
- 线程1和线程2的执行逻辑中都需要锁定对象A和对象B。
- 线程1在执行中先锁定了A对象。
- 线程2在执行中锁定了B对象。
- 线程1需要锁定B对象时发现B对象已经被其他线程锁住,所以线程1需要等待B对象锁释放后继续执行。
- 线程2需要锁定A对象时发现A对象已经被其他线程锁住,所以线程2需要等待A对象锁释放后继续执行。
总结: 线程1和线程2都在相互等待,它们都将无法推进下去。
八、Lock锁
ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 与 Synchronized 对比:
- Lock是显式锁(手动开启和关闭,别忘记关闭锁)Synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,Synchronized有代码块锁和方法锁
代码如下(示例):
/**
* 描述:ReentrantLock可重入锁
*/
public class TestLock implements Runnable {
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
// 上锁
lock.lock();
if (ticketNums > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"获得了第"+(ticketNums--)+"票");
}else {
break;
}
} finally {
// 释放解锁
lock.unlock();
}
}
}
public static void main(String[] args) {
TestLock testLock = new TestLock();
new Thread(testLock,"a").start();
new Thread(testLock,"b").start();
new Thread(testLock,"c").start();
}
}
九、线程通信
方法:管程法 和 信号灯法
十、线程池
背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处: 提高响应速度(减少了创建新线程的时间)、降低资源消耗、便于线程管理
- ExecutorService:真正的线程池接口
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
代码如下(示例):
/**
* 描述:线程池
*/
public class ThreadPool {
public static void main(String[] args) {
//1、创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10); //newFixedThreadPool 参数为:线程池大小
//2、执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//3、关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"天天向上");
}
}
总结
好了Java多线程基础先到这里了,记得点个赞哦!