【Java核心技术】Java线程技术详解

线程

在某个时间,CPU的某个核中只能处理一个进程,一个进程中只能处理一个线程

底层依赖于线程的轮换,才产生多个任务之间的切换

线程工作职责:

  • 和CPU进行交互
  • 和硬件进行交互

多线程的好处

  • 线程和硬件进行交互时,CPU处于空闲状态,为了提高CPU的利用率,引入多线程(理论上可以提高100%利用率)

进程和线程

  • 一个进程中可以有多个线程

  • 每个进程开辟一块空间,进程中的线程不用开辟空间

  • 线程的执行效率,速度比进程快

  1. 程序:静态的代码块
  2. 进程:具有独立的内存,动态的执行过程
    • CPU执行的任务分成的多个小任务
  3. 线程:在进程内,动态的执行过程。线程可以同时运行(相对意义上的)
    • 进程分成的多个小任务

实现线程的两种方式:

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

继承Thread类

  • 实现步骤:
    • 继承Thread类
    • 重写public void run() 方法
      • run方法也叫线程体方法,线程执行的代码放到run方法中
    • 启动线程:start方法,由系统分配时间片,由cpu自动调用run方法

Thread.sleep(1000):线程休眠

线程体方法------run()方法,线程的执行语句必须方法run方法中,run方法不用自己调用

class MyThread extends Thread{
    
    
	//线程体方法------run()方法,线程的执行语句必须方法run方法中,run方法不用自己调用
	@Override
	public void run() {
    
    
		for(int i = 1; i<=10;i++) {
    
    
			//进程休眠
			try {
    
    
				Thread.sleep(1000);
			} catch (InterruptedException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("MyThread");
		}
	}
	
}


class MyThread2 extends Thread{
    
    
	//线程体方法------run()方法
	@Override
	public void run() {
    
    
		for(int i = 1; i<=10;i++) {
    
    
			try {
    
    
				Thread.sleep(1000);
			} catch (InterruptedException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("MyThread2");
		}
	}
	
}

public class Demothread {
    
    

	public static void main(String[] args) {
    
    
		MyThread t1 = new MyThread();
		MyThread2 t2 = new MyThread2();
		t1.start();
		t2.start();
		
	}

}

使用匿名类实现

方法一:

/**
 * 使用匿名类继承Thread创建Thread对象
 */
new Thread() {
    
    
    @Override
    public void run() {
    
    
        for(int i = 0;i<=10;i++) {
    
    
            //currentThread():获取正在执行的线程对象
            //getName() : 获取线程的名称
            System.out.println(Thread.currentThread().getName());
        }
    }
}.start();

方法二:

setName(String str):给线程起名字

Thread t1 = new Thread() {
    
    
	@Override
	public void run() {
    
    
		for(int i = 0;i<=10;i++) {
    
    
			//currentThread():获取正在执行的线程对象
			//getName() : 获取线程的名称
			System.out.println(Thread.currentThread().getName());
		}
	}
};
t1.setName("线程A");
t1.start();

实现Runnable接口

  • 实现步骤

    • 实现Runnable接口
  • 重写run()方法

    • 创建Thread进程对象,把实现Runnable类的对象作为Thread构造方法的参数传进去
  • 最常用

MyRunnable r1 = new MyRunnable();
Thread t2 = new Thread(r1);
//Thread类和MyRunnable类同级类,这样的设计模式为:装饰者模式
/**
 * 实现Runnable接口
 * 	实现Runnable接口的类,不是线程类
 * 	实现线程功能,Thread(Runnable r),把是现实Runnable接口的类传进去,相当于重写Thread的run方法
 * @author dell
 *
 */
class MyRunnable implements Runnable{
    
    
	//线程体方法------run()方法
	@Override
	public void run() {
    
    
		for(int i = 1; i<=10;i++) {
    
    
			try {
    
    
				Thread.sleep(1000);
			} catch (InterruptedException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("MyRunnable");
		}
	}
}

public class Demothread {
    
    

