1 多线程
1.1 进程和线程
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序多线程:一个进程如果有多条执行路径,则称为多线程程序
1.2 多线程的实现方式
方法一:
(1)定义一个MyThread类继承Thread类
(2)在MyTread类中重写run方法
(3)创建MyThread类对象
(4)调用start方法启动线程
说明:
为什么要重写run方法:
因为run是用来封装被线程执行的代码
run和start方法的区别
run封装线程执行的代码,直接调用,相当于普通方法调用
start启动线程然后由JVM调用此线程的run方法
代码演示:
public class MyThread extends Thread{
@override
public void run(){
}
}
public ………main(){
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.run();
mt1.start();
mt2.start();
}
1.3设置和获取线程名称
方法介绍
void setName(String name)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
Thread currentThread()返回对当前正在执行的线程对象的引用
代码演示
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) { super(name); }
@Override
public void run() { System.out.println(getName();}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.setName("高铁");
my2.setName("飞机");
MyThread my1 = new MyThread("高铁");
MyThread my2 = new MyThread("飞机");
System.out.println(Thread.currentThread().getName());
}}
1.4线程优先级
1.4.1线程调度
(1)两种调度方式:
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片.
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
(2)Java使用的是抢占式调度模型
(3)随机性:
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
1.4.2 线程优先级相关方法
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级,默认5,线程范围 1-10。
1.5线程控制
相关方法
sleep(long millis):使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁
wait():使当前线程暂停执行并释放对象锁标志
notify()唤醒等待的线程
join(): 直观解释就是”等待调用此方法的线程死去,再执行其他线程“。说得更直观一点就是:在线程 t1 内部调用t2.join(), 那么 t1 将会等待 t2 执行完毕后才会继续执行。
Wait和sleep方法不同点 :
1.每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
2.wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
3.sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
4.sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
5.wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
join方法的作用,代码演示
先写一个子线程
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run(){
for(int i=1;i<=2;i++){
System.out.println(this.getName() + ":" + i);
}
}
}
再写主线程测试类
public class TestThread {
public static void main(String[] args) throws InterruptedException
MyThread mt = new MyThread("子线程");
mt.start();
for (int i = 1; i <= 2; i++) {
System.out.println("主线程:" + i);
}
}
}
执行main方法,打印结果很明显,主线程和子线程会交替打印
主线程:1
子线程:1
主线程:2
子线程:2
然后,我们在主线程打印之前,加一句mt.join(),会怎样呢?
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread("子线程");
mt.start();
mt.join();
for (int i = 0; i <= 2; i++) {
System.out.println("主线程:" + i);
}
}
执行main方法,打印结果如下:
子线程:1
子线程:2
主线程:1
主线程:2
————————————————
结论:原来主线程和子线程是并行的关系,但是一旦使用了join()方法,就会变成串行的关系;当主线程调用子线程的join()方法时,意味着必须等子线程执行完毕之后,主线程才会开始执行。
1.6实现多线程方式二:实现Runnable接口
1.6.1 实现步骤
(1)定义一个类MyRunnable实现Runnable接口
(2)在MyRunnable类中重写run()方法
(3)创建MyRunnable类的对象创建Thread类的对象,把MyRunnable对象作为构造方法的参数
(4)启动线程
1.6.2代码演示:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()); }}
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
Thread t1 = new Thread(my);//
Thread t2 = new Thread(my);
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"高铁");
Thread t2 = new Thread(my,"飞机");
//启动线程
t1.start(); t2.start();
}}
1.6.3 多线程的实现方案有两种:
继承Thread类;
实现Runnable接口.
相比继承Thread类,实现Runnable接口的好处:
避免了Java单继承的局限性适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
1.7 线程的生命周期
2 线程同步
2.1 Synchronized 锁
卖票案例
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票。
public class SellTicket implements Runnable{
private int tickets=100;
//三个窗口共用一把锁
private Object obj=new Object();
@override
public void run(){
// 没票了,也一直有人问
while(true){
// Synchronized(new Object)
错误每个线程创建一把锁
synchronized(obj){
if(tickets>0){
try{
Thread.sleep(100);//出票时间
}catch()
System…(Thread.current.getname()+”正在卖第”+tickets+”张票”);
tickets--;
}}}}
Public ….main(){
SellTicket st=new SellTicket();
Thread t1=new Thread();
Thread t2=new Thread();
Thread t3=new Thread();
t1.start(); t2.start(); t3.start();
}
同步代码块格式:synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步的好处和弊端好处:解决了多线程的数据安全问题弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
同步方法:
格式:把synchronized加到方法上
修饰符(static)synchronized返回类型方法名
同步方法的锁对象是this
同步静态方法的锁对象是类名.class
线程安全的类:StringBuffer/Vector/Hashtable
线程不安全的类:StringBuilder/ArrayList/HashMapb
2.2 lock锁
lock锁提供了获取锁和释放锁的方法。
Lock锁提供了比使用synchronized方法可以获得更广泛的锁操作对象
代码演示:
public class SellTicket implements Runnable
{ private int tickets=100;
private Lock lock=new ReenTrantLock();
@override
public void run(){
while(true){
try{
lock.lock();
if(tickets>0){
try{Thread.sleep(100);}catch{}
System……(“ 正卖几张票”);
Tickets--;
}
}finally{lock.unlock();}
}
}
}