1.什么是进程
正在运行的程序,是系统的进行资源分配的基本单位,目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分,单核cpu在同一时刻,只能有一个进程,宏观并行,微观串行
2.什么是线程
线程,又称轻量级进程,进程中的一条执行路径,也是cpu的基本调度单位。一个进程由一个或多个线程组成,彼此间完成不同的工作,同时进行,成为多线程
例子:java虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并行执行
3.进程和线程的区别
1.进程是操作系统资源分配的基本单位,而线程是cpu的基本调度单位---进程运行调用cpu里面的线程
2.一个程序运行后至少有一个线程
3.一个进程可以包换多个线程,但是至少需要有一个线程,否则这个进程是没有意义
4.进程间不能共享数据段地址,但是同进程的线程之间可以
4.线程的特点
4.1.线程抢占式执行
效率高,可防止单一线长时间独占cpu
4.2.在单核cpu中,宏观上同时执行,微观上顺序执行
5.java如何创建线程
5.1.继承Thread类
public class Mythred extends Thread {
//重写run方法
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("haha"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建线程对象
Mythred m1=new Mythred();
//开启线程
m1.start();
for (int i = 0; i < 20; i--) {
System.out.println("主线程"+i);
}
}
}
获取线程的名字
//1.通过Thread对象中getName();--只能在Thread子类中获取
System.out.println(this.getName()+"haha"+i);
//2.在Thread类中存在一个静态方法获取当前线程对象,在通过该对象调用的getName()
System.out.println(Thread.currentThread().getName()+i);
设置线程的名字
//设置线程的名称
m1.setName("哈哈");
5.2.实现Runnable接口
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-16 09:50:56
* 实现接口
*/
public class MyRunnable implements Runnable {
private int ticket = 100;//定义总票数为100张
//重写方法
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket < 0) {
break;
}
try {
Thread.sleep(100);//模拟售票需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
ticket--;
}
}
}
}
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-16 09:56:34
*/
public class MyRunnableTest {
public static void main(String[] args) {
//创建线对象
MyRunnable m1=new MyRunnable();
Thread t1=new Thread(m1,"m1");
Thread t2=new Thread(m1,"m2");
Thread t3=new Thread(m1,"m3");
Thread t4=new Thread(m1,"m4");
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
5.3.实现Callable接口
实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出。
public class Test2 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//1-100的和
int sum=0;
for (int i =1; i <=100; i++) {
sum+=i;
}
return sum;
}
}
package guan.demo4;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-19 10:30:26
*/
public class Test {
public static void main(String[] args) throws Exception{
//创建对象
Test2 test2=new Test2();
//创建定长的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务给线程池中线程对象
Future<Integer> future = executorService.submit(test2);
Integer sum = future.get();//--需要等线程执行完毕后,才会把结果输出给该变量
//main中的方法
System.out.println(sum);
//关闭线程池--执行玩任务后关闭
executorService.shutdown();
}
}
6.线程中常见的方法
6.1.休眠
package guan;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-17 20:37:56
*/
public class Demo1 {
//sleep 当前主线程休眠多少毫秒--静态方法
public static void main(String[] args) {
Sleep sleep=new Sleep();
Thread t=new Thread(sleep);
//开启线程
t.start();
}
static class Sleep implements Runnable{
@Override
public void run() {
try {
Thread.sleep(3000);//当前cpu主动休眠多少毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("休息=============="+i);
}
}
}
}
6.2.放弃
package guan;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-17 21:17:39
*/
public class Demo2 {
//yield--当前线程主动放弃时间片,回叙到就绪状态,竞争下一次时间片--静态方法---y1 y2的交替变得多了
public static void main(String[] args) {
Yield y1=new Yield();
Thread t1=new Thread(y1,"hello");
t1.start();
Thread t2=new Thread(y1,"world");
t2.start();
}
static class Yield implements Runnable{
@Override
public void run() {
for (int i = 0; i < 30; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
6.3.加入
package guan;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-17 21:36:40
*/
public class Demo3 {
//join,允许其他线程添加到当前线程中,直到其他线程执行完成后,才执行--在start()之后添加
public static void main(String[] args) throws InterruptedException {
Join join=new Join();
Thread t1=new Thread(join,"你好");
t1.start();
t1.join();
JoinTest joinTest=new JoinTest();
Thread t2=new Thread(join,"吃饭吗");
t2.start();
}
static class Join implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
static class JoinTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
6.4.守护线程
线程与两类:用户线程(前台线程)和守护线程(后台线程)
如果线程中所有的前台进程都执行完毕了,后台进程也会自动结束,垃圾回收线程属于守护线程
package guan;
import com.sun.org.apache.xml.internal.security.signature.reference.ReferenceNodeSetData;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-17 21:57:06
*/
public class Demo4 {
//Daemon守护线程
public static void main(String[] args) {
Daemon daemon=new Daemon();
Thread t1=new Thread(daemon,"睡觉");
//设置t1为守护线程。当前台进程执行完毕,后台也会执行完毕
//设置谁为守护进程,谁就是后台进程
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程"+i);
}
}
static class Daemon implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":副线程"+i);
}
}
}
}
7.线程的状态
8.线程安全问题
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省
解决方法:加锁
synchronized (临界资源对象) {
}
//this代表当前对象
synchronized (this) {
}
注意:括号里面的必须是对象
9.线程死锁
9.1.什么是线程死锁
当A线程拥有A锁的资源,这个时候A锁需要B锁的资源,B线程拥有B锁的资源,这个时候B锁需要A锁的资源,这样会导致线程A等待B线程释放资源B,B线程等待A线程释放A资源,使A线程和B线程处于永久的等待,从而造成死锁
//对象
public class Lock {
public static Object a=new Object();
public static Object b=new Object();
}
//a对象
public class Boy extends Thread{
@Override
public void run() {
synchronized (Lock.a){
System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
synchronized (Lock.b){
System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
System.out.println("可以吃饭了");
}
}
}
}
//b对象
public class Gril extends Thread {
@Override
public void run() {
synchronized (Lock.b){
System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
synchronized (Lock.a){
System.out.println(Thread.currentThread().getName()+"获取到1根筷子");
System.out.println("可以吃饭了");
}
}
}
}
//测试类
//获取对象
Boy boy=new Boy();
//设置线程的名字
boy.setName("哈哈");
Gril gril=new Gril();
gril.setName("曦曦");
//开启线程
boy.start();
gril.start();
9.2.造成死锁的原因
锁与锁之间的嵌套导致
9.3.如何解决死锁
1.尽量减少锁得嵌套
2.可以使用一些安全类
3.可以使用Lock中的得枷锁,设置枷锁时间
10.线程通信
10.1.常见的方法
10.1.1.等待
1.wait() 2.wait(long timeout)
注意:必须在对Object枷锁的同步代码块中,在一个线程中,调用Object.wait()时,此线程会释放其拥有的所有标记。同时此线程阻塞在Object的等待队列中,释放锁,进入等待队列
10.1.2.通知--唤醒等待队列中线程,进入到就绪队列,参与cpu的竞争
1.notify() 2.notifyAll();
package guan.demo2;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-18 15:19:35
*/
public class Card {
//定义余额剩余多少
private double balance;
//true:表示有钱 false:表示没钱
private boolean flag=false;
//get set方法
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();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有钱的话,存钱
this.balance=this.balance+money;
System.out.println(Thread.currentThread().getName()+"往卡中存入"+money+"现在卡中余额"+balance);
//存钱完毕,代表有钱
flag=true;
//唤醒等待队列中的线程
this.notify();
}
public synchronized void leave(double money) {
//判断卡中有没有钱
if (flag==false) {
try {
//如果没有钱的话等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//卡中有钱,取钱
this.balance=this.balance-money;
System.out.println(Thread.currentThread().getName()+"往卡中取出"+money+"现在卡中余额"+balance);
//取钱完毕,没有钱了
flag=false;
//唤醒等待队列中的线程
this.notify();
}
}
public class BoyTest implements Runnable{
//定义的卡对象
private Card card;
//有参
public BoyTest(Card card) {
this.card = card;
}
//重写Runnable里面的run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//调用卡中的存钱方法
card.save(1000);
}
}
}
public class GrilTest implements Runnable{
//定义的卡对象
private Card card;
//有参
public GrilTest(Card card) {
this.card = card;
}
//重写Runnable里面的run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//调用卡中的取钱的方法
card.leave(1000);
}
}
}
public class Test {
public static void main(String[] args) {
//创建银行卡对象
Card card=new Card();
BoyTest boyTest=new BoyTest(card);
GrilTest grilTest=new GrilTest(card);
Thread t1=new Thread(boyTest,"曦曦");
Thread t2=new Thread(grilTest,"哈哈");
t1.start();
t2.start();
}
}
11.sleep和wait的区别?
11.1.所在的类不同
sleep属于Thread类,wait属于Object类
11.2.使用的地方不同
sleep可以使用在任何代码块,wait只能在同步代码块内使用
11.3.是否有释放锁资源
sleep不释放锁资源,wait释放锁资源
11.4.sleep时间到了会自动唤醒,wait必须使用notify或者notifyAll来唤醒
12.notify和notifyAll的区别?
notifyAll()会唤醒所有的线程,notify()之后唤醒的是一个线程,notify()调用后,会将全部的线程由等待池移到锁池,然后参与锁的竞争,竞争成功之后则继续执行,如果不成功则流在锁池等待锁被释放然后参与下一次竞争,而notify()只会唤醒一个线程,具体唤醒那一个线程由cpu控制
13.线程池
13.1.什么是线程池
该池子中预先存储若干个线程对象,整个池子就是线程池
13.2.为什么使用线程池
线程是宝贵的内存资源,单个线程约占1MB空间,过分分配容易造成溢出。频繁的创建以及销毁会增加线程虚拟机的回收频率,资源开销,造成功能下降
13.3.线程池的作用
1.线程容器,可以设定线程分配的数量上限
2.将预先创建的线程对象存入池中,并重用线程池中的线程对象
3.避免频繁的创建和销毁
13.4.4.线程池创建的方法有哪些
所有的线程池---封装了一个父接口---java.util.concurrent.Executor.它的实现接口: ExecutorService.
有一个工具类Executors可以帮你创建相应的线程池。
[1] 创建单一线程池 newSingleThreadExecutor()
[2] 创建定长线程池。newFixedThreadPool(n);
[3] 创建可变线程池. newCachedThreadPool()
[4] 创建延迟线程池 .newScheduledThreadPool(n);
package guan.demo3;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-19 09:44:56
*/
public class Test {
public static void main(String[] args) {
// Executor:线程池的根类。里面有一个方法。execute执行线程任务的方法Runnable类型的任务
// * ExecutorService: 线程池的子接口
// * shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
// * shutdownNow(): 立即关闭线程池。
// * isTerminated():判断线程池是否终止了。
// * submit(任务参数): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。
//单一线程池: 适应场景:队列要求线程有序执行。
// ExecutorService executorService = Executors.newCachedThreadPool();
//创建固定长度的线程池对象--默认3个,超出的线程等待
//ExecutorService executorService = Executors.newFixedThreadPool(3);
//创建可变长度的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//延迟任务执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 5; i++) {
//提交任务给线程池中线程对象
//schedule定时执行
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"====");
}
},10, TimeUnit.SECONDS);
}
//关闭线程池--执行玩任务后关闭
executorService.shutdown();
}
}
13.4.5.使用最原始的方式创建线程池
上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
package guan.demo3;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* TODO
*
* @author lenovo
* @version 1.0
* @since 2022-07-19 10:11:00
*/
public class Test2 {
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();
}
}
14手动锁
Lock它是手动锁的父接口,它下面有很多实现类。
lock()方法。 tryLock()---查看是否获取锁资源
unlock()释放锁资源,放在finally中
public void run() {
while (true) {
//上锁
s.lock();
try {
if (ticket < 0) {
break;
}
try {
Thread.sleep(100);//模拟售票需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
ticket--;
}catch (Exception e){
e.printStackTrace();
}finally {
//解锁
s.unlock();
}
}
}
}
15.synchronized和Lock有什么区别?
1.synchronized可以给类,方法,代码块枷锁,而Lock只能给代码块枷锁。
2.synchronized不需要手动获取锁和释放锁,使用简单,发生异常或自动解锁,不会造成死锁,而lock需要自己手动释放锁,如果使用不当没有unlock()去释放锁,就会造成死锁
3.通过Lock可以知道有没有成功获取死锁,ersynchronized却没有办法办到