多线程教程、学习笔记

一、线程与进程的区别
把操作系统的多个任务称为进程(Process),而程序中的多任务则称为线程。
一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
二、程序运行原理
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
三、创建线程
    方式一: 继承Thread
① 自定义一个类继承Thread.
② 重写Thread的run方法,把自定义线程的任务代码放在run方法上。
③ 创建Thread类的子类对象,并且调用start方法开启线程。
public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName()+":正在执行!"+i);
		}
	}

	public static void main(String[] args) {
		new MyThread().start();
		for (int i = 0; i < 100; i++) {
			System.out.println("main线程:"+i);
		}
	}
}
    方式二:实现Runnable接口
① 自定义一个类实现Runnable接口.
② 实现Runnable的run方法。把自定义线程的任务代码放在run方法上。
③ 创建Runnable实现类的对象。
④ 创建Thread的对象,然后把Runnable实现类的对象作为参数传递。
⑤ 调用Thread对象的start方法开启线程。
public class Demo02 {
	public static void main(String[] args) {
		//创建线程执行目标类对象
		Runnable runn = new MyRunnable();
		//将Runnable接口的子类对象作为参数传递给Thread类的构造函数
		Thread thread = new Thread(runn);
		Thread thread2 = new Thread(runn);
		//开启线程
		thread.start();
		thread2.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程:正在执行!"+i);
		}
	}
}

class MyRunnable implements Runnable{


	//定义线程要执行的run方法逻辑
	@Override
	public void run() {

		for (int i = 0; i < 10; i++) {
			System.out.println("我的线程:正在执行!"+i);
		}
	}
}
总结:实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。
    方式三:通过Callable和Future创建线程
