线程小结

本文深入探讨Java多线程的基础概念、创建线程的方法、线程的生命周期及同步机制等核心知识点,并通过示例代码详细解析线程安全问题及其解决方案。

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

1.   理解程序、进程、线程的概念

程序:静态的代码

进程:执行中的程序

线程:可以理解为进程的多条执行线索,每条线索又对应着各自独立的生命周期

2.   如何创建java程序的线程(重点)

1)       创建多线程的方式一:继承方式实现

package cn.km.thread01;

class SubThread1 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i%2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
	
	public SubThread1(String name) {
		super(name);
	}
}

class SubThread2 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i%2 != 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
	public SubThread2(String name) {
		super(name);
	}
}

public class TestThread01 {
	public static void main(String[] args) {
		SubThread1 st1 = new SubThread1("线程1");
		SubThread2 st2 = new SubThread2("线程2");
		
		st1.setPriority(Thread.MIN_PRIORITY);//设置优先级最低 1
		st2.setPriority(Thread.MAX_PRIORITY); //设置优先级最高 10
		
		st1.start();
		st2.start();
	}
}

2)       多线程实现方式二:实现方式实现

创建一个实现了Runnable接口的类

实现接口的抽象方法

创建一个Runnable接口实现类的对象

将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程

启动线程,执行Thread的start方法。

package cn.km.thread01;
class subThread implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i%2 != 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}
public class TestThread02 {
	public static void main(String[] args) {
		subThread target = new subThread();
		Thread thread1 = new Thread(target);
		Thread thread2 = new Thread(target);
		Thread thread3 = new Thread(target);
		
		thread1.setName("线程1");
		thread2.setName("线程2");
		thread3.setName("线程3");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

3)       两者的异同

联系:Thread也实现了Runnable,

哪个方式好??实现方式优于继承方式

Why?  避免了java单继承的局限性

                   如果多个线程要操作同一份资源(或数据),更适合使用实现的方式

4)       模拟窗口售票代码(此代码有隐患)


package cn.km.thread01;
class Window implements Runnable{
	int ticket = 100;
	@Override
	public void run() {
		while(true){
			if(ticket > 0){
//				try {
//					Thread.currentThread().sleep(10);
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
				System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--);
			}else{
				break;
			}
		}
		
	}
}

public class TestThread03 {
	public static void main(String[] args) {
		Window target = new Window();
		Thread thread1 = new Thread(target);
		Thread thread2 = new Thread(target);
		Thread thread3 = new Thread(target);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		thread3.setName("窗口3");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

3.   线程的常用方法


1)       start(),启动线程并执行相应的run()方法

2)       run():子线程要执行的代码放入run()方法中

3)       currentThread() :静态的,调取当前的线程

4)       getName() :获取此线程的名称

5)       setName() :设置此线程的名称

6)       yield() :调用此方法的线程释放当前CPU的执行权

7)       join():在A线程中调用B线程的join()方法,表示当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行

8)       isAlive() :判断当前线程是否还存活

9)       sleep(long l) :显示的让当前线程睡眠1毫秒

10)    线程的通信:wait()  notify()  notifyAll()

11)    设置线程的优先级

getPriority() :返回线程优先值

setPriority(intnewPriority) :改变线程的优先级【MAX_PRIORITY=10;MIN_PRIORITY =1;NORM_PRIORITY=5;】


4.   线程的生命周期



5.   线程的同步机制(重点、难点)

package cn.km.thread02;

/**
 * 此程序存在线程安全问题:打印车票时,会出现重票、错票
 * 1.线程安全问题存在的原因
 *	由于一个线程再操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题
 * 2.如何解决线程安全问题
 * 	必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作
 * 3.java如何实现线程的安全:线程同步机制
 * 	方式一:同步代码块
 * 		synchronized(同步监视器){
 * 			//需要被同步的代码块(即为操作共享数据的代码)
 * 		}
 * 		共享数据:多个线程共同操作的同一个数据(变量)
 * 		同步监视器:由一个类的对象充当。哪个线程获取此监视器,谁就执行大括号中被同步的代码。俗称:锁
 * 		要求:所有线程必须公用一把锁
 * 		注意:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this
 * 	方式二:同步方法
 */