	public static void main(String[] args) {
    
    
		MyThread t1 = new MyThread();
		MyRunnable r1 = new MyRunnable();
		Thread t2 = new Thread(r1);
		t1.start();
		t2.start();
		
	}

}

使用匿名类实现

方法一

currentThread():获取正在执行的线程对象

getName() : 获取线程的名称

new Thread(new Runnable() {
    
    

    @Override
    public void run() {
    
    
        for(int i = 0;i<=10;i++) {
    
    
            System.out.println(Thread.currentThread().getName());
        }

    }

}).start();

方法二

Thread (Runnable target,String name)------第二个参数给线程起名字

Thread t2 = new Thread(new Runnable() {
    
    

    @Override
    public void run() {
    
    
        for(int i = 0;i<=10;i++) {
    
    
            System.out.println(Thread.currentThread().getName());
        }

    }

},"线程B");
t2.start();

实现Callable接口*(了解)*

步骤:

  • 实现Callable接口
  • 重写call()方法
  • 在call()方法中写任务,有返回值

开启线程

  • 通过线程池返回执行服务器
    • ExecutorService e = Executors.newCachedThreadPool();
  • 将线程类添加到执行服务器,返回Future
    • Future<Integer> submit = e.submit(new CDemo());
  • 通过Future类对象调用get()方法,获取线程返回值
    • submit.get()
package cn.tedu.callable;

import java.util.concurrent.*;

public class CallableDemo {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //开启线程

        //通过线程池返回执行服务器
        ExecutorService e = Executors.newCachedThreadPool();

        Future<Integer> submit = e.submit(new CDemo());
        System.out.println(submit.get());//22
    }
}

//线程类
class CDemo implements Callable<Integer> {
    
    

    @Override
    public Integer call() throws Exception {
    
    
        return 22;
    }
}

常用的线程方法

currentThread():获取正在执行的线程对象

getName() : 获取线程的名称

join():线程执行顺序

join()

调用join()方法的线程开始执行,直到执行完毕,当前线程继续执行

package thread;

public class JoinThread {
    
    

