java多线程及并发编程(1)

本文深入解析Java多线程的实现方式,包括继承Thread类和实现Runnable接口,探讨线程间的数据共享与同步机制,如volatile关键字和synchronized关键字的作用,以及线程的生命周期管理和多线程间的协作。通过生产者消费者模型实例,阐述了如何避免线程死锁,介绍了守护线程的概念及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Part1: OS多线程多进程简介

Part2:Java多线程的实现

两种实现方法:
  1. 继承Thread类 extends Thread
  2. 实现Runnable方法 implements Runnable

在类中实现run方法

public void run(){
    //todo: do thread task at here
}
Java四大接口:
  1. Clonable 用于对象克隆
  2. Comparable 用于对象比较
  3. Serializable 用于对象序列化 (*序列化:将数据对象转化成二进制流
  4. Runnable 用于对象线程化
线程启动

start方法:底层用JNI实现 (*JNI:java native interface 可以使java程序调用c/c++程序

Runnable方式:

public class myThread implements Runnable{
    public void run(){
        //todo: do thread task at here
        
    }
}

public class test{
    public static void main(String[] arg){
        //必须把实现类包装在线程类里面再进行start
        new Thread( new myThread() ).start();   
    }
}

当前线程名:

Thread.currentThread.getName()

一个线程对象只能启动一次线程,要多次启动线程请使用多个对象:

Error:

Thread t = new Thread(new myThread());
t.start();
t.start();
Accept:

Thread t1 = new Thread(new myThread());
Thread t2 = new Thread(new myThread());
t1.start();
t2.start();

Part 3:多线程信息共享

粗粒度:线程之间没有交流

细粒度:线程之间有信息交流通讯

  • 通过共享变量达到信息共享
  • c/c++ 有MPI并行库,可以支持线程发送消息(jdk没有)
通过共享变量来共享消息
  • static变量: 所有线程共享static变量
  • extends Thread类的成员变量: 非共享,一个线程对应一个成员对象
  • Runnable类的成员变量: Runnable类只被new了一次,只会有一个成员变量

extends Thread:

public class testThread extends Thread{
    private int tickets = 100;
    
    public void run(){
        //todo: to do thread task at here
    }
}

implements Runnable:

public class testRunnable implements Runnable{
    private int tickets = 100;
    
    public void run(){
        //todo:to do thread task at here
    }
}

启动线程

public void main(String[] args){
    //启动testThread时,new了三次该类,所以就会有三个tickets,一个线程对应一个tickets
    new testThread().start();
    new testThread().start();
    new testThread().start();
    
    //启动testRunnable时,只new类一次该类,所以只会有一个tickets被三个线程使用
    testRunnable t = new testRunnable();
    new Thread(t).start();
    new Thread(t).start();
    new Thread(t).start();
}
volatile关键字
解决工作副本的可见性问题

对于加了volatile关键字的成员变量,它发生修改时,java会同步所有线程内的工作缓存与内存

public class testRunnable implements Runnable{
    //boolean flag = true;
    volatile boolean flag = true;
    
    public void run(){
        while(flag){
            System.out.println(Thread.currentThread().getName() + "is existing");
        }
        
        System.out.println(Thread.currentThread().getName() + "is quit");
    }
    
    public static void main(String[] args) throws InterruptedException{
        testRunnable t = new testRunnable();
        new Thread(t).start();
        Thread.sleep(2000);
        t.flag = false;
    }
}
synchronized关键字
关键步骤加锁限制
  • 互斥:某一个线程运行一个代码段(关键区),其他线程不能同时运行
  • 同步:多个线程运行,必须按照某一种规定的先后顺序来运行
  • 互斥是同步的一种特例

以下代码中的示例两把锁,只要加任意一把都可以实现互斥逻辑:

public class saleTickets implements Runnable{
   
    private volatile int tickets = 100;
    //代码块的锁
    private String lock = new String("");
    
    public void run(){
        while(true){
            //通过线程竞争lock信号量来同步代码块
            synchronized(lock){ sale(); }
            if( tickets <= 0 ) break;
        }
    }
    //函数的锁
    public synchronized void sale(){
        if( tickets > 0){ 
            --tickets;
            System.out.println(Thread.currentThread().getName() + "is saling" + tickets);
            
        } 
    }
    
    public static void main(String[] args){
        saleTickkets t = new saleTickets();
        new Thread(t,"Thread-0").start();
        new Thread(t,"Thread-1").start();
        new Thread(t,"Thread-2").start();
        new Thread(t,"Thread-3").start();
    }
}
synchronized会加大程序性能负担,请务必理解java的内存模型

Part4:Java多线程管理

线程的生命周期管理

IDE线程查看工具:

jvisualvm

线程的同步协作:

  • 等待
  • 通知/唤醒
  • 终止

线程状态:

image

Thread类API:

已废弃:

  • 暂停/恢复:suspend/resume
  • 停止/消亡:stop/destroy

仍在使用的api:

例子:生产者消费者模型

生产者消费者模型—详解及代码实现

经典生产者与消费者问题:

生产者不断的往仓库中存放产品,消费者从仓库中消费产品。

其中生产者和消费者都可以有若干个。

仓库规则:容量有限,库满时不能存放,库空时不能取产品。

image

Storage中pop方法

    // 生产者往仓库中放入产品
	public synchronized void push(Product product) {
		while (top == products.length) {
			try {
				System.out.println("producer wait");
				wait();//仓库已满,等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        //把产品放入仓库
		products[top++] = product;
		System.out.println(Thread.currentThread().getName() + " 生产了产品"
				+ product);
		System.out.println("producer notifyAll");
		notifyAll();//唤醒等待线程
	}

Storage中的push方法

// 消费者从仓库中取出产品
	public synchronized Product pop() {
		while (top == 0) {
			try {
				System.out.println("consumer wait");
				wait();//仓库空,等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		//从仓库中取产品
		--top;
		Product p = new Product(products[top].getId(), products[top].getName());
		products[top] = null;
		System.out.println(Thread.currentThread().getName() + " 消费了产品" + p);
		System.out.println("comsumer notifyAll");
		notifyAll();//唤醒等待线程
		return p;
	}

上述代码的缺点:
线程被动地暂停和终止

  1. 依靠别的线程来拯救自己
  2. 没有及时释放资源(如果这种线程拿到了锁,很容易造成死锁)

改进方法:
线程主动暂停和终止

  1. 定期监测共享变量(信号量),而不是等待别的线程来notify
  2. 如需暂停或终止,先释放资源,再做动作
线程被动暂停和线程主动暂停

被动终止:等待其他线程interrupt自己,本线程在sleep时被打断,会抛出sleep interrupt异常

class TestThread1 extends Thread {
	public void run() {
		// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
		while (!interrupted()) {
			System.out.println("test thread1 is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
				break;
			}
		}
		System.out.println("test thread1 is exiting");
	}
}

主动终止:监控共享信号量flag,能做到安全的退出线程

class TestThread2 extends Thread {
	public volatile boolean flag = true;
	public void run() {
		// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
		while (flag) {
			System.out.println("test thread2 is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("test thread2 is exiting");
	}
}
多线程死锁
  • 每个线程互相持有别人需要的锁
  • 预防死锁:对资源进行等级排序
守护线程

线程对象.setDaemon(true)

守护线程与系统main线程同生共死,这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。

当java虚拟机中没有非守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,守护线程不管运行到哪里,虚拟机都会退出运行。

例如:垃圾回收线程

注意⚠️:守护线程永远不要访问资源,如文件或数据库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值