多线程


一、概述


多线程就是一个应用程序有多条执行路径


1.进程与线程

1)进程:

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

2)线程:

是进程中的单个顺序控制流,是一条执行路径。

一个进程如果只有一条执行路径,则称为单线程程序。

一个进程如果有多条执行路径,则称为多线程程序。


2.多进程和多线程

1)意义:

单线程:一个应用程序只有一条执行路径

多线程:一个应用程序有多条执行路径


2)作用

多进程:提高CPU的使用率

多线程:提高应用程序的使用率


3.Java程序的运行原理

1)Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。

2)JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。


4.线程Thread

构造方法:

public Thread()

public Thread(Runnable target)

public Thread(Runnable target, String name)


二、Thread详解

1.多线程的实现方案

1)继承Thread类

A:自定义类MyThread继承Thread类。

B:MyThread类里面重写run()

C::创建对象

D:启动线程:调用start();方法

run():仅仅是封装被线程执行的代码,直接调用是普通方法

start():首先启动了线程,然后再由jvm去调用该线程的run()方法。


2)实现Runnable接口

A:自定义类MyRunnable实现Runnable接口

B:重写run()方法

C:创建MyRunnable类的对象

D:创建Thread类的对象,并把C步骤的对象作为构造参数传递


3)案例

继承Thread类:

class MyThread extends Thread {

	@Override
	public void run() {
		// 自己写代码
		for (int x = 0; x < 200; x++) {
			System.out.println(x);
		}
	}

}

public class MyThreadDemo {
	public static void main(String[] args) {
		// 创建线程对象
		// MyThread my = new MyThread();
		// // 启动线程
		// my.run();
		// my.run();
		// 调用run()方法为什么是单线程的呢?
		// 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
		// 要想看到多线程的效果,就必须说说另一个方法:start()
		// 面试题:run()和start()的区别?
		// run():仅仅是封装被线程执行的代码,直接调用是普通方法
		// start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
		// MyThread my = new MyThread();
		// my.start();
		// // IllegalThreadStateException:非法的线程状态异常
		// // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
		// my.start();

		// 创建两个线程对象
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();

		my1.start();
		my2.start();
	}
}


实现Runnable接口:

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}

}

public class MyRunnableDemo {
	public static void main(String[] args) {
		// 创建MyRunnable类的对象
		MyRunnable my = new MyRunnable();

		// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
		// Thread(Runnable target)
		// Thread t1 = new Thread(my);
		// Thread t2 = new Thread(my);
		// t1.setName("林青霞");
		// t2.setName("刘意");

		// Thread(Runnable target, String name)
		Thread t1 = new Thread(my, "林青霞");
		Thread t2 = new Thread(my, "刘意");

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


2.线程的调度和优先级问题

1)线程的调度

A:分时调度:每个线程都被单独的分配一段时间进行执行

B:抢占式调度:线程随机的获取时间片段进行执行,Java采用的是该调度方式

2)获取和设置线程优先级

A:获取

public final int getPriority():返回线程对象的优先级

默认是5

B:设置

public final void setPriority(int newPriority):更改线程的优先级。 

范围是1-10


3.线程的控制(常见方法)

1)休眠线程

public static void sleep(long millis): 线程休眠millis毫秒

2)加入线程

public final void join():等待该线程终止。

3)礼让线程

public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 

让多个线程的执行更和谐,但是不能靠它保证一人一次。

4)后台线程

public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。

当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 

5)终止线程

public final void stop():让线程停止,过时了,但是还可以使用。

public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。


4.线程的生命周期

1)新建——2)就绪——3)运行——4)阻塞——5)死亡



5.运行问题

多线程共享资源时会发生问题。

public class SellTicket implements Runnable {
	// 定义100张票
	private int tickets = 100;

	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "张票");
			}
		}
	}
}

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}
程序会产生小于0的票数等问题。


三、多线程安全问题


1.产生的原因

也是我们以后判断一个程序是否有线程安全问题的依据

