1.进程、线程
进程:正在运行的程序
线程:进程中负责程序运行的执行单元
即:进程的范围>线程的范围。
且:一个进程可以有多个线程。
2.多线程的意义:多部分代码同时执行,提高CPU使用效率
3.多线程的特点:CPU的随机性
4.创建线程的三种方法
(1).继承Thread
^1.继承Thread
^2.覆盖run()方法
^3.创建线程对象
^4.调用start()方法开启线程
(2).实现Runnable接口
^1.实现Runnable接口
^2.覆盖run()方法
^3.新建Thread对象,在该对象构造函数时将上述对象作为参数传入
^4.调用start()方法。
(3).实现Callable接口
^1.实现Callable,实现call()方法,有返回值
^2.FutureTask<V>在外部封装一下再获取返回值
^3.新建Thread对象,在该对象构造函数时将上述对象作为参数传入
^4.调用start()方法。
比较:第一种因为是继承,java中的继承都是单继承,有局限性。第二种比较灵活,且耦合也比较低,建议。
5.线程的状态变化关系
线程的状态有五种:被创建,运行,冻结,阻塞,消亡
被创建->运行:start()方法。
运行->消亡:run()方法结束。
运行->冻结:sleep(time),wait()两种方法。
冻结->运行:sleep时间到了,notify()
运行,阻塞,冻结之间的关系:
运行:有CPU执行资格,有CPU执行权
阻塞:有CPU执行资格,无CPU执行权
冻结:无CPU执行资格,无CPU执行权
6.线程安全:当多个线程同时操作同一个数据时,会导致数据错误,造成线程安全问题。
(1)解决方法:(1).同步代码块 (2)同步函数
同步代码块:synchronized(对象){代码};(可用try,catch解决错误)
同步函数:正常的函数上加synchronized。
常规函数的锁:this
静态函数的锁:类名.class(字节码对象)
(2)比较:同步函数一个类只有一个锁,所以一个类一个锁的情况用同步函数。一个类多个锁或者多个类用一个锁的用同步代码块。
(3)加锁为什么能保证线程安全?因为加了锁就用了单例模式。
单例模式分成2种:饿汉式,懒汉式。
(1)饿汉式(先创建对象,然后通过方法返回该对象)
(2)懒汉式(先不创建对象,通过方法返回时再创建对象)
class Single{//饿汉式
private Single single = new Single();
Single(){}
public Single getInstance(){
return single;
}
}
class Single{//懒汉式,双重if判断保证安全
private Single single = null;
Single(){}
public Single getInstance(){
if(single == null){
synchronized(Single.class){
if(single == null){
single = new Single();
}
}
}
return single;
}
}
7.死锁:加锁时需要注意导致一些新的问题,即死锁。
死锁的原因:(1).同步嵌套 (2).生产者消费者的同一方(生产者转到另一个生产者,消费者转到另一个消费者)
(1).同步嵌套:A线程拥有CPU执行权,但是A内还嵌套一个B。如果A想运行B需要把执行权给B,但是B拿到执行权又进不了A,就在互相等待,造成死锁。
synchronized(A.class){
synchronized(B.class){
xxx;
}
}
(2).生产者消费者模式的几个规则:
^1.生产时不可以消费(多线程且需要同步)
^2.生产一个后就不能生产,让消费者把生产出来的东西消费掉。然后转到生产者继续生产。
所以,如果一个生产者生产好了东西,然后CPU执行权转给另外一个生产者,这个时候是不可以生产的,2个生产者互相等待别人去消费,造成死锁。
生产者消费者模式的其他一些细节:
^1.判断是生产者还是消费者时,可以用一个flag来判断,但是不要用if(flag),用while(flag)。因为如果一个线程刚刚判断完if,转到另一个线程,则判断实际是无效的。
^2.为了避免死锁,一个生产者结束之后,唤醒其他线程的时候一定要有消费者线程在里面。所以不用notify(),用notifyAll();
^3.但是紧接着又有一个问题,一个生产者结束后,只需要消费者,但是notifyAll()把生产者也唤醒了,降低效率,所以为了解决效率低的问题,引入一套锁和监视器机制。
^4.每次只能生产一个产品,效率有点低,所以可以设置数组,保证可以生产多个,那么while判断时是数量判断。并且每次生产满后需要将数组的指针归为0重新开始。
class Resource(){
private int count;
private boolean flag = false;//一开始默认没有产品
private String name = "";
//生产
public synchronized void set(String name){
while(flag){//如果有产品了,等待
try{
wait();
}catch(Exception e){
e.printStack();
}
}
count++;
this.name = name + "..." + count;
system.out.println(Thread.currentThread.getName + "生产者" + this.name);
flag = true;
notifyAll();
//消费
public synchronized void get(){
while(!flag){
try{
wait();
}catch(Exception e){
e.printStack();
}
}
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(false)
r.set("面包");
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this .r = r;
}
public void run(){
while(true)
r.get();
}
}
public class Test{
public static void main(String[] args){
Resource r = new Resource();
Producer p = new Producer(r);
Consumer c = new Consumer(c);
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
锁和监视器的写法:
private Lock lock = new ReenTrantLock();
将上述synchronized变成lock.lock();
在finally里加上lock.unlock();
private Condition con = lock.newCondition();
wait()变成con.await();
notify()变成con.signal();
notifyAll()变成con.signalAll();
在唤醒时唤醒需要的那一方即可。
数组使可以同时生产多个。
生产者:while(count == objs.length), objs[put] = name, if(++put == objs.length) put = 0;
消费者:while(count == 0), name = objs[take], if(++take == objs.length) take = 0;
8.插入一个线程先运行
t.join():让t线程先运行,运行完之后再运行原本的线程
9.转化成守护线程
t.setDaemon(true):垃圾回收机制是一个很典型的守护线程。前台线程结束,守护线程也结束。
10.中止线程
如果不是在一个线程,用while判断
如果是一个线程,用t.interrept();
11.优先级
t.setPrority(数字):数字1-5,数字越大,CPU倾向分配的资源就越多。
12.wait和sleep异同
同:都会把线程从运行状态变成冻结状态
异:(1).sleep时间到了会自动从冻结状态变成运行状态,wait不会
(2).wait会释放执行资格和锁,sleep只会释放执行资格不会释放锁