16.多线程

1.概述

    进程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。

    线程:线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。

    注意:一个进程至少有一个线程。

2.好处与弊端

    优点:

(1).使程序的相应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态

(2).当前没有进行处理的任务可以将处理器时间给其它任务

(3).占用大量处理时间的任务可以定期将处理器时间给其它任务

(4).可以随时停止任务

(5).可以分别设置各个任务的优先级和优化性能

    缺点:

(1).线程也是程序,所以线程需要占用内存,线程越多内存占用越多

(2).多线程需要协和管理,所以需要CPU时间跟踪线程

(3).线程之间对共享资源 的访问会互相影响,必须解决竞用共享资源的问题

(4).线程太多会导致控制太复杂,最终可能造成很多bug

3.JVM中多线程的解析

    JVM启动时就启动了多个线程,至少有两个线程可分析出来:

(1).执行main函数的线程,该线程的任务代码都在main函数中 (2).负责垃圾回收的线程

代码示例:

class Demo{
	public void finalize(){
		System.out.println("demo ok");
	}
}
public class ThreadDemo {

	public static void main(String[] args) {
		
		new Demo();
		new Demo();
		System.gc();
		System.out.println("hello world");
	}

}

图解:


4.多线程创建的方式一:继承Thread类

代码示例:

/*
 * 如何创建一个线程?
 * 	方式一:继承Thread类
 * 		步骤:
 * 			1.定义一个类继承Thread类,
 * 			2.覆盖Thread类中的run方法,
 * 			3.直接创建Thread的子类对象创建线程,
 * 			4.调用start方法开启线程应调用线程的任务run方法执行
 * 
 */
class Demo1  extends Thread{
	private String name;

	Demo1(String name){
		this.name=name;
	}
	
	public void run(){
		show();
	}
	public void show(){
		for(int x=0;x<10;x++){
			System.out.println(name+"....x="+x+"...name="+Thread.currentThread().getName());
		}
	}
}
public class ThreadDemo {

	public static void main(String[] args) {
		
		Demo1 d1 = new Demo1("xiaoqiang");
		Demo1 d2 = new Demo1("小明");
		d1.start();
		d2.start();
		
	}

}

5.线程的状态

图解:



6.创建线程的方法二:实现Runnable接口

代码示例:

/*
 * 方式二:
 *  1.定义实现Runnable接口 
 *  2.覆盖接口中的run方法,将线程的任务代码封装到run方法中
 *  3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递 4.调用线程对象的start方法开启线程
 * 
 * 好处: 
 * 	1.将线程的任务从线程的子类中分离出来,进行单独的封装,按照面向对象的思想将任务封装成对象 
 * 	2.避免java单继承的局限性
 * 
 */
class Demo1 implements Runnable {
	private String name;

	public void run() {
		show();
	}

	public void show() {
		for (int x = 0; x < 10; x++) {
			System.out.println(Thread.currentThread().getName() + "..." + x);
		}
	}
}

public class ThreadDemo {

	public static void main(String[] args) {

		Demo1 d = new Demo1();

		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);

		t1.start();
		t2.start();
	}

}

7.同步代码块

            a.概述:将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码时,其它线程不可以参与运算,必须要当前线程把这些代码都执行完毕后,其它线程才可以参与运算。在java中,用同步代码块解决这个问题。

              b.线程安全问题:当一个线程在执行操作共享数据的多条代码时,其它线程参与了运算,就会导致线程安全问题。

              c.格式:  synchronized(对象) { 需要被同步的代码; }

              d.好处和弊端:好处是,解决了线程的安全问题。弊端是相对降低了效率,因为同步外的线程都会判断同步锁。

              e:同步的前提:必须有多个线程并使用同一个锁

代码示例:

class Ticket implements Runnable {
	private int num = 100;
	Object obj = new Object();

	public void run() {
		show();
	}

	public void show() {
		while (true) {
			synchronized (obj) {//同步代码块
				if (num > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {

					}
					System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
				}
			}

		}
	}
}

public class TicketDemo {

	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		Thread t4 = new Thread(ticket);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

8.同步函数

 代码示例:

class Bank {
	private int sum;
	private Object obj = new Object();

	public synchronized void add(int num) {//同步函数

//		synchronized (obj) {
			sum += num;
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {

			}
			System.out.println("sum=" + sum);
//		}

	}
}

class Cus implements Runnable {
	private Bank b = new Bank();

	public void run() {

		for (int x = 0; x < 3; x++) {
			b.add(100);
		}
	}
}

public class BankDemo {

	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}

}