1)是否有多线程环境

2)是否有共享数据

3)是否有多条语句操作共享数据

1)2)的问题改变不了,所以只能想办法去把3)改变一下


2.同步解决线程安全问题


把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。

问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。


1)同步代码块

synchronized(对象) {

需要被同步的代码;

}

这里的锁对象可以是任意对象。


2)同步方法

把同步加在方法上。

这里的锁对象是this


3)静态同步方法

把同步加在方法上。

这里的锁对象是当前类的字节码文件对象


4)注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
多个线程必须是同一把锁。


3.线程安全的一些类

StringBuffer,Vector,Hashtable

查看原码之后,发现这些类都加了线程锁

把一个线程不安全的集合类变成一个线程安全的集合类的方法:

用Collections工具类的方法:public static <T> List<T> synchronizedList(List<T> list)


4.同步的弊端:

1)效率低,线程经常处于等待状态
2)容易产生死锁


5.解决了线程同步问题的案例

public class SellTicket implements Runnable {

	// 定义100张票
	private static int tickets = 100;

	// 定义同一把锁
	private Object obj = new Object();
	private Demo d = new Demo();

	private int x = 0;
	
	//同步代码块用obj做锁
//	@Override
//	public void run() {
//		while (true) {
//			synchronized (obj) {
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()
//							+ "正在出售第" + (tickets--) + "张票 ");
//				}
//			}
//		}
//	}
	
	//同步代码块用任意对象做锁
//	@Override
//	public void run() {
//		while (true) {
//			synchronized (d) {
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()
//							+ "正在出售第" + (tickets--) + "张票 ");
//				}
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			if(x%2==0){
				synchronized (SellTicket.class) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票 ");
					}
				}
			}else {
//				synchronized (d) {
//					if (tickets > 0) {
//						try {
//							Thread.sleep(100);
//						} catch (InterruptedException e) {
//							e.printStackTrace();
//						}
//						System.out.println(Thread.currentThread().getName()
//								+ "正在出售第" + (tickets--) + "张票 ");
//					}
//				}
				
				sellTicket();
				
			}
			x++;
		}
	}

//	private void sellTicket() {
//		synchronized (d) {
//			if (tickets > 0) {
//			try {
//					Thread.sleep(100);
//			} catch (InterruptedException e) {
//					e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName()
//						+ "正在出售第" + (tickets--) + "张票 ");
//			}
//		}
//	}
	
	//如果一个方法一进去就看到了代码被同步了,那么我就再想能不能把这个同步加在方法上呢?
//	 private synchronized void sellTicket() {
//			if (tickets > 0) {
//			try {
//					Thread.sleep(100);
//			} catch (InterruptedException e) {
//					e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName()
//						+ "正在出售第" + (tickets--) + "张票 ");
//			}
//	}
	
	private static synchronized void sellTicket() {
		if (tickets > 0) {
		try {
				Thread.sleep(100);
		} catch (InterruptedException e) {
				e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()
					+ "正在出售第" + (tickets--) + "张票 ");
		}
}
}

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}


四、死锁问题


1.死锁问题的描述和代码体现

两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。

public class MyLock {
	// 创建两把锁对象
	public static final Object objA = new Object();
	public static final Object objB = new Object();
}

public class DieLock extends Thread {

	private boolean flag;

	public DieLock(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			synchronized (MyLock.objA) {
				System.out.println("if objA");
				synchronized (MyLock.objB) {
					System.out.println("if objB");
				}
			}
		} else {
			synchronized (MyLock.objB) {
				System.out.println("else objB");
				synchronized (MyLock.objA) {
					System.out.println("else objA");
				}
			}
		}
	}
}
public class DieLockDemo {
	public static void main(String[] args) {
		DieLock dl1 = new DieLock(true);
		DieLock dl2 = new DieLock(false);

		dl1.start();
		dl2.start();
	}
}


2.等待唤醒:
Object类中提供了三个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程



3.生产者和消费者多线程体现(线程间通信问题)

