文章目录
- 线程状态
- 线程方法
- 线程礼让
- 线程优先级
- 守护线程
- 线程同步
- 生产者消费者问题
- 解决方式一:管程法
- 方法二:标志位法
- 死锁
- 总结
线程状态
线程有5种状态,新生态、就绪态、阻塞态、运行态、死亡态
在该图上,就绪状态和运行状态是一个双向箭头,这里是双向箭头的原因是是在线程中有一个线程礼让,当线程进行线程礼让之后,线程会从运行状态转为就绪状态。所以在上面的图上的就绪状态到运行状态是一个双向箭头。
这里需要注意的是当线程被new出来之后并不一定会立即执行,而是进入线程的就绪状态,等待CPU的调度,当线程进入运行状态之后,线程才真正的执行县城提的代码块。
线程方法
线程礼让
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功!看CPU心情
public class ThreadYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"停止执行");
}
}
线程优先级
在线程中,线程获取CPU的调用有多种方式,但是在java中线程获取CPU调用是采用的抢占式进行的。也就是多个线程在抢夺CPU的资源,CPU的什么时候执行哪个线程是不确定的,执行多长时间也是不确定的,所以抢占式调度体现了一个随机性
如果不设置线程的优先级那么默认是5。
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority testPriority=new MyPriority();
Thread thread1=new Thread(testPriority,"线程1");
Thread thread2=new Thread(testPriority,"线程2");
Thread thread3=new Thread(testPriority,"线程3");
Thread thread4=new Thread(testPriority,"线程4");
//先设置优先级再启动
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(4);
thread3.start();
thread4.setPriority(Thread.MAX_PRIORITY);
thread4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
应用场景:后台记录操作日志,监控内存,垃圾回收
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread=new Thread(god);
thread.setDaemon(true);//默认是false,表示用户线程
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 < 3600; i++) {
System.out.println("你一生平安");
}
System.out.println("goodBye");
}
}
线程同步
场景:有三个人(我、你、黄牛)同时抢票,每个人都想抢到票
先看代码:
public class TestThread4 implements Runnable{
//票数
private int ticketNums=10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
//模拟延时
try{
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛").start();
}
}
根据上面的代码可能会出现两个人或多跟人拿到同一张票:
那如何解决这个问题呢?由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来方问
冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待
使用后释放锁即可。
同步后的代码:
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"我").start();
new Thread(station,"ni").start();
new Thread(station,"黄牛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;//票
boolean flag=true;//外部停止方法
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(ticketNums<=0){
flag=false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
存在以下问题:
◆一个线程持有锁会导致其他所有需要此锁的线程挂起;
◆在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引
起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒
置,引起性能问题。
生产者消费者问题
应用场景:生产者和消费者问题
◆假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将
仓库中产品取走消费
◆如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到
仓库中的产品被消费者取走为止
◆如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,
直到仓库中再次放入产品为止
解决方式一:管程法
生产者:负责生产数据的模块(可能是方法,对象,线程,进程):
◆消费者:负责处理数据的模块(可能是方法,对象,线程,进程):
◆缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区“
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
下面用一个买卖东西的例子来说一下,有一个仓库,当仓库中没有产品之后,卖家就往仓库中添加产品,当买家看到仓库中有产品之后就去消费。买家消费的时候卖家等待,卖家生产的时候买家等待。
代码示例:
生产者:
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了"+i+"只鸡");
}
}
}
消费者:
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println("消费了-->"+container.pop().id+"只鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
产品:
//产品
class Chicken{
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
```java
缓冲区
//缓冲区
class SynContainer{
//容器大小
Chicken[] chickens=new Chicken[10];
// 容器计数器
int count=0;
//生产者放入产品
public synchronized void push(Chicken chicken) throws InterruptedException {
//如果容器满了,需要等待消费者消费
if(count== chickens.length){
//通知消费者消费,生产者等待
this.wait();
}
//如果没有满,我们就需要丢入产品
chickens[count]=chicken;
count++;
System.out.println("count------"+count);
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop() throws InterruptedException {
//判断能否消费
if (count==0){
//等待生产者生产,消费者等待
this.wait();
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
System.out.println("消费者---"+count);
//吃完了,通知生产者生产
return chicken;
}
}
主函数:
public class TubeSide {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
方法二:标志位法
类似于信号灯,当处于什么信号的时候,执行什么状态。
下面用一个演员和观众的例子来说一下标志位法,当演员表示的时候,观众等待,演员表演完之后通知观众来观看,这时候演员等待。同理,当观众观看时,演员等待。
生产者:
//生产者---演员
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
try {
this.tv.play("播放中");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
try {
this.tv.play("记录生活");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
消费者
//消费者---观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
tv.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
产品
//产品---节目
class TV{
//演员表演,观众等待
//观众观看,演员等待
String voice;//表演的节目
boolean flag=true;
//表演
public synchronized void play(String voice) throws InterruptedException {
if(!flag){
this.wait();
}
System.out.println("演员表演了"+voice);
//通知观众观看
this.notify();
this.voice=voice;
this.flag=! this.flag;
}
//观看
public synchronized void watch() throws InterruptedException {
if (flag){
this.wait();
}
System.out.println("观看了:"+voice);
//通知演员表演
this.notify();
this.flag=!this.flag;
}
}
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而
导致两个或者多个线程都在等待对方释放资源,都停止执行的情形某一个同步块
同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
避免死锁的办法:
产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件
就可以避免死锁发生
总结
线程是计算机程序中执行的最小单位,它是进程中的一个独立执行路径。多线程编程使得程序可以同时执行多个任务,提高了程序的并发性和响应性。
以下是关于线程的一些总结:
线程的创建:线程可以通过继承Thread类或实现Runnable接口来创建。通过继承Thread类,需要重写run()方法来定义线程的任务;通过实现Runnable接口,需要实现run()方法,并将Runnable对象传递给Thread类的构造函数来创建线程。
线程的状态:线程可以处于多个状态,包括新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和终止(Terminated)等。线程的状态会随着线程的执行和调度而不断变化。
线程同步:在多线程环境下,可能会出现多个线程同时访问共享资源的情况,为了避免竞态条件和数据不一致的问题,需要使用线程同步机制。常用的线程同步机制包括使用synchronized关键字、使用ReentrantLock类、使用volatile关键字等。
线程通信:线程之间可以通过共享变量、等待/通知机制、管道、锁等方式进行通信。常用的线程通信方式包括使用wait()、notify()、notifyAll()方法实现等待/通知机制,以及使用BlockingQueue、CountDownLatch、CyclicBarrier等线程同步工具类。
线程池:线程池是一种管理和复用线程的机制,它可以避免频繁创建和销毁线程的开销。通过线程池,可以方便地管理线程的生命周期、控制线程的数量、提高线程的执行效率。
线程安全:线程安全是指多个线程同时访问某个资源时,不会出现数据不一致或不正确的情况。编写线程安全的代码需要考虑对共享资源的并发访问控制,使用合适的同步机制来保证数据的一致性和正确性。
线程调度:线程调度是操作系统或虚拟机决定哪个线程在某个时刻执行的过程。线程调度算法可以根据不同的策略和优先级来决定线程的执行顺序和时间片分配。