@SuppressWarnings("static-access")
class Window implements Runnable{
	int ticket = 100;
	@Override
	public void run() {
		while(true){
			synchronized (this) { //加锁
				if (ticket > 0) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--);
				} else {
					break;
				}
			}
		}
		
	}
}

public class TestWindow01 {
	public static void main(String[] args) {
		Window target = new Window();
		Thread thread1 = new Thread(target);
		Thread thread2 = new Thread(target);
		Thread thread3 = new Thread(target);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		
		thread1.start();
		thread2.start();
	}
}

此程序存在线程安全问题:打印车票时,会出现重票、错票

  1.线程安全问题存在的原因

        由于一个线程再操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题

  2.如何解决线程安全问题

       必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作

 3.java如何实现线程的安全:线程同步机制

6.   线程的同步实现(重点、难点)

1)       同步代码块

synchronized(同步监视器){

           //需要被同步的代码块(即为操作共享数据的代码)

 }

共享数据:多个线程共同操作的同一个数据(变量)

同步监视器:由一个类的对象充当。哪个线程获取此监视器,谁就执行大括号中被同步的代码。俗称:锁

要求:所有线程必须公用一把锁

 注意:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this


2)       同步方法

package cn.km.thread02;

/**
 * 此程序存在线程安全问题:打印车票时,会出现重票、错票
 * 1.线程安全问题存在的原因
 *	由于一个线程再操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题
 * 2.如何解决线程安全问题
 * 	必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作
 * 3.java如何实现线程的安全:线程同步机制
 * 	方式一:同步代码块
 * 		synchronized(同步监视器){
 * 			//需要被同步的代码块(即为操作共享数据的代码)
 * 		}
 * 		共享数据:多个线程共同操作的同一个数据(变量)
 * 		同步监视器:由一个类的对象充当。哪个线程获取此监视器,谁就执行大括号中被同步的代码。俗称:锁
 * 		要求:所有线程必须公用一把锁
 * 		注意:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this
 * 	方式二:同步方法
 */

@SuppressWarnings("static-access")
class Window3 implements Runnable{
	int ticket = 100;
	@Override
	public void run() {
		while(true){
			sell();
		}
		
	}
	
	public synchronized void sell(){ //添加同步方法  
		if (ticket > 0) {
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--);
		}
	}
}




public class TestWindow02 {
	public static void main(String[] args) {
		Window3 target = new Window3();
		Thread thread1 = new Thread(target);
		Thread thread2 = new Thread(target);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		
		thread1.start();
		thread2.start();
	}
}



7.   线程的通信

Notify/notifyAll

Wait

注意:java.lang.Object提供的这三个方法只有在synchronized方法或代码块中才能使用,否则会有异常:java.lang.IllegalMonitorStateException



package cn.km.thread02;

/**
 * 
 */

@SuppressWarnings("static-access")
class Window4 implements Runnable{
	int ticket = 100;
	@Override
	public void run() {
		while(true){
			sell();
		}
		
	}
	
	public synchronized void sell(){ //添加同步方法  
		notify(); //唤醒线程
		if (ticket > 0) {
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--);
		}
		//线程等待
		try {
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}




public class TestWindow03 {
	public static void main(String[] args) {
		Window4 target = new Window4();
		Thread thread1 = new Thread(target);
		Thread thread2 = new Thread(target);
		
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		
		thread1.start();
		thread2.start();
	}
}


8.   生产者和消费者问题

生产者(Productor)将产品交给店员(Clerk),消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品,如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。


package cn.km.thread03;

/**
 * 生产者/消费者问题 生产者(Productor)将产品交给店员(Clerk), 消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品,如果生产者视图生产更多的产品,店员会叫生产者停一下, 如果店中有空位放产品了再通知生产者继续生产;
 * 如果店中没有产品了,店员会告诉消费者等一下, 如果店中有产品了再通知消费者来取走产品。
 * 
 * 
 * 分析:
 1.是否涉及到多线程的问题? 是 生产者/消费者 
2.是否涉及共享数据 ? 有 需要考虑线程安全 
3.此共享数据为谁? 产品的数量
 4.是否涉及到线程的通信呢? 存在生产者、消费者的通信;
 */

class Clerk { // 店员
	int product;