以学生作为资源来实现

资源类:Student

设置数据类:SetThread(生产者)

获取数据类:GetThread(消费者)

测试类:StudentDemo


wait()

notify()

notifyAll() (多生产多消费)

class Student {
	String name;
	int age;
	boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}

class SetThread implements Runnable {

	private Student s;
	private int x = 0;

	public SetThread(Student s) {
		this.s = s;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (s) {
				//判断有没有
				if(s.flag){
					try {
						s.wait(); //t1等着,释放锁
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				if (x % 2 == 0) {
					s.name = "林青霞";
					s.age = 27;
				} else {
					s.name = "刘意";
					s.age = 30;
				}
				x++; //x=1
				
				//修改标记
				s.flag = true;
				//唤醒线程
				s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
			}
			//t1有,或者t2有
		}
	}
}

class GetThread implements Runnable {
	private Student s;

	public GetThread(Student s) {
		this.s = s;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (s) {
				if(!s.flag){
					try {
						s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				System.out.println(s.name + "---" + s.age);
				//林青霞---27
				//刘意---30
				
				//修改标记
				s.flag = false;
				//唤醒线程
				s.notify(); //唤醒t1
			}
		}
	}
}

public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student();
		
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);

		//线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);

		//启动线程
		t1.start();
		t2.start();
	}
}


4.线程组

把多个线程组合到一起。

它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

具体实现代码如下


public class ThreadGroupDemo {
	public static void main(String[] args) {
		// method1();

		// 我们如何修改线程所在的组呢?
		// 创建一个线程组
		// 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
		method2();

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

	private static void method2() {
		// ThreadGroup(String name)
		ThreadGroup tg = new ThreadGroup("这是一个新的组");

		MyRunnable my = new MyRunnable();
		// Thread(ThreadGroup group, Runnable target, String name)
		Thread t1 = new Thread(tg, my, "林青霞");
		Thread t2 = new Thread(tg, my, "刘意");
		
		System.out.println(t1.getThreadGroup().getName());
		System.out.println(t2.getThreadGroup().getName());
		
		//通过组名称设置后台线程,表示该组的线程都是后台线程
		tg.setDaemon(true);
	}

	private static void method1() {
		MyRunnable my = new MyRunnable();
		Thread t1 = new Thread(my, "林青霞");
		Thread t2 = new Thread(my, "刘意");
		// 我不知道他们属于那个线程组,我想知道,怎么办
		// 线程类里面的方法:public final ThreadGroup getThreadGroup()
		ThreadGroup tg1 = t1.getThreadGroup();
		ThreadGroup tg2 = t2.getThreadGroup();
		// 线程组里面的方法:public final String getName()
		String name1 = tg1.getName();
		String name2 = tg2.getName();
		System.out.println(name1);
		System.out.println(name2);
		// 通过结果我们知道了:线程默认情况下属于main线程组
		// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
		System.out.println(Thread.currentThread().getThreadGroup().getName());
	}
}


5.线程池(实现多线程的第三种方案

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

1)线程池的实现

A:创建一个线程池对象,控制要创建几个线程对象。

public static ExecutorService newFixedThreadPool(int nThreads)

B:可以执行线程池的线程

可以执行Runnable对象或者Callable对象代表的线程

做一个类实现Runnable接口。

C:调用如下方法即可实现线程的执行

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

D:线程池也可以结束

shutdown()

2)案例

//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
public class MyCallable implements Callable {

	@Override
	public Object call() throws Exception {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
		return null;
	}

}

public class ExecutorsDemo {
	public static void main(String[] args) {
		// 创建一个线程池对象,控制要创建几个线程对象。
		// public static ExecutorService newFixedThreadPool(int nThreads)
		ExecutorService pool = Executors.newFixedThreadPool(2);

		// 可以执行Runnable对象或者Callable对象代表的线程
		pool.submit(new MyCallable());
		pool.submit(new MyCallable());

		//结束线程池
		pool.shutdown();
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值