9.单例模式涉及的多线程问题

代码示例:

//饿汉式
class Single {
	private static final Single s = new Single();//先加载

	Single() {}
	public static Single getInstance(){
		return s;
	}
	
}

// 懒汉式
class Sinlge {
	private static Single s = null;//后加载

	private void Single() {
	}

	public static Single getInstance() {
		if (s == null) {//解决效率问题
			synchronized (Single.class) {//解决线程安全问题
				if(s==null)
					s = new Single();
			}
		}

}

10.死锁

          两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,他们都将无法推进下去。

代码示例:

class Ticket3 implements Runnable{
	private int num = 100;
	Object obj = new Object();
	boolean flag = true;
	
	@Override
	public void run() {
		if(flag)
			while(true){
				synchronized (obj) {
					show();//同步代码块中写入同步函数
					
				}
			}
		else
			while(true)
				this.show();
	}
	public synchronized void show(){
		synchronized (obj) {
			if(num>0){//同步函数中写入同步代码块
				try{Thread.sleep(10);}catch(InterruptedException e){}
				System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
			}
		}
		
	}
}

public class DeadLockDemo{

	public static void main(String[] args) {
		Ticket3 t = new Ticket3();
		
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t1.start();
		try{Thread.sleep(10);}catch(InterruptedException e){}
		t.flag = false;
		t2.start();
	}

}

11.线程间通信

         a.概述: 多个线程处理同一资源,但任务不同。

代码示例:

//资源
class Resource {
	String name;
	String sex;
	
}

// 输入
class Input1 implements Runnable {
	Resource r;

	Input1(Resource r) {
		this.r = r;
	}

	public void run() {
		int x = 0;

		while (true) {
			synchronized (r) {
				if (x == 0) {
					r.name = "mike";
					r.sex = "nan";
				} else {
					r.name = "娜娜";
					r.sex = "女";
				}
				x = (x + 1) % 2;
			}

		}

	}

}

// 输出
class Output1 implements Runnable {
	Resource r = new Resource();

	Output1(Resource r) {
		this.r = r;
	}

	public void run() {

		while (true) {
			synchronized (r) {
				System.out.println(r.name + "..." + r.sex);
			}

		}

	}
}

public class ResourceDemo {

	public static void main(String[] args) {
		// 创建资源
		Resource r = new Resource();
		// 创建任务
		Input1 in = new Input1(r);
		Output1 out = new Output1(r);
		// 创建线程,执行路径
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		// 开启线程
		t1.start();
		t2.start();
	}

}

           b.等待唤醒机制

图例:


代码示例:

/*
 * 等待/唤醒机制
 * 涉及的方法:
 * 	1.wait(); 让线程处于冻结状态,被wait的线程会被存储到线程池中
 *      2.notify();唤醒线程池中的一个线程(任意)
 *      3.notifyAll();唤醒线程池中的所有线程
 *  
 *  这些方法都必须定义在同步中
 *  因为这些方法是用于操作线程状态的方法
 *  必须要明确到底操作的是哪个锁上的线程
 *  
 *  为什么操作线程的方法 wait notify notifyAll 定义在Object类中?
 *  	因为这些方法是监视器的方法,监视器其实就是锁
 *  	锁可以是任意的对象,任意的对象调用方式一定定义在Object类中
 *  
 */

class Resource {
	String name;
	String sex;
	boolean flag = false;
}

// 输入
class Input implements Runnable {
	Resource r;

	Input(Resource r) {
		this.r = r;
	}

	public void run() {
		int x = 0;

		while (true) {
			synchronized (r) {
				if (r.flag)
					try {
						r.wait();
					} catch (InterruptedException e) {

					}
				if (x == 0) {
					r.name = "mike";
					r.sex = "nan";
				} else {
					r.name = "娜娜";
					r.sex = "女";
				}
				r.flag = true;
				r.notify();
				x = (x + 1) % 2;
			}

		}

	}

}

// 输出
class Output implements Runnable {
	Resource r = new Resource();

	Output(Resource r) {
		this.r = r;
	}

	public void run() {

		while (true) {
			synchronized (r) {
				if (!r.flag)
					try {
						r.wait();
					} catch (InterruptedException e) {

					}
				System.out.println(r.name + "..." + r.sex);
				r.flag = false;
				r.notify();
			}

		}

	}
}

public class ResourceDemo2 {

	public static void main(String[] args) {
		Resource r = new Resource();
		Input in = new Input(r);
		Output out = new Output(r);

		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);

