目录
概述
多线程
进程:正在执行中的程序,在内存中有自己的数据集合,进程控制块,每个进程都有各自独立的一块内存。进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其它进程一起并发执行;
独立性:进程是系统进行资源分配和调度的一个最小独立单位,也是程序执行的最小单位;
结构性:进程由程序,数据和进程控制块三部分组成
线程:进程中的一个执行单元
一个进程中可以有多个线程。
可以实现多任务同时执行,表象看起来是同时执行,其实是CPU执行效率高,时间片极短,各个任务快速切换,给人的感觉就是同时执行,这就是传说中的并发。
并行是多个任务或事件在同一时间点同时执行。
多线程的合理使用,也是提高CPU资源利用率的一大绝招。线程过多会适得其反。
多线程执行时,在栈内存中,每一个执行线程都有自己的栈内存区域,执行方法的入栈和出栈。
创建多线程
继承Thread类
- 定义一个类继承thread;
- 重写run方法;run()里边写我们需要用多线程处理的程序。
- 创建线程对象;
- 调用start方法启动线程,同时告诉JVN去调用run方法。
实现Runnable接口
- 定义类实现Runnable接口;
- 覆盖接口中的run方法,需要用多线程处理的程序;
- 创建Thread类的对象;
- 将Runnable接口的子类对象作为对象传递给Thread类的构造函数;
- 调用Thread类的start方法开启线程。
实现Runnable接口避免了Java单继承的局限性。
实现Runnable接口的方式,更加符合面向对象的特性,线程分为两部分,一部分是线程对象,一部分是线程任务。
而继承Thread是线程对象与线程任务耦合在一起,因为一旦创建Thread类的子对象,既是线程对象,又是线程任务。
Runnable接口对线程对象和线程任务进行解耦。
多线程安全问题
产生原因:
- 多个线程在操作共享的数据
- 线程任务操作共享数据的代码有多个运算
解决思路:
只要让一个线程在执行多线程任务时将多条操作共享数据的代码执行完,在执行过程中,不要让其它线程参与运算就可以了。
解决代码:
Java中解决此类问题是通过代码块完成的。
同步代码块的锁对象可以是Object,也可以是this。
该代码块又称同步代码块,synchronized封装的代码块。
格式:
Synchronized(对象){
//需要被同步的代码
}
同步锁好处:解决多线程安全性问题
同步锁弊端:降低了程序的性能
同步锁前提:必须保证多个线程,且在同步中使用的是同一个锁。
同步函数:
同步函数:同步的另一种方式,即把同步代码块抽象为一个同步函数,即在函数定义时加Synchronized关键字,例如:public Synchronized void sale(){}
同步函数的锁对象使用的是this对象。
同步代码块与同步函数的区别:
同步函数使用的锁对象是固定的this,同步代码块使用的锁可以是任意对象。
当线程任务只需要一个同步锁时,完全可以使用同步函数;
当线程任务需要多个同步锁时,必须通过锁来区分,这时必须使用同步代码快。
静态同步函数:
静态方法只能调用静态成员变量。
静态方法没有this对象,随着类的加载而加载。
静态同步函数使用的锁对象是字节码文件对象:类名.class。
多线程对饿汉式单例模式无影响,对懒汉式单例模式有影响,需要加同步机制保证安全。
同步的弊端
- 降低程序的性能
- 死锁
当线程任务中出现了多个同步(多个同步锁对象)时,如果同步中嵌套了其它的同步,这时容易引发一种现象,死锁。
只要线程不执行,都叫死锁。
生产者消费者问题
生产和消费同时执行,需要多线程。
但是执行的任务却不相同,处理的资源却是相同的,线程间的通信。
等待/唤醒机制
wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
notify():会唤醒线程池中任意一个等待的线程。
notifyAll():会唤醒线程池中所有的等待线程。
注意:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。
同一个锁上的notify,只能唤醒该锁上的被wait的线程。
为什么这些方法定义在Object类中呢?
因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是Object类中的方法。
注意:对于等待都需要判断,定义条件。
多线程通信
多生产多消费问题
问题1;生产了商品没有被消费,同一个商品被消费多次。
Thread-1......生产者....面包63556
Thread-0......生产者....面包63557
Thread-3....消费者....面包63557
Thread-2....消费者....面包63557
被唤醒的线程没有判断标记,造成问题1的产生。
解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while判断标记。记住:多生产多消费,必须时while判断条件。
问题2:发现while判断后,死锁了。
原因:生产方唤醒了线程池中生产方的线程。本方唤醒了本方。
解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。
其实还有一些遗憾的,效率低了。
Lock接口
jdk1.5以后提供多生产多消费的解决方案。
在java.util.concurrent.locks 软件包中提供相应的解决方案
Lock接口:比同步更厉害,有更多操作。lock():获取锁 unlock():释放锁;
提供了一个更加面对对象的锁,在该锁中提供了更多的显示的锁操作。
替代同步。
升级到JDK1.5及以上,先把同步改成 Lock。
Condition接口
已经将旧锁替换成新锁,那么锁上的监视器方法(wait,notify,notifyAll)也应该替换成新锁的监视器方法。
而jdk1.5中将这些原有的监视器方法封装到了一个Condition对象中。
想要获取监视器方法,需要先获取Condition对象。
Condition对象的出现其实就是替代了Object中的监视器方法。
await();
signal();
signalAll();
将所有的监视器方法替换成了Condition。
功能和老程序的功能一样,仅仅是用新的对象。改了写法而已。
但是问题依旧;效率还是低。
希望本方可以唤醒对方中的一个。
老程序中可以通过两个锁嵌套完成,但是容易引发死锁。
新程序中,就可以解决这个问题,只用一个锁,
可以在一个锁上加上多个监视器对象。代码如下:
/*
jdk1.5以后提供多生产多消费的解决方案。
在java.util.concurrent.locks 软件包中提供相应的解决方案
Lock接口:比同步更厉害,有更多操作。lock():获取锁 unlock():释放锁;
提供了一个更加面对对象的锁,在该锁中提供了更多的显示的锁操作。
替代同步。
新程序中,就可以解决这个问题,只用一个锁,
可以在一个锁上加上多个监视器对象。
*/
import java.util.concurrent.locks.*;
//描述资源类
class Resource
{
private String name;
private int count = 1;
//定义一个锁对象。
private final Lock lock = new ReentrantLock();
//获取锁上的Condition对象。为了解决本方唤醒对方的问题。可以一个锁创建两个监视器对象。
private Condition produce = lock.newCondition();//负责生产。
private Condition consume = lock.newCondition();//负责消费。
//定义标记。
private boolean flag = false;
//1,提供设置的方法。
public void set(String name)//
{
//获取锁。
lock.lock();
try{
while(flag)
try{produce.await();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);
//将标记改为true。
flag = true;
consume.signal();
}finally{
lock.unlock();//一定要执行。
}
}
public void out()//
{
lock.lock();
try{
while(!flag)
try{consume.await();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);
//将标记该为false。
flag = false;
produce.signal();
}
finally{
lock.unlock();
}
}
}
//2,描述生产者。
class Producer implements Runnable
{
private Resource r ;
// 生产者一初始化就要有资源,需要将资源传递到构造函数中。
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("面包");
}
}
}
//3,描述消费者。
class Consumer implements Runnable
{
private Resource r ;
// 消费者一初始化就要有资源,需要将资源传递到构造函数中。
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ThreadDemo11
{
public static void main(String[] args)
{
//1,创建资源对象。
Resource r = new Resource();
//2,创建线程任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//3,创建线程。
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
sleep和wait的区别
相同点:都会使线程处于冻结状态,暂停执行。
不同点:
1,
sleep必须指定时间。
wait可以指定时间,也可以不指定时间。
2,
sleep时间到,线程处于临时阻塞或者运行。
wait如果没有时间,必须要通过notify或者notifyAll唤醒。
3,
sleep不一定非要定义在同步中。
wait必须定义在同步中。
4,
都定义在同步中,
线程执行到sleep,不会释放锁。
线程执行到wait,会释放锁。
线程如何停止
stop方法过时了,看起描述发现,有其他解决方案。
线程结束:就是让线程任务代码执行完,run方法结束。
run方法咋结束呢?
run方法中通常都定义循环,只要控制住循环就可以了。
注意:万一线程在任务中处于了冻结状态,那么它还能去判断标记吗?不能!
咋办?通过查阅stop方法的描述,发现提供了一个解决方法:
如果目标线程等待很长时间,则应使用 interrupt 方法来中断该等待
所谓的中断并不是停止线程。
interrupt的功能是 将线程的冻结状态清除,让线程恢复到的运行状态(让线程重新具备cpu的执行资格)。
因为是强制性的所以会有异常InterruptedException发生,可以在catch中捕获异常,
在异常处理中,改变标记让循环结束,让run方法结束。
守护线程
也可以理解为后台线程,之前创建的都是前台线程。
只要线程调用了setDaemon(true);就可以把线程标记为守护线程。
前台后台线程运行时都是一样的,获取CPU的执行权执行。
只有结束的时候有些不同。
前台线程要通过run方法结束,线程结束。
后台线程也可以通过run方法结束,线程结束,还有另一种情况,
当进程中所有的前台线程都结束了,这时无论后台线程处于什么样的状态,都会结束, 从而进程会结束。
进程结束依赖的都是前台线程。
线程优先级
用数字标识的,1-10
其中默认的初始优先级时5 最明显的三个优先级 1,5,10。
setPriority(Thread.MAX_PRIORITY);
线程组
ThreadGroup:可以通过Thread的构造函数明确新线程对象所属的线程组。
线程组的好处,可以对多个同组线程,进行统一的操作。
默认都属于main线程组。
Join、yield方法
class Demo implements Runnable
{
public void run()
{
for(int x=1; x<=40; x++)
{
System.out.println(Thread.currentThread().getName()+"------>"+x);
Thread.yield();//线程临时暂停。将执行权释放,让其他线程有机会获取执行权,包括它自己。
}
}
}
class JoinThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
//主线程执行到这里,知道t1要加入执行,主线程释放了执行权,
//执行资格并处于冻结状态,什么时候恢复呢?等t1线程执行完。
// try{t1.join();}catch(InterruptedException e){}//用于临时加入一个运算的线程。让该线程运算完,程序才会继续执行。
for(int x=1; x<=50; x++)
{
System.out.println("main---------->"+x);
}
System.out.println("over");
}
}
匿名内部类
线程的匿名内部类体现:
new Thread(new Runnable()
{
public void run()
{
System.out.println("runnable run");
}
}){
public void run()
{
System.out.println("subthread run");//执行。
}
}.start();