    public static void main(String[] args) {
    
    
        Thread t1 = new Thread() {
    
    
            @Override
            public void run() {
    
    
                for(int i = 0;i<10;i++) {
    
    
                    System.out.println("t1");
                }
            }
        };

        Thread t2 = new Thread(new Runnable() {
    
    

            @Override
            public void run() {
    
    
                for(int i = 0;i<10;i++) {
    
    
                    if(i4) {
    
    
                        try {
    
    
                            t1.join();//-----------
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                    System.out.println("t2");
                }

            }
        }); 
        t2.start();
        t1.start();
    }

}

结果
t2 t2 t2 t2 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t2 t2 t2 t2 t2 t2
t1.join(),t1调用的join,所以t1线程执行,执行完毕后,再继续执行t2

线程的分类

  1. 前台线程、后台线程
  2. 两种线程的创建方式完全相同
  3. 新创建的线程默认都是前台线程
  4. 设置当前线程为后台线程的方法:setDaemon(true)
  5. 两种线程完成的功能没有区别
  6. 两种线程不同的地方:
    • 前台所有线程执行完毕,不管后台线程是否执行完毕,都被强制停止

前台线程

//前台线程
Thread rose = new Thread() {
    
    
    @Override
    public void run() {
    
    
        for(int i = 0;i<=3;i++) {
    
    
            System.out.println("jack,help me");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        System.out.println("扑通");
    }
};

后台线程

//后台线程
Thread jack = new Thread() {
    
    
    @Override
    public void run() {
    
    
        System.out.println("You jump,I jump!");
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
};

//设置后台进程
jack.setDaemon(true);


启动线程

rose.start();
jack.start();

线程的生命周期

  1. 新建状态:创建线程new Thread();

    • 可以直接进入就绪状态或者死亡状态
  2. 就绪状态:start();

  3. 阻塞状态:可能是网络慢、sleep()、wait()方法等,这种状态结束了,重新到就绪状态

  4. 运行状态:Cpu执行run()方法的代码

  5. 死亡状态:

    • 执行完run()方法------正常进入
    • yield()方法------强制当前进程终止

图解

、

线程的优先级

  • 3个等级:低(1),中(5),高(10),系统会自动设置优先级(5,中等优先级);理论上,级别越大可以抢占的资源概率越大
  • 如果线程之间的优先级之差大于5,理论上可以抢占的资源概率就会稍微大一点

设置优先级:

  • setPriority(Thread.MIN_PRIORITY)------低优先级
  • setPriority(Thread.MAX_PRIORITY)------高优先级
  • setPriority(Thread.NORM_PRIORITY)------中等优先级
  • 内存足够的时候,设置优先级没什么作用,只有在内存不足的时候,才会高优先级优先执行

获取优先级:

  • getPriority()
类型 等级
public static final int MAX_PRIORITY(高优先级) 10
public static final int MIN_PRIORITY(低优先级) 1
public static final int NORM_PRIORITY(中等优先级) 5

守护线程

setDaemon(true):true,设置为守护线程

特点:

  • 被守护线程结束,其他的守护线程随之结束
  • 线程要么是守护线程,要么是被守护线程
  • 被守护线程可以有多个,只有等到所有的被守护线程结束,其他的守护线程才结束
package cn.tedu.thread;

public class DeamonDemo {
    
    
    public static void main(String[] args) {
    
    

        //创建线程对象
        Thread t1 = new Thread(new Soilder(), "小兵甲");
        Thread t2 = new Thread(new Soilder(), "小兵乙");
     
        //设置成守护线程
        t1.setDaemon(true);
        t2.setDaemon(true);
        t3.setDaemon(true);
        t4.setDaemon(true);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        //Boss---主线程:被守护线程
        for (int i = 20; i >= 0 ; i--) {
    
    
            System.out.println("Boss还剩"+i+"滴血");
        }
    }
}

class Soilder implements Runnable{
    
    
    @Override
    public void run() {
    
    
        //小兵掉血
        for (int i = 10; i >= 0 ; i--) {
    
    
            System.out.println(Thread.currentThread().getName() + "还剩" + i + "滴血");
        }
    }
}


线程的调度

  • 不同优先级
    • 高先执行,低后执行
  • 同优先级
    • 先到先执行

线程同步

问题:

  • 多线程之间存在相互抢占(CPU的执行权),发生在代码的每一行
  • 产生了错误数据问题(重复,跳过,负数),导致了多线程数据安全问题

解决线程抢占问题:

  • 当一个线程执行时,线程锁会把该线程锁住,直到执行完毕,解锁

  • 实现步骤:

    • 在操作共享变量的方法上添加关键字:synchronized,只要添加关键字synchronized的方法,一个线程访问,其他线程不能访问;直到当前线程释放锁,其他线程获取锁,才可以访问共享变量
  • 一个栈对应一个线程

同步代码锁

synchronized(锁对象){......}

锁对象:指定线程对象共享的范围(指定方法区资源默认共享所有线程对象)

  • this.getClass()

  • Math.class

  • String.class

  • 该类.class

  • this:特殊。使用this时,只能有一个this代表的对象,在多线程中,只能锁住一个实现了Runnable接口的对象。

package cn.tedu.test;

import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class Demo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //创建properties对象
        Properties p = new Properties();
        p.load(new FileReader("ticket.properties"));
        String count = p.getProperty("count");//动态获取每次给定的票数
        Ticket t = new Ticket(Integer.parseInt(count));//创建总票数
        //售票员
        Seller s1 = new Seller(t);
        Seller s2 = new Seller(t);
        
        //线程
        Thread t1 = new Thread(s1, "A");
        Thread t2 = new Thread(s2, "B");
        
        //对象锁为:this的情况下
        //Seller s1 = new Seller(t);
        //Thread t1 = new Thread(s1, "A");
        //Thread t2 = new Thread(s1, "B");
        //Thread t3 = new Thread(s1, "C");
        //Thread t4 = new Thread(s1, "D");
        
        //启动线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Seller implements Runnable {
    
    
    Ticket ticket;
    public Seller(Ticket ticket) {
    
    this.ticket = ticket;}

    @Override
    public void run() {
    
    
        while (true) {
    
    
            synchronized (Seller.class) {
    
    //同步代码锁---代码区域不会出现抢占
                //Seller.class Math.class String.class  都在方法区,方法区资源被所有线程共享
                //判断出循环的条件
                if(ticket.getNum()<=0) break;
                ticket.setNum(ticket.getNum() - 1);
                System.out.println(Thread.currentThread().getName() + "卖出一张,剩余:" + ticket.getNum());
            }
        }
    }
}
class Ticket {
    
    
    private int num;
    public Ticket() {
    
    }
    public Ticket(int num) {
    
    this.num = num;}
    public int getNum() {
    
    return num;}
    public void setNum(int num) {
    
    this.num = num;}
}


同步方法锁

  • 如果加在非静态的方法上,锁对象就是:this
  • 如果加在静态方法上,锁对象就是:当前类.class
@Override
public synchronized void run() {
    
    
    while (true) {
    
    
        ticket.setNum(ticket.getNum() - 1);
        System.out.println(Thread.currentThread().getName() + "卖出一张,剩余:" + ticket.getNum());
    }
}

同步和异步

同步:在某个时刻只能有一个资源被使用

异步:在某个时刻可以被多个线程抢占同一资源

同步的一定是安全的,不安全的一定是异步

线程的死锁问题

原因:由于锁的嵌套,产生死锁问题

解决:

  • 逻辑上尽量避免锁的嵌套;
  • 设置线程的优先级,指定某个线程先执行
synchronized (printer) {
    
    //--------
    printer.print();
    try {
    
    
        Thread.sleep(50);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    synchronized (scann){
    
    //-------------
        scann.scan();
    }
}


synchronized (scann) {
    
    //-----------
    scann.scan();
    try {
    
    
        Thread.sleep(50);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    synchronized (printer){
    
    //-----------
        printer.print();

    }
}

线程池

作用:

  • 控制线程数量(避免因为大量的线程导致的系统崩溃)
  • 重用线程(避免频繁创建销毁线程)

概念:

​ 首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。

​ 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,

​ 一个线程只能执行一个任务,但我们可以同时向一个线程池提交多个任务

1

提高效率原理:

​ 现有一个集合(数组),在集合中先把线程对象放进去,如果需要线程完成工作,有集合调度线程,完成任务之后,把线程放回到线程池(集合),线程可以重复使用

实现类:ExecutorService

  • 线程池对象的创建:

ExecutorService threadPool = Executors.newFixedThreadPool(3);//初始化3个线程对象

ExecutorService threadPool = Executors.newFixedThreadPool(3);//初始化3个线程对象
for(int i = 0;i<3;i++){
     
     
 Thread t = new Thread(new Runnable(){
     
     
     @Override
     public void run() {
     
     
         System.out.println(Thread.currentThread().getName());
     }
 });
 threadPool.execute(t);//将任务提交给线程池
}

  • 将任务提交给线程池

threadPool.execute(t);//放到线程池中

  • 线程池的关闭:

    • shutdown : 只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断------通常会用这一个
    • shutdownNow : 将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    
    

	public static void main(String[] args) {
    
    
        
		ExecutorService threadPool = Executors.newFixedThreadPool(3);//初始化3个线程对象
        
		for(int i = 0;i<3;i++){
    
    
			Thread t = new Thread(new Runnable(){
    
    
				@Override
				public void run() {
    
    
					System.out.println(Thread.currentThread().getName());
				}
			});
            
			threadPool.execute(t);//将任务提交给线程池
		}
        
		//threadPool.shutdown();
		threadPool.shutdownNow();
	}
}

生产与消费模型

  • 每次生产的商品数量都要求是一个随机整数,保证每次剩余商品数量不能超过1000
  • 每次消费的商品数量也是一个随机整数,保证每次剩余商品数量不能小于0
  • 让生产和消费交替出现

代码

通过标志位flag,控制生产还是消费

  • 如果为true,执行
  • 如果为false,挂起
package cn.tedu.notify;
public class NotifyWaitDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建商品类的对象
        Product p = new Product();
        //创建线程对象
        new Thread(new Pruductor(p)).start();//生产
        new Thread(new Pruductor(p)).start();//生产
        new Thread(new Pruductor(p)).start();//生产

        new Thread(new Consumer(p)).start();//消费
        new Thread(new Consumer(p)).start();//消费
        new Thread(new Consumer(p)).start();//消费

    }

}
//代表线程生产的过程
class Pruductor implements Runnable {
    
    
    Product p;
    public Pruductor(Product p) {
    
    
        this.p = p;
    }
    @Override
    public void run() {
    
    
        while (true) {
    
    //保证一直生产
            synchronized (p) {
    
    
                while (!p.flag) {
    
    //false:挂起;true:执行
                    //让线程等待
                    try {
    
    
                        Thread.sleep(1000);
                        p.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                int max = 1000 - p.getNum();//最大生产商品的数量
                int count = (int) (Math.random() * (max + 1));
                p.setNum(count+p.getNum());//生产随机数量
                System.out.println("生产:" + count + "个商品,剩余:" + p.getNum() + "个");
                p.flag = false;
                //唤醒线程
                p.notifyAll();

            }
        }

    }
}

//表示线程消费过程
class Consumer implements Runnable{
    
    
    Product p;
    public Consumer(Product p) {
    
    
        this.p = p;
    }
    @Override
    public void run() {
    
    
        while (true) {
    
    //保证一直消费
            synchronized (p){
    
    
                while (p.flag){
    
    //false:挂起;true:执行
                    try {
    
    
                        Thread.sleep(1000);
                        p.wait();//让线程等待
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                //随机消费的商品数量
                int count = (int) (Math.random() * (p.getNum() + 1));
                //设置剩余商品数量
                p.setNum(p.getNum()-count);
                System.out.println("消费:" + count + "个商品,剩余:" + p.getNum() + "个");
                p.flag = true;
                //唤醒线程
                p.notifyAll();

            }
        }
    }
}

//代表商品类
class Product {
    
    
    private int num;

    boolean flag = true;//标志位---控制哪个线程执行

    public int getNum() {
    
    
        return num;
    }

    public void setNum(int num) {
    
    
        this.num = num;
    }
}

等待唤醒机制

  • 根据wait()方法、notify()方法、标志位可以去控制线程对象之间的执行轨迹
  • 结合锁才能使用

wait()和sleep()区别

  • sleep()方法:一定要指定休眠的时间,到点自然醒;
    • 如果使用时出现锁,休眠就会释放线程的CPU的执行权,但是不会释放锁对象
    • 如果没有锁,会释放CPU的执行权
    • 定义在Thread类里的静态方法
  • wait()方法:可以指定等待时间,也可以不指定
    • 指定时间:到点自然醒
    • 没有指定时间:必须需要notify()notiftAll()手动唤醒
    • 如果使用时出现锁,等待时即会释放CPU执行权,也会释放锁对象
    • 不使用锁,会释放CPU执行权
    • 定义在Object类里的方法

猜你喜欢

转载自blog.csdn.net/weixin_54707168/article/details/114126176
今日推荐