		t1.start();
		t2.start();

	}

}

优化代码:

/*
 * 等待/唤醒机制 代码优化
 */
class Resource {
	private String name;
	private String sex;
	private boolean flag = false;

	public synchronized void set(String name, String sex) {
		if (flag)
			try {
				this.wait();
			} catch (InterruptedException e) {
			}
		this.name = name;
		this.sex = sex;
		flag = true;
		notify();
	}

	public synchronized void out() {
		if (!flag)
			try {
				this.wait();
			} catch (InterruptedException e) {
			}
		System.out.println(name + "...+..." + sex);
		flag = false;
		notify();
	}
}

// 输入
class Input implements Runnable {
	Resource r;

	Input(Resource r) {
		this.r = r;
	}

	public void run() {
		int x = 0;
		while (true) {
			if (x == 0) {
				r.set("mike", "nan");
			} else {
				r.set("丽丽", "女");
			}
			x = (x + 1) % 2;
		}

	}
}

// 输出
class Output implements Runnable {
	Resource r = new Resource();

	Output(Resource r) {
		this.r = r;
	}

	public void run() {

		while (true) {

			r.out();

		}
	}

}

public class ResourceDemo {

	public static void main(String[] args) {
		Resource r = new Resource();
		Input in = new Input(r);
		Output out = new Output(r);

		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);

		t1.start();
		t2.start();

	}

}       

          c.多生产多消费问题

代码示例:

/*
 * 多生产者多消费者问题解决办法
 * if判断标记只有一次,会导致不该运行的线程运行,出现数据错误的情况
 * while判断标记,解决线程获取执行权后,是否要运行
 * 
 * notify:只能唤醒一个线程,如果本方唤醒本方,没有意义,而且while判断标记+notify会导致死锁
 * notifyAll:解决本方线程一定会唤醒对方线程
 */
class Resource {
	private String name;
	private int count = 1;
	private boolean flag = false;

	public synchronized void set(String name) {
		while (flag)
			try {
				this.wait();
			} catch (InterruptedException e) {
			}

		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);
		flag = true;
		notifyAll();//唤醒所有线程
	}

	public synchronized void out() {
		while (!flag)
			try {
				this.wait();
			} catch (InterruptedException e) {
			}
		System.out.println(Thread.currentThread().getName() + "........消费者......." + this.name);
		flag = false;
		notifyAll();//唤醒所有线程
	}
}

class Producer implements Runnable {
	private Resource r;

	Producer(Resource r) {
		this.r = r;
	}

	public void run() {
		while (true) {
			r.set("烤鸭");
		}
	}
}

class Consumer implements Runnable {
	private Resource r;

	Consumer(Resource r) {
		this.r = r;

	}

	public void run() {
		while (true) {
			r.out();
		}
	}
}

public class ProducerConsumerDemo {

	public static void main(String[] args) {
		// 创建资源
		Resource r = new Resource();
		// 创建任务
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		// 创建线程
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		// 开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();

	}

}

12.wait和sleep的区别

          (1).wait可以指定时间也可以不指定,sleep必须指定时间

            (2).在同步中,对cpu的执行权和锁的处理不同--wait释放执行权释放锁,sleep释放执行权,不释放锁

13.停止线程的方式

代码示例:

/*
 * 停止线程
 * 	1.stop方法
 * 	2.run方法结束
 * 怎么控制线程的任务结束呢?
 * 任务中都会有循环结构,只要控制住循环就可以结束任务
 * 控制循环通常就用定义标记来完成 flag = true;
 * 
 * 但是如果线程处于冻结状态,无法读取标记,如何结束呢
 * 可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格
 * 但是强制动作会发生InterruptedException,要处理
 * 
 */
class StopThread implements Runnable {
	private boolean flag = true;// 定义结束标记

	public synchronized void run() {

		while (flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				System.out.println(Thread.currentThread().getName() + "...." + e);
				flag = false;
			}
			System.out.println(Thread.currentThread().getName() + "......+++++");
		}
	}

	public void setFlag() {// 对外提供改变标记的方式
		flag = false;
	}
}

public class StopThreadDemo {

	public static void main(String[] args) {
		StopThread st = new StopThread();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);

		t1.start();
		t2.start();

		int num = 0;
		for (;;) {
			if (++num == 50) {
				t1.interrupt();// 强制唤醒
				t2.interrupt();
				// st.setFlag();将标记重置为false
				break;
			}
			System.out.println("main..." + num);
		}
		System.out.println("over");
	}

}