①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
②创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
③使用FutureTask对象作为Thread对象的target创建并启动新线程。
④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableThreadTest implements Callable<Integer> {
	public static void main(String[] args) {
		CallableThreadTest ctt = new CallableThreadTest();
		FutureTask<Integer> ft = new FutureTask<>(ctt);
		for(int i = 0;i < 30;i++) {
			System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
			if(i==20) {
				new Thread(ft,"有返回值的线程").start();
			}
		}
		try {
			System.out.println("子线程的返回值:"+ft.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}

	@Override
	public Integer call() throws Exception {
		int i = 0;
		for(;i<30;i++) {
			System.out.println("call:"+Thread.currentThread().getName()+" "+i);
		}
		return i;
	}
}	
四、线程匿名内部类使用
方式1:创建线程对象时,直接重写Thread类中的run方法
	new Thread() {
		public void run() {
			for (int x = 0; x < 40; x++) {
				System.out.println(Thread.currentThread().getName()
						+ "...X...." + x);
			}
		}
	}.start();
方式2:使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法
	public static void main(String[] args) {
		Runnable r = new Runnable() {
			public void run() {
				for (int x = 0; x < 40; x++) {
					System.out.println(Thread.currentThread().getName()
							+ "...Y...." + x);
				}
			}
		};
		new Thread(r).start();
	}
五、常用方法
Thread(String name)        初始化线程的名字
setName(String name)    设置线程对象名
getName()             返回线程的名字
sleep()                        静态的方法, 那个线程执行了sleep方法代码那么就是那个线程睡眠。
currentThread()         返回当前的线程对象,静态方法,哪个线程执行了currentThread()代码就返回哪个线程的对象
getPriority()               返回当前线程对象的优先级,默认5
setPriority(int newPriority) 设置线程的优先级  
currentThread() 获取当前线程对象
run() 该方法用于线程的执行。你需重载该方法。
start() 该方法使得线程启动run()方法。
六、线程池
1.概述:就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
2.使用线程池中线程对象的步骤:
创建线程池对象
创建Runnable接口子类对象
提交Runnable接口子类对象
关闭线程池
3.使用线程池方式示例--Runnable接口:
 private static final Executor executor = Executors.newFixedThreadPool(10);
 executor.execute(new Runnable() {
@Override
public void run() {
try {
(运行的代码)
} catch (Exception e) {
e.printStackTrace();
}finally {}
}
}
4.使用线程池方式示例—Callable接口:(有返回值)
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("我要一个教练:call");
return "返回了";
}
});
System.out.println(future.get());
}
七、线程安全
线程安全问题是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
public class ThreadDemo {
	public static void main(String[] args) {
		//创建票对象
		Ticket ticket = new Ticket();
		
		//创建3个窗口
		Thread t1  = new Thread(ticket, "窗口1");
		Thread t2  = new Thread(ticket, "窗口2");
		Thread t3  = new Thread(ticket, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
public class Ticket implements Runnable {
	//共100票
	int ticket = 100;


	@Override
	public void run() {
		//模拟卖票
		while(true){
			if (ticket > 0) {
				//模拟选坐的操作
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
			}
		}
	}
}

八、线程同步的两种方式
1.同步代码块
同步代码块的格式:
synchronized(锁对象){
可能会产生线程安全问题的代码
}
使用同步代码块,对电影院卖票案例中Ticket类进行如下代码修改:
public class Ticket implements Runnable {
//共100票
int ticket = 100;
//定义锁对象
Object lock = new Object();
@Override
public void run() {
//模拟卖票
while(true){
//同步代码块
synchronized (lock){
if (ticket > 0) {
//模拟电影选坐的操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
}
}
}
}
注意的细节:
①锁对象可以是任意的对象。
② 锁对象必须是多线程共享的资源,否则锁不住。
③ 没有线程安全问题的时候不要使用锁,因为会导致效率降低。
④ 调用sleep方法并不会释放锁对象,但是调用wait方法的线程就会释放锁对象。
2.同步方法
同步方法的格式:
修饰符 synchronized 返回值类型 函数名(形参列表..){
可能会产生线程安全问题的代码
}
静态同步方法的格式: 
public static synchronized void method(){
可能会产生线程安全问题的代码
}
使用同步方法,对电影院卖票案例中Ticket类进行如下代码修改:
public class Ticket implements Runnable {
//共100票
int ticket = 100;
//定义锁对象
Object lock = new Object();
@Override
public void run() {
//模拟卖票
while(true){
//同步方法
method();
}
}


//同步方法,锁对象this
public synchronized void method(){
if (ticket > 0) {
//模拟选坐的操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
}
}


注意的细节: 
① 同步函数的锁对象是不能任意的,非静态同步函数的锁对象是this对象,静态函数的锁对象是当前字节码对象。
② 同步函数的锁不能由你指定,是固定的。
九、死锁
当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
示例:见文档
十、Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的常用方法
locl() 获取锁
unlock() 释放锁
使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对电影院卖票案例中Ticket类进行如下代码修改:
见文档
十一、线程优先级
线程的优先级从低到高以整数1~10表示,共分为10级,默认优先级为5。
setPriority()方法修改
十二、线程调度
public static void sleep(long millis) throws InterruptedException
使当前运行的线程休眠millis指定的毫秒数。
public static void yield()
当前正在运行的线程让出CPU,允许其它线程运行。
public final void wait() throws InterruptedException
使当前线程处于等待状态,直到其它线程唤醒它。
public final void notify()
唤醒一个正在等待的线程。
public final void notifyAll()
唤醒所有正在等待的线程。


模拟等待唤醒机制的实现:
见文档
十三、线程的状态
①new(新建):一个线程在调用new()方法之后,调用start()方法之前所处的状态。
②runnable(就绪):一旦线程调用了start()方法,线程就转到Runnable()状态。注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还存在优先级和调度问题。
③running(运行状态):线程正在占用CPU时间
④blocked(阻塞/挂起状态):线程处于阻塞状态。这是由两种可能性造成的:因挂起而暂停;由于某些原因而阻塞,例如等待IO请求的完成等。
⑤dead(终止状态):线程转到退出状态。这有两种可能性:run()方法执行结束;调用了stop()方法。

十四、线程间的控制,等待唤醒机制
在进行线程同步时,可以使用wait() /notify()方法进行线程的协同。
notifyAll()方法唤醒所有因一个同步对象调用
wait()阻塞的所有线程。
十五、线程之间的通信
PipedInputStream
代表了线程从管道读数据的一端;
PipedOutputStream
代表了线程向管道写数据的一端
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值