本文共 16671 字,大约阅读时间需要 55 分钟。
目录
程序、进程、线程的概念
程序是为完成特定任务、用某种语言编写的一组指令的集合,即指一段静态的代码。
进程就是正在执行的程序,从windows的角度讲,进程是操作系统进行资源分配的最小单位。
线程进程可进一步细化为线程,是一个进程内部的最小执行单元 ,是操作系统进行任务调度的最小单元,隶属于进程。是cpu进行调度的最小单位。
线程和进程的关系
一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程
而独立运行; (例如在执行qq的进程时,不会突然跳转到其他的进程中)
每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序,java 程序的入口main()方法就是在主线程中被执行的。
在主线程中可以创建并启动其它的线程;
一个进程内的所有线程共享该进程的内存资源。(线程在进程的内部执行)
创建线程
在单线程中仍然遵循的时自上而下的运行规则。在多线程中才会并发的执行线程。
方式有两种:
继承Thread类的方式
Thread是程序中执行的线程,Java虚拟机允许程序同时执行多个线程。创建线程的方法创建一个类继承Thread类,直接调用父类中的方法启动即可。
public class Test { public static void main(String[] args) { ThreadDemo threadDemo=new ThreadDemo(); //启动线程的方法 start0(); threadDemo.start(); ThreadDemo1 threadDemo1=new ThreadDemo1(); threadDemo1.start();//会出现交替执行,消费完本次的时间片后,会交替为其他线程 }}public class ThreadDemo extends Thread{ @Override public void run() { for(int i=0;i<30;i++){ try { sleep(100); System.out.println(i); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class ThreadDemo1 extends Thread { @Override public void run() { for(int i=0;i<30;i++){ try { sleep(100); System.out.println("ThreadDemo1:"+i); } catch (InterruptedException e) { e.printStackTrace(); } } }}
实现Runnable接口的方式
java.lang.Runnable接口中仅仅只有一个抽象方法:
public void run();
Runnable接口的存在主要是为了解决Java中不允许多继承的问题。
public class Demo { //main方法在主线程中执行 public static void main(String[] args) { MyThread myt=new MyThread(); Thread th=new Thread(myt); //这才是创建了一个线程,myt的类中的内容是为这个线程分配的任务 th.start(); for(int i=0;i<100;i++){ System.out.println("main:"+i); } }}public class MyThread implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println("Runable:"+i); } }}
继承Thread类虽然方便,可以直接调用,但是却不能再继承其他的类,因此,实现Runable接口有更好的扩展性。
继承方式和实现方式的联系与区别
【区别】
继承Thread: 线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
【实现Runnable的好处】
1)避免了单继承的局限性
2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处
理同一份资源。
Thread类中方法
Thread.curentThread() //得到当前执行线程的对象
Thread.currentThread().getName () //拿到线程的名字
Thread th=new Thread(myth,"窗口1");//也可以通过在创建对象是赋值名字。这是一个构造方法,可以分配任务以及定义线程的名称
Thread.currentThread().setName();另一种为对象定义名称的方式
线程的优先级
事实上,计算机只有一个cpu,各个线程轮流获得cpu的使用权,才能执行任务;优先级较高的线程有更多获得cpu的机会,反之亦然。优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级;
public class Test { public static void main(String[] args) { ThreadDemo threadDemo=new ThreadDemo(); threadDemo.setPriority(5);//放置的优先级为5 threadDemo.start(); ThreadDemo1 threadDemo1=new ThreadDemo1(); threadDemo1.setPriority(10);//放置的优先级为10 threadDemo1.start(); System.out.println( "threadDemo1: "+threadDemo1.getPriority()); System.out.println("threadDemo: "+threadDemo.getPriority()); }}
public class ThreadDemo extends Thread{ @Override public void run() { for(int i=0;i<30;i++){ try { sleep(100); System.out.println("ThreadDemo:"+i); } catch (InterruptedException e) { e.printStackTrace(); } } }}
public class ThreadDemo1 extends Thread { @Override public void run() { for(int i=0;i<30;i++){ try { sleep(100); System.out.println("ThreadDemo1:"+i); } catch (InterruptedException e) { e.printStackTrace(); } } }}
开始要先运行主线程,因此会先显示优先级
调度策略
时间片 每个线程执行固定的时间片,来进行交替执行
抢占式:高优先级的线程抢占CPU
Java中的线程支持设置优先级的,默认的优先级是5,最低是1,最高是10
线程状态
线程在它的生命周期中会处于不同的状态:
● 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对
象处于新建状态
● 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时
间片,此时它已具备了运行的条件,只是没分配到CPU资源
● 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run
()方法定义了线程的操作和功能
● 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出
CPU并临时中止自己的执行,进入阻塞状态
● 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常
导致结束
新建状态需要调用start()进入就绪状态,就绪状态被cpu调度上后进入到运行状态获得cpu的执行权(有操作系统的调度算法来决定运行状态)。
如果正常的失去cpu的执行权,就回到就绪状态。而yield()方法属于线程让步,当线程正在运行时,调用此方法,主动的让出cpu的执行权,不会进入到阻塞状态,会重新进入到就绪状态进行排队。还有IO阻塞,例如在输入时一直没有输入,cpu也不会一直等待,等确定输入内容后提交,才会再重新进入就绪状态进行排队。
非正常的失去cpu的执行权则到达阻塞状态,例如sleep()主动的让线程进入休眠。join()方法让此线程加入到当前线程,等待此线程死亡,死亡后再执行当前线程,因此当前线程进入阻塞。
运行状态中的线程正常执行完毕,就会进入到死亡状态。stop()方法也可以结束线程,但是这种方法是不安全的,在运行过程中可能会造成进程的异常。
线程的分类
Java中的线程分为两类:用户线程和守护线程
守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个 IllegalThreadStateException异常。
public class ThreadDemo extends Thread{ @Override public void run() { for(int i=0;i<30;i++){ try { sleep(100); System.out.println("用户线程:"+i); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThreadDemo threadDemo=new ThreadDemo(); threadDemo.start(); ThreadDemo1 threadDemo1=new ThreadDemo1(); threadDemo1.setDaemon(true);//设置为守护线程,必须在线程启动前设置 threadDemo1.start(); } public void run() { while(true){ try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("守护进程"); } }}
多线程概念
对线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程执行不同的任务,也就是说允许多个程序创建多个并行执行的线程来完成各自的任务。
何时需要多线程
● 程序需要同时执行两个或多个任务。
● 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、
网络操作、搜索等。
● 需要一些后台运行的程序时。
多线程的优点
-
提高程序的响应.
-
提高CPU的利用率.
-
改善程序结构,将复杂任务分为对个线程,独立运行.
多线程的缺点
-
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多
-
多线程需要协调和管理,所以需要cpu时间跟踪线程
-
线程之间对共享资源的访问会相互影响,必须解决竟用共享资源的问题(多个线程在对同一个共享资源访问时,可能会出现问题。例如买票问题)
线程同步
多线程同步
多个线程同时读写同一份共享资源时,可能会引起冲突,所以引入线程“同步”机制,即各线程间要有先来后到。
同步就是排队+锁
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(访问量大时必须加锁)
public class ReenDemo implements Runnable{ //ReentrantLock() int ticket=10; static Lock lock=new ReentrantLock(); /* 显示的加锁,加在代码中间 */ @Override public void run() { while(true){ try { lock.lock(); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(ticket>0){ System.out.println(Thread.currentThread().getName()+" "+ticket); ticket--; }else { break; } lock.unlock(); } } public static void main(String[] args) { ReenDemo ticket1=new ReenDemo(); new Thread(ticket1,"窗口1").start(); new Thread(ticket1,"窗口2").start(); }}
确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源.
在Java代码中实现同步:
使用synchronized(同步监视器)关键字同步方法或代码块。
synchronized (同步监视器){
// 需要被同步的代码;
}
synchronized还可以放在方法声明中,表示整个方法,为同步方法。
例如:
public synchronized void show (String name){
// 需要被同步的代码;
}
/* 创建一个出票的线程 */public class TicketThread extends Thread{ //static 修饰的变量是类变量,在内存中只有一份 static int num = 10;// 设定有10张票 static Object obj = new Object(); @Override public void run() { while(true){ /* synchronized (同步对象) 同步对象可以是任意类的对象,但是只能唯一的,只有一份的 继承Thread时不能用this,因为我们创建两个线程对象,哪个线程访问,this就表示的是哪个线程,因此会造成两个线程都可以进入。但是实现Runable接口时,因为线程任务只有一个,因此使用this时可以的 对象中有一个区域叫对象头,对象头中有一个锁状态的记录 */ synchronized (obj){//进入同步代码块 加锁 也就将对象头锁状态改为 加锁 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(num>0){ System.out.println(Thread.currentThread().getName()+"买到了票:"+num); num--; }else{ break; } //同步代码块执行完成后,会自动释放锁 也就将对象头锁状态改为 无锁 } } } public static void main(String[] args) { TicketThread t1 = new TicketThread(); t1.setName("窗口1"); t1.start(); TicketThread t2 = new TicketThread(); t2.setName("窗口2"); t2.start(); }}
/* 创建一个出票的线程*/public class ThreadDemo implements Runnable{ static int num=10; public void run(){ while(true) { synchronized (this) { try { Thread.sleep(30); if (num > 0) { System.out.println(Thread.currentThread().getName() + ":" + num); num -= 1; } else { break; } } catch (InterruptedException e) { e.printStackTrace(); } } } }}public static void main(String[] args){ TicketThread t=new TicketThread(); new Thread(t,"窗口1").start(); new Thread(t,"窗口2").start( ); }
synchronized 修饰非静态方法,锁标志的对象默认是this。因此继承Thread类的类创建线程时,调用ticket()方法需要使用静态方法
synchronized修饰静态方法,锁标志对象时类的Class对象,只要是同一个类的对象,那么他们对应的Class对象是相同的 (继承Thread)
public class TicketThread extends Thread{ //static 修饰的变量是类变量,在内存中只有一份 static int num = 10;// 设定有10张票 static Object obj = new Object(); @Override public void run() { while(true){ ticket(); if(num==0){ break; } } } /* synchronized 修饰非静态方法,锁标志的对象默认是this synchronized 修饰静态方法,锁标志对象是类的Class对象,只要是同一个类的对象,那么他们对应的Class对象时相同的 */ public static synchronized void ticket(){ if(num>0){ System.out.println(Thread.currentThread().getName()+"买到了票:"+num); num--; } } public static void main(String[] args) { TicketThread t1 = new TicketThread(); t1.setName("窗口1"); t1.start(); TicketThread t2 = new TicketThread(); t2.setName("窗口2"); t2.start(); System.out.println(t1.getClass()==t2.getClass()); }}
实现Runnable接口时,synchronized修饰非静态方法,锁标志的对象默认是this,但执行的任务本来就只有一个所以可以直接调用这个非静态方法。在修饰静态方法时,因为本来就是执行一个任务,因此是否使用静态方法都可以。
public class TicketDemo implements Runnable { int num = 10; @Override public void run() { while(true){ try { ticket(); } catch (InterruptedException e) { e.printStackTrace(); } if(num<=0){ break; } } } /* 由于出票任务类,只创建了一个对象,所以两个线程拿到的this是同一个 */ public synchronized void ticket() throwsInterruptedException { if(num>0){ Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+":"+num); num--; } } public static void main(String[] args) { TicketDemo ticketDemo = new TicketDemo(); Thread t1 = new Thread(ticketDemo,"窗口1"); Thread t2 = new Thread(ticketDemo,"窗口2"); t1.start(); t2.start(); }}
一个线程持有锁会导致其他素有需要此锁的线程挂起;在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
Lock(锁)
•从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象
来实现同步。同步锁使用Lock对象充当。
•java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的
工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,
线程开始访问共享资源之前应先获得Lock对象。
•ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存
语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加
锁、释放锁.
public class TicketThread extends Thread{ //static 修饰的变量是类变量,在内存中只有一份 static int num = 10; static Lock lock = new ReentrantLock(); @Override public void run() { while(true){ try { lock.lock();//加锁 锁标记在ReentrantLock对象维护 Thread.sleep(100); if(num>0){ System.out.println(Thread.currentThread().getName()+"买到了票:"+num); num--; }else{ break; } }catch (Exception e){ }finally { lock.unlock();//释放锁 } } } public static void main(String[] args) { TicketThread t1 = new TicketThread(); t1.setName("窗口1"); t1.start(); TicketThread t2 = new TicketThread(); t2.setName("窗口2"); t2.start(); }}
synchronized和Lock ReentrantLock的区别
synchronized是一个关键字,修饰代码块和方法,隐式的加锁和释放锁,底层实现需要靠虚拟机底层控制完成。
Lock ReentrantLock是一个实现类,实现了Lock接口中的方法,只能修饰代码块,并且是显示的加锁和释放锁,是通过Java代码来控制实现的。
线程死锁
死锁概念
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步 资源,就形成了线程的死锁. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续.
以下模拟一段可能造成死锁的代码:
public class DieLockThread extends Thread{ static Object objA = new Object(); //a锁 static Object objB = new Object(); //b锁 boolean flag;//标记 public DieLockThread(boolean flag) { this.flag = flag; } @Override public void run() { if(flag){ synchronized (objA){ System.out.println("if objA"); synchronized (objB){ System.out.println("if objB"); } } }else{ synchronized (objB){ System.out.println("else objb"); synchronized (objA){ System.out.println("else obja"); } } } } public static void main(String[] args) { DieLockThread t1 = new DieLockThread(true); DieLockThread t2 = new DieLockThread(false); t1.start(); t2.start(); }}
线程通信
线程通讯指的是多个线程相互牵制,相互调度,即线程间对的相互作用,要实现这种牵制,前提需要在同步代码块中才能实现。
涉及三个方法:
.wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(相当于释放锁)。 因此wait()与sleep()的区别就在于 wait()属于Object类,线程执行该方法的同时会释放锁,进入到阻塞状态,唤醒需要使用notify()方法。sleep()方法属于Thread类,线程调用该方法进入休眠,但不会释放锁,一直等到休眠结束继续运行该线程。
.notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
.notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
注意:
.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
经典例题:生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待。
/* 消费者线程 */public class CustomerThread extends Thread{ Counter counter; public CustomerThread(Counter counter) { this.counter = counter; } @Override public void run() { while(true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } counter.minus(); } }}
/* 生产者线程 */public class ProductorThread extends Thread{ Counter counter; public ProductorThread(Counter counter) { this.counter = counter; } @Override public void run() { while(true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } counter.plus(); } }}
/* 只用一个柜台 里面有一个产品数量 */public class Counter { int num = 1; /* 生产者生产商品 由于只创建了一个Counter对象,synchronized修饰方法,同步锁对象时this,plus和minus 两个方法公用同一把锁 */ public synchronized void plus(){ if(num==0){ num++; System.out.println("生产者生产商品:"+num); this.notify();//生产者生产后,唤醒消费者 }else{ try { this.wait();//柜台有商品的,直接等待,释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } } /* 消费者取走商品 */ public synchronized void minus(){ if(num>0){ num--; System.out.println("消费者拿走商品:"+num); this.notify();//消费者拿走商品,唤醒生产者 }else{ try { this.wait();//没有商品,消费者等待,释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } }}
public class Test { public static void main(String[] args) { Counter counter = new Counter();//两个线程共享的柜台 ProductorThread p = new ProductorThread(counter); CustomerThread c = new CustomerThread(counter); p.start(); c.start(); }}
新增创建线程方式
实现Callable接口与使用Runnable相比,Callable功能更强大些.
• 相比run()方法,可以有返回值
• 方法可以抛出异常 ,其他的创建线程的方式都需要try catch处理异常
• 支持泛型的返回值
• 需要借助FutureTask类,获取返回结果
接收任务
FutureTask<Integer> futureTask = new FutureTask(任务);
创建线程
Thread t = new Thread(futureTask);
t.start();
Integer val = futureTask.get();获得线程call方法的返回值
有问题希望大家可以提出来大家一起学习~~~
转载地址:https://blog.csdn.net/weixin_53086191/article/details/123362254 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!