一、多线程
进程:正在进行中的程序(直译)
线程:就是进程中控制程序执行的一个控制单元(执行路径)。
一个进程中可以有多个执行路径,称为多线程。
多线程的好处:解决了多个部分同时运行的问题。
多线程的缺点:线程太多后效率低下。
JVM运行的时候至少有两个线程:
1.主线程,执行main函数
2.负责垃圾回收
创建新执行线程有两种方法:
1.将类声明为Thread的子类,该子类应重写Thread类的run方法。接下来可以分配并启动该子类的实例。
但是直接在主线程中调用线程类的run方法,并不能执行该线程,只相当于在主线程中new该线程类,仍然处于主线程内。要想创建并启动一个线程,在new该线程类后,用start()方法启动该线程。
如:
class Demo extends Thread { void run() { } } class Zu { public static void main(String [] args) { Demo d=new Demo(); d.start(); } }
线程的四种状态:
有一种特殊的状态:就绪。具备了执行资格,但是还没有获取资源。
CPU的执行资格:可以被CPU处理,在执行队列中排队。
CPU的执行权:正在被CPU处理。
所以,上图的运行状态为具备执行资格,同时具备执行权。冻结状态为不具备执行权,也不具备执行资格。如果具备了执行资格,但是沿未具备执行权,则为就绪状态。
2.实现Runable接口
在Runnable的子类中实现run方法,通过Thread类的创建线程对象,并将Runnable接口的子类对象作为Thread类线程对象构造函数的参数传入,最后调用线程对象的start()启动线程。
如:
class Demo implements Runnable { void run() { } } class Zu { public static void main(String [] args) { Demo d=new Demo(); Thread th=new Thread(d); th.start(); } }
实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行单独的封装。按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。
线程安全问题产生的原因:
1.多个线程在操作共享的数据。
2.操作共享数据的代码有多条
在Java中用同步代码块可以解决上述问题。
synchronized(对象锁)
{
需要同步的代码;
}
上述同步代码块中的对象是任意的,比如Object obj=new Object();中的obj
同步的优点:解决了线程的安全问题
同步的缺点:同步代码中有可能失去CPU执行权而等待,效率比较低。
同步的前提:必须是多个线程使用同一个对象锁。
对于一个函数也可以用同步锁。
public synchronized void add(int num)
{
……
}
同步函数的锁为函数所属的对象本身。
建议使用同步块,少用同步函数。
线程间通讯:
等待唤醒机制:
1.wait(); //让线程处于冻结状态,被wait的线程会被存储到线程池
2.notify(); //唤醒线程池中的一个线程(任意)
3.notifyall(); //唤醒线程池中的所有线程
注意:上述方法必须定义在同步中,因为这些方法都是操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。
为什么操作线程的方法wait、notify、notifyall定义在了Object类中?因为这些方法就是监视器的方法,监视器其实就是锁。锁可以是任意的对象,任意对象的调用方式一定定义在Object类中。
要注意的是 sleep和wait方法都会抛出异常,这些异常不能用throws向上抛,因为Runnable接口中未抛出异常,只能用try来处理异常。
多生产者、多消费者的问题(烤鸭问题):
package test; class Duck { private int num=0; Boolean flag=false; public void set() { synchronized (this) { while (flag) { try { this.wait(); } catch(InterruptedException e) {} } num++; System.out.println("生产…………"+num); flag=true; notifyAll(); } } public void get() { synchronized (this) { while (!flag) { try { this.wait(); } catch(InterruptedException e) {} } System.out.println("消费………………"+num); flag=false; notifyAll(); } } } class Producer implements Runnable{ Duck duck; public Producer(Duck d) { this.duck=d; } @Override public void run() { while(true) { duck.set(); } } } class Consummer implements Runnable{ Duck duck; public Consummer(Duck d) { this.duck=d; } @Override public void run() { while(true) { duck.get(); } } } public class MyThread { public static void main(String[] args) { Duck duck=new Duck(); Thread t0=new Thread(new Producer(duck)); Thread t1=new Thread(new Producer(duck)); Thread t2=new Thread(new Consummer(duck)); Thread t3=new Thread(new Consummer(duck)); t0.start(); t1.start(); t2.start(); t3.start(); } }
if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。
while判断标记,解决了线程获取执行权后,是否要运行!
notify只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
notifyall解决了本方线程一定会唤醒对方线程。
wait和sleep的区别:
1.wait可以有时间参数,也可以没有
sleep一定有时间参数
2.wait释放CPU执行权,同时释放锁
sleep释放CPU执行权,但是不释放锁
在jdk1.5以后,将同步和锁封装成了对象,并将操作锁的方法定义到了该对象中,将隐式动作定义成了显示动作。常用lock接口对象代替同步代码块。
Lock lock=new ReentrantLock(); void show() {
try
{ lock.lock(); ……
}
finally
{ lock.unlock();
} }
Lock替代Synchronized,而Condition替代对象锁。一个Lock,可用newCondition方法生成多个Condition对象,每个Condition对象对应不同的wait,notify,notifyAll。在Condition对象中wait,notify,notifyAll对应为await、signal、signalAll。
例如上例的多生产者、多消费者的问题(烤鸭问题),用Lock接口写,代码如下:
package test; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Duck { private int num=0; Boolean flag=false; Lock lock=new ReentrantLock(); Condition pro_lock=lock.newCondition(); Condition con_lock=lock.newCondition(); public void set() { lock.lock(); while (flag) { try { pro_lock.await(); } catch(InterruptedException e) {} } num++; System.out.println("生产…………"+num); flag=true; con_lock.signal(); lock.unlock(); } public void get() { lock.lock(); while (!flag) { try { con_lock.await(); } catch(InterruptedException e) {} } System.out.println("消费………………"+num); flag=false; pro_lock.signal(); lock.unlock(); } } class Producer implements Runnable{ Duck duck; public Producer(Duck d) { this.duck=d; } @Override public void run() { while(true) { duck.set(); } } } class Consummer implements Runnable{ Duck duck; public Consummer(Duck d) { this.duck=d; } @Override public void run() { while(true) { duck.get(); } } } public class MyThread { public static void main(String[] args) { Duck duck=new Duck(); Thread t0=new Thread(new Producer(duck)); Thread t1=new Thread(new Producer(duck)); Thread t2=new Thread(new Consummer(duck)); Thread t3=new Thread(new Consummer(duck)); t0.start(); t1.start(); t2.start(); t3.start(); } }
停止线程的方法:
1.stop方法
2.run方法
run方法一般都有循环,只要控制循环的次数就可以控制停止线程。
中断线程:interrupt 强制取消线程的冻结状态,让线程具备CPU执行资格。但是强制中断会发生中断异常。
线程对象的daemon方法,可以实现线程的前后台切换,当所有线程均为后台线程时,则程序结束。
线程对象的join方法,可以中止当前线程,执行join方法的线程对象,执行完后,当前线程才能重新获得CPU执行权。
Thread.yield方法可以暂停当前线程,继续执行其他的线程。
二、包(package)
注意点:
1.对类文件进行分类管理
2.给类提供多层命名空间
3.写在程序文件的第一行
4.类名的全称是:包名.类名
5.包也是一种封装形式
6.包在资源管理器中体现为文件夹
对象的权限:
public protected default private
同一个类中 ok ok ok ok
同一个包中 ok ok ok ok
子类中 ok ok
不同包中 ok