1. 什么是线程
线程,又称轻量级进程。进程中的一条执行路径,也是CPU的基本调度单位。一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,成为多线程。
1.2 进程和线程的区别
(1)进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
(2)一个程序运行后至少有一个进程。
(3)一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的。
(4)进程间不能共享数据段地址,但是同进程的线程之间可以。
1.3 获取和修改线程ID和线程名称:
获取线程ID和线程名称:
a. 在Thread的子类中调用this.getId()或this,getName()
b. 使用Thread,currentThread().getId()和Thread.currentThread.getName()
修改线程名称:
a. 调用线程对象的setName()方法
b. 使用线程子类的构造方法赋值
2. 线程的创建方式
(1)继承Thread类,重写run方法
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程---------------"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
//创建一个线程对象
MyThread m1 = new MyThread();
//开启线程 执行时会调用run方法
m1.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程**********"+i);
}
}
(2)实现Runnable接口
public class TicketSell implements Runnable{
private static int total = 100;
private Object object = new Object();
@Override
public void run() {
while (true){
synchronized (this.object){
if (total>0){
System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+total--+"张票");
}
}
}
}
public class Test03 {
public static void main(String[] args) {
//创建任务对象
TicketSell ticketSell = new TicketSell();
//将任务对象放入创建的线程中,并设置线程名
Thread t1 = new Thread(ticketSell,"售票窗口1");
Thread t2 = new Thread(ticketSell,"售票窗口2");
Thread t3 = new Thread(ticketSell,"售票窗口3");
Thread t4 = new Thread(ticketSell,"售票窗口4");
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
3. 常见方法
休眠: public static void sleep(long millis)
当前线程主动休眠millis毫秒
放弃: public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
加入: public final void join()
允许其他线程加入到当前线程中
优先级: 线程对象.setPriority()
线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高。
守护线程:线程对象.setDaemon(true);设置为守护线程。
线程有两类:用户线程(前台线程)和守护线程(后台线程)
如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。
垃圾回收线程属于守护线程。
4. 多线程安全问题
使用synchronized保证线程的安全性:
//同步代码块:
//对临街资源对象加锁
synchronized(临界资源对象){
//代码(原子操作)
}
synchronized也可以用在方法上(同步方法,只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中)
同步方法:
synchronized 返回值类型 方法名称(形参列表){
//代码(原子操作)
}
注意:每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的所标记。如调用
不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
5.线程死锁
当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。
例子: 情人节—两个情人去餐厅吃饭—必须具有两个筷子。
男方拥有一根筷子,女方拥有另一个筷子。
操作死锁得原因:1. 锁与锁之间有嵌套导致。
如何解决死锁:1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。
6.线程通信
线程通信中使用得方法有哪些?
例子: 存钱和取钱。
package com.hzk.demo6;
/**
* @author hzk
* @date 2022/7/20 16:27
*/
public class BankCard {
private double balance;//余额
//true表示有钱 false表示没钱
private boolean flag=false;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public synchronized void save(double money){
if (flag==true){
//进入等待队列
try {
this.wait();
System.out.println("nan");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存钱
this.balance=this.balance+money;
System.out.println(Thread.currentThread().getName()+"向卡中存入"+money+"卡中余额为"+this.balance);
flag=true;
//唤醒等待队列
this.notify();
}
public synchronized void take(double money){
if (flag==false){
//进入等待队列
try {
this.wait();
System.out.println("nv");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
this.balance=this.balance-money;
System.out.println(Thread.currentThread().getName()+"在卡中取走"+money+"卡中余额为"+this.balance);
flag=false;
//唤醒等待队列
this.notify();
}
}
sleep和wait得区别?
1.所在得类不同。sleep属于Thread类,wait属于Object类。
2.使用的地方: sleep可以使用再任何代码块。wait只能再同步代码块中。
3.是否释放锁资源: sleep不释放锁资源,wait会释放锁资源。
4.sleep时间到了自动唤醒,wait必须需要使用notify和notifyAll唤醒
notify和notyfyAll区别?
7.线程池
线程池的创建方式有哪些?
package com.hzk.demo7;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*Executor:线程池的根类。里面有一个方法。execute执行线程任务的方法Runnable类型的任务
* ExecutorService: 线程池的子接口
* shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
* shutdownNow(): 立即关闭线程池。
* isTerminated():判断线程池是否终止了。
* submit(): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。*/
/**
* int corePoolSize, 核心线程数
* int maximumPoolSize, 最大线程数
* long keepAliveTime, 空闲时间
* TimeUnit unit, 时间单位
* BlockingQueue<Runnable> workQueue: 堵塞队列,
*
* 根据你自己的业务以及服务器配置来设置。
*/
/**
* @author hzk
* @date 2022/7/20 18:21
*/
public class Test {
public static void main(String[] args) {
//单一线程池:适应场景:队列要求线程有序执行
/*ExecutorService executorService = Executors.newSingleThreadExecutor();*/
//创建固定长度的线程池对象
//ExecutorService executorService = Executors.newFixedThreadPool(3);
//创建可变长度的线程池
//ExecutorService executorService = Executors.newCachedThreadPool();
//创建延迟线程池对象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
/*for (int i = 0; i < 100; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==================");
}
});
}*/
for (int i = 0; i < 5; i++) {
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==================");
}
},10, TimeUnit.SECONDS);
}
executorService.shutdown();
}
}
8.使用最原始的方式创建线程池
上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。
public class Test02 {
public static void main(String[] args) {
/**
* int corePoolSize, 核心线程数
* int maximumPoolSize, 最大线程数
* long keepAliveTime, 空闲时间
* TimeUnit unit, 时间单位
* BlockingQueue<Runnable> workQueue: 堵塞队列,
*
* 根据你自己的业务以及服务器配置来设置。
*/
//LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。
BlockingQueue blockingQueue=new LinkedBlockingDeque(3);
ThreadPoolExecutor threadPoolExecutor=
new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);
for (int i = 0; i <5 ; i++) {
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~");
}
});
}
threadPoolExecutor.shutdown();
}
}
9.创建线程的第三种方式
实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出
package com.hzk.demo12;
import java.util.concurrent.*;
/**
* @author hzk
* @date 2022/7/21 9:41
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
My my = new My();
My1 my1 = new My1();
final ExecutorService executorService = Executors.newFixedThreadPool(2);
final Future<Integer> future = executorService.submit(my);
Future<Integer> future1 = executorService.submit(my1);
final Integer sum = future.get();//需要等线程执行完毕后,才会把结果返回给该变量、
final Integer sum1 = future1.get();
System.out.println(sum+sum1);
}
}
class My implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <=50; i++) {
sum+=i;
}
return sum;
}
}
class My1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 51; i <=100; i++) {
sum+=i;
}
return sum;
}
}
10. 手动锁
Lock它是手动锁的父接口,它下面有很多实现类。