14.守护线程

          a.概述:Java中有两类线程--用户线程(User Thread),守护线程(Daemon Thread),守护线程(Daemon)是运行在后台的一种特殊线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

          b.特点:只有所有非守护线程执行完毕后,才会执行守护线程,否则不会执行。

          c.举例:JVM中的垃圾回收器GC

15.其他方法- join等

          概述:join方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。

代码示例:

class Demo3 implements Runnable{
	public void run(){
		for(int x=0;x<50;x++){
			System.out.println(Thread.currentThread().getName()+"..."+x);
		}
	}
}
public class JoinDemo {

	public static void main(String[] args) throws Exception {
		Demo3 d = new Demo3();
		
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		
		t1.start();
		t1.join();//t1线程要申请加入进来运行,主线程释放执行权和执行资格
		t2.start();
		
		for(int x=0;x<50;x++){
			System.out.println(Thread.currentThread().getName()+"..."+x);
		}
	}

}

### 多线程的基本使用方法 多线程是一种并发执行多个任务的技术,广泛应用于需要提高程序性能和响应能力的场景中。Java 提供了多种方式来实现多线程,以下是几种基本的使用方法。 #### 1. 继承 `Thread` 类 通过继承 `Thread` 类并重写其 `run()` 方法,可以创建一个线程。然后调用该线程对象的 `start()` 方法启动线程。 ```java public class MyThread extends Thread { @Override public void run() { // 线程执行的任务 System.out.println("线程正在运行"); } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程 } } ``` 这种方法简单直观,但因为 Java 不支持多重继承,所以如果类已经继承了其他类,则无法再继承 `Thread` 类[^3]。 #### 2. 实现 `Runnable` 接口 另一种常见的方式是实现 `Runnable` 接口,并将其实例作为参数传递给 `Thread` 构造函数。这种方式更灵活,因为它允许类继续继承其他类。 ```java public class MyRunnable implements Runnable { @Override public void run() { // 线程执行的任务 System.out.println("线程正在运行"); } public static void main(String[] args) { MyRunnable task = new MyRunnable(); Thread thread = new Thread(task, "MyThread"); thread.start(); // 启动线程 } } ``` 这种方式适用于需要共享资源或任务的情况,因为多个线程可以共享同一个 `Runnable` 实例。 #### 3. 使用 `Callable` 和 `Future` 与 `Runnable` 类似,`Callable` 接口也用于定义线程任务,但它可以返回结果并抛出异常。通常结合 `ExecutorService` 使用,通过 `submit()` 方法提交任务,并获取 `Future` 对象以获取任务结果。 ```java import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MyCallable implements Callable<String> { @Override public String call() throws Exception { // 线程执行的任务 return "任务完成"; } public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); MyCallable task = new MyCallable(); Future<String> future = executor.submit(task); try { String result = future.get(); // 获取任务结果 System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } finally { executor.shutdown(); // 关闭线程池 } } } ``` 这种方式适合需要返回值的任务,并且能够更好地管理线程生命周期[^1]。 #### 4. 使用线程池 线程池是一种管理和复用线程的机制,它可以减少创建和销毁线程的开销。Java 提供了 `ExecutorService` 接口以及 `Executors` 工厂类来简化线程池的创建和管理。 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); // 创建固定大小的线程池 for (int i = 0; i < 10; i++) { Runnable worker = new WorkerThread("" + i); executor.execute(worker); // 提交任务到线程池 } executor.shutdown(); // 关闭线程池 } } class WorkerThread implements Runnable { private String command; public WorkerThread(String s) { this.command = s; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 开始执行命令:" + command); processCommand(); System.out.println(Thread.currentThread().getName() + " 执行结束"); } private void processCommand() { try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 线程池特别适用于需要频繁创建和销毁线程的场景,例如处理大量请求或批量数据处理。 #### 5. 守护线程 守护线程(Daemon Thread)是一种后台线程,它的存在不影响 JVM 的退出。当所有非守护线程都结束时,JVM 会自动退出。可以通过设置 `setDaemon(true)` 来创建守护线程。 ```java public class DaemonThreadExample { public static void main(String[] args) { Thread daemonThread = new Thread(() -> { while (true) { try { Thread.sleep(1000); System.out.println("守护线程正在运行"); } catch (InterruptedException e) { e.printStackTrace(); } } }); daemonThread.setDaemon(true); // 设置为守护线程 daemonThread.start(); // 启动守护线程 } } ``` 守护线程常用于执行一些不需要长时间存在的后台任务,例如监控、日志记录等。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值