	public synchronized void addProduct() {// 生产产品
		if (product >= 20) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			product++;
			System.out.println(Thread.currentThread().getName() + ":生产了第" + product + "个产品");
			notifyAll();
		}
	}

	public synchronized void consumeProduct() { // 消费产品
		if (product <= 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			System.out.println(Thread.currentThread().getName() + ":消费了第" + product + "个产品");
			product--;
			notifyAll();
		}
	}

}

class Producer implements Runnable {
	Clerk clerk;

	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}

	public void run() {
		System.out.println("生产者开始生产产品");
		while (true) {
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	};
}

class Consumer implements Runnable {
	Clerk clerk;

	public Consumer(Clerk clerk) {
		this.clerk = clerk;
	}

	public void run() {
		System.out.println("消费者开始消费产品");
		while (true) {
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.consumeProduct();
		}
	};
}

public class ProductorCustomer {

	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		Producer producer1 = new Producer(clerk);
		Consumer consumer1 = new Consumer(clerk);
		
		Thread t1 = new Thread(producer1);
		Thread t2 = new Thread(consumer1);
		Thread t3 = new Thread(producer1);
		
		t1.setName("生产者1");
		t2.setName("消费者");
		t3.setName("生产者2");
		
		
		t1.start();
		t2.start();
		t3.start();
	}
}



9.   面试题总结

1)     wait  和 sleep 方法的区别

1)这两个方法来自不同的类分别是   sleep来自Thread类,和wait来自Object

sleep是Thread的静态方法,谁调用谁就去睡觉,

即使在a线程里调用b线程的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep????????

2)锁:最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他的线程可以使用同步控制块或方法

sleep不出让系统资源

wait是进入线程等待池等待,出让系统资源,其他线程可以占用cpu。

wait不加时间限制,要等待其他的notify/notifyAll唤醒等待池中所有的线程,才会进入就绪队列等待分配系统资源。。

sleep(milliseconds)可以使用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

3)使用范围:wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

synchronized(){

         notify();//notifyAll();

         //wait();

}


1)     创建线程的3种方式

继承Thread

实现Runnable接口

实现Callable接口

package cn.km.thread05;
import java.util.concurrent.Callable;

public class MyCallable implements Callable{

	@Override
	public Object call() throws Exception {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":"+i);
		}
		return null;
	}
}
public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(2);
		pool.submit(new MyCallable());
		pool.submit(new MyCallable());
		
	}


2)     什么是线程安全
3)     Runnable和Callable接口的区别
4)     synchronized、lock、ReentrantLock、ReadWriteLock
5)     介绍下CAS(无锁技术)
6)     什么是ThreadLocal
7)     创建线程池的4中方式
8)     ThreadPoolExecutor的内部工作原理

9)     分布式环境下,怎么保持线程安全


正在整理中。。。





Java多线程是指在一个Java程序中同时执行多个线程,每个线程都是独立的执行流。Java中创建线程的方式有三种:继承Thread类、实现Runnable接口和实现Callable接口。每种方式都有其优缺点。 1. 继承Thread类创建线程类: ```java class MyThread extends Thread { public void run() { // 线程执行的代码 } } // 创建线程对象并启动线程 MyThread thread = new MyThread(); thread.start(); ``` 优点:简单易用,可以直接重写Thread类的run()方法。 缺点:由于Java不支持多继承,继承了Thread类就无法再继承其他类。 2. 实现Runnable接口创建线程类: ```java class MyRunnable implements Runnable { public void run() { // 线程执行的代码 } } // 创建线程对象并启动线程 Thread thread = new Thread(new MyRunnable()); thread.start(); ``` 优点:避免了单继承的限制,可以继续继承其他类或实现其他接口。 缺点:需要额外创建Thread对象,并将Runnable对象作为参数传递给Thread对象。 3. 实现Callable接口创建线程类: ```java class MyCallable implements Callable<Integer> { public Integer call() throws Exception { // 线程执行的代码 return 0; } } // 创建线程池对象 ExecutorService executor = Executors.newFixedThreadPool(1); // 提交Callable任务并获取Future对象 Future<Integer> future = executor.submit(new MyCallable()); // 获取线程执行结果 int result = future.get(); ``` 优点:可以获取线程执行的结果,并且可以抛出异常。 缺点:相对于前两种方式,使用Callable需要更多的代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值