进程:正在进行中的程序
线程:就是进程中一个执行单元或执行情景或执行路径负责进程中程序执行的控制单元
一个线程中至少要有一个线程。当一个进程中线程有多少个时,就是多线程。
多线程解决的问题:可以多部分代码同时执行。
Jvm在启动线程的时候,每一个线程都有自己要执行的内容。
其中一个负责执行main函数中的内容:这个线程称之为主线程 main
负责垃圾回收器运行的称之为垃圾回收线程
每个线程都有自己要执行的neir,这个内容称之为:线程的任务
简单说:
启动线程就是为了执行任务,当任务有多个需要同时执行时,就需要多个线程
如何创建线程
Java既然需要调用底层才能完成进程的建立和线程的创建,那么,java应该有对外提供线程的对象来方便与程序员对线程的操作,在java的lang包中,去描述线程的类
创建线程有两种方法
方式一:继承Thread类,覆盖run方法
步骤:
1, 定义thread类
2, 覆盖thread类中的run方法
3, 创建thread类的子类对象创建线程对象
4, 调用线程对象的start方法,开启线程
*线程光创建不行,还需要运行才会出现多执行路径
发现要运行线程,必须使用start方法。
Start方法做了两件事:1,开启了线程让线程可以运行。2,调用了run方法
为什么结果不规律?
因为现在有多个线程在同时执行。Cpu在这几个线程间快速切换,随机性造成了结果的没规律
*Thread.currentThread():获取当前正在运行的线程
class Demo extends Thread
{
privateString name;
Demo(Stringname)
{
super();
this.name=name;
}
publicvoid run()
{
for(inti=0;i<10;i++)
System.out.println(name+"..........."+Thread.currentThread().getName());
}
}
class Test
{
publicstatic void main(String[] args)
{
Demod1=new Demo("小明");
Demod2=new Demo("小强");
d1.start();
d2.start();
}
}
调用run方法和start的区别
调用run方法,仅仅是一般对象在调用对象中的方法,并没有开启线程,还有主线程来完成的run方法的运行。
调用start方法,是开启了一个线程(一个新的执行路径)这个线程去执行了run方法
多线程运行状态
方式二:实现Runnable接口
1, 定义一个类实现Runnable接口
2, 覆盖Runnable接口中的run方法。将线程要运行的代码存储到run方法中
3, 创建该接口的子类对象
4, 通过Thread类进行线程的创建,并将Runnable接口的子类对象作为Thread类的构造函数的实参进行传递
*让线程对象创建时,就要明确运行哪个run方法,而这个run方法是需要被对象调用的,所以将run方法所属的对象传递给Thread类中的构造函数。
5, 调用Thread类中的start方法开启线程
Runnable接口的由来其实就是将线程的任务进行对象到的封装。
将线程任务封装成对象后,通过Runnable接口可以降低和Thread对象的耦合性。
如果是继承Thread类,覆盖run方法这种情况,Thread的子类即封装了线程的任务,而且自身还是一个线程对象,这就是任务和对象耦合性过高。
class Demo implements Runnable
{
Publicvoid run()
{
//线程的任务
}
}
Demo d=new Demo();//这是一个任务对象
Thread t1=new Thread(d);//创建线程对象去运行指定的任务
t1.start();
安全问题产生的原因;
1, 多个线程在操作共享数据
2, 操作共享数据的代码有多条
一个线程在执行多条操作共享数据的过程中,其他线程参数与了运算,这时就会发生安全问题。 依据:线程任务中有没有共享数据,该数据是否被多条语句操作
解决方案:只要保证一个线程在执行多操作共享数据的语句时,其他线程不能参与运算即可。
当该线程都执行完后,其他线程才可以执行这些语句
代码表现:java中给我们提供具体的解决语句。
那就是同步代码块
格式;
synchronized(对象)//对象可以是任意的对象
{
需要被同步的语句。
}
同步的原理:其实就是将需要同步的代码进行封装,并在该代码上加了一个锁
同步的好处:解决多线程的安全问题
同步的弊端:会降低性能
一种现象:出现了多线程安全问题,为了解决,加上了同步,发现问题依旧。
同步的前提:
必须要保证在同步中有多个线程。因为同步中只有一个线程该同步是没有意义的。
必须要保证多个线程在同步中使用的是同一个锁
这时才保证多个线程被同步
同步函数:
在声明函数时在头加上synchronized
同步函数使用的锁是this
同步函数与同步代码块的区别
1、 同步 函数比同步代码块写法简单
2、 同步函数使用的锁是 this。同步代码块使用的锁是任意指定的对象
建议开发时使用同步代码块。尤其是需要不同锁时
静态同步函数使用的锁
静态随着类的加载而加载,这时内存中存储的对象至少有一个就是该类字节码文件对象
这个对象的表现方式 类名.class它就表示字节码文件对象。
单例中的懒汉式的并发访问
在被多线程并发访问时,会出现线程安全问题
为了解决,加入了同步,虽然安全问题解决了。
但是性能降低了。
有没有办法将安全问题解决的同时,还可以提高性能。
有的,改成同步代码块
可以通过双重判断的形式来完成这个过程
class Single
{
privatestatic Single s=null;
privateSingle()
publicstatic Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s=newSingle();
}
}
returns;
}
}
同步的另一个弊端:死锁
最常见的死锁:同步嵌套
同步中还有同步,两个同步用的不是同一个锁
class Demo implements Runnable
{
privateboolean flag;
Demo(booleanflag)
{
this.flag=flag;
}
publicvoid run()
{
if(flag)
{
while(true)
{
synchronized(MyLock.locka)
{
System.out.println("if..........locka");
synchronized(MyLock.lockb)
{
System.out.println("if..........lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb)
{
System.out.println("else.........lockb");
synchronized(MyLock.locka)
{
System.out.println("else..........locka");
}
}
}
}
}
}
class MyLock
{
publicstatic Object locka=new Object();
publicstatic Object lockb=new Object();
}
class deadLock
{
publicstatic void main(String[] args)
{
Demod1=new Demo(true);
Demod2=new Demo(false);
Threadt1=new Thread(d1);
Threadt2=new Thread(d2);
t1.start();
t2.start();
}
}
线程间通信
多个线程在处理同一个济源。但是处理的动作(线程的任务)却不相同
等待唤醒机制
涉及到的方法:
wait()::等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中
notify():唤醒,唤醒线程池中被wait的线程,一次唤醒一个,而且是任意的
nsotifAll():唤醒全部,可以将线程池中的所有wait线程都唤醒
唤醒的意思就是让线程池中的线程具备执行资格
这些方法都要使用在同步中才有效
这些方法在使用时必须标明所属锁,这样才可以明确出这些放操作的到底是哪个锁上的线程
为什么这些操作线程的方法定义在object类中?
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象
能被任意对象调用的方法一定定义在Object类中
等待唤醒机制中经典问题:生产者消费者问题
多生产者消费者问题
产生问题:
生产者生产的商品没有被消费就生产
问题在于两点:
1, 本方唤醒了本方
2, 被唤醒的本方没有判读标记。只要将if判断该外while判断
将if改为while循环判断标记后,出现了死锁
因为本方唤醒了本方,而被唤醒的本方一判断标记,就继续等待。这样所有的线程都等待了。
必须唤醒对方才行,但是没有直接唤醒对方的动作,所以就使用了notifyAll,唤醒全部。
classResorce {
private String name;
private int num = 1;
private boolean flag = false;
public synchronized void set(String name){
while (flag)
try {
this.wait();
} catch(InterruptedException e) {
}
this.name = name + num;
num++;
System.out.println("生产者。。。。。pro。。。。" + this.name);
flag = true;
this.notifyAll();
}
public synchronized void get() {
while (!flag)
try {
this.wait();
} catch(InterruptedException e) {
}
System.out.println("消费者。。。。。。。消费" + this.name);
flag = false;
this.notifyAll();
}
}
classproducer implements Runnable {
Resorce r;
producer(Resorce r) {
this.r = r;
}
public void run() {
while (true) {
r.set("个馒头");
}
}
}
classcustomer implements Runnable {
Resorce r;
customer(Resorce r) {
this.r = r;
}
public void run() {
while (true) {
r.get();
}
}
}
publicclass Test {
public static void main(String[] args) {
Resorce r = new Resorce();
producer p = new producer(r);
customer c = new customer(r);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
JDK1.5后出现的新的接口和类:
Lock:比同步函数和同步代码块要好一些。
分析发现:
同步函数还是同步代码块所做的都是隐式的锁操作
同步函数或者同步代码块使用的锁和监视器是同一个
Lock接口:是将锁进行单独对象的封装。而且提供了对锁对象的很多功能。
比如:lock()获取锁,unlock()释放锁
Lock对锁的操作都是显示操作。
所以它的出现要比同步函数或者同步代码块明确的多,更符合面向对象的思想。
简单一句话:Lock接口的出现替代同步。
原来的同步中,锁和监视器是同一个对象。
现在,升级后,锁是一个单独的对象。
而且将监视器的方法也单独封装到了一个对象中。这个对象就是升级后Condition
升级后都进行了单独的封装。
锁被封装成了lock对象
监视器方法都被封装到了condition对象(监视器对象)中
说白了,Lock替代了同步,Condition替代了Object中的监视方法
condition中提供了监视器的方法:await(),singnal(),singnalAll()
如何让锁和监视器产生联系?
直接通过Lock接口中的newCondition()方法就可以获取到可能绑定到该Lock对象上的监视器对象condition
之前要解决本方只唤醒对方用的方式就是定义两个锁。
Synchronized(生产锁)
{
synchronized(消费锁)
{
if()
生产的wait();
。。。。。。。。。
消费的notify消费();//这里应该用的是消费锁上的notify
}
}
Synchronized(消费锁)
{
synchronized(生产锁)
{
if()
消费的wait();
。。。。。。。。。
生产的notify生产();
}
}
这样看上去是可以解决问题的,但是出现了同步嵌套,容易发生死锁
所以解决办法就是同一个锁,while的标记判断,notifyAll的全部唤醒
这种解决方案效率低,因为还会唤醒本方
有了新特性Lock,condition。就可以解决这个问题了。之前是有两个锁嵌套,容易死锁,
现在的方案只有锁,但是锁上可以加入多个监视器,一个监视生产者,一个监视消费者
importjava.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
class Resorce {
privateString name;
privateint num = 1;
privateboolean flag = false;
private Lock lock=new ReentrantLock();
Condition d1=lock.newCondition();
Condition d2=lock.newCondition();
publicvoid set(String name) {
lock.lock();
while(flag)
try{
d1.await();
}catch (InterruptedException e) {
}
this.name= name + num;
num++;
System.out.println("生产者。。。。。pro。。。。" + this.name);
flag= true;
d2.signal();
lock.unlock();
}
publicvoid get() {
lock.lock();
while(!flag)
try{
d2.await();
}catch (InterruptedException e) {
}
System.out.println("消费者。。。。。。。消费" + this.name);
flag= false;
d1.signal();
lock.unlock();
}
}
class producer implements Runnable {
Resorcer;
producer(Resorcer) {
this.r= r;
}
publicvoid run() {
while(true) {
r.set("个馒头");
}
}
}
class customer implements Runnable {
Resorcer;
customer(Resorcer) {
this.r= r;
}
publicvoid run() {
while(true) {
r.get();
}
}
}
class test {
publicstatic void main(String[] args) {
Resorcer = new Resorce();
producerp = new producer(r);
customerc = new customer(r);
Threadt1 = new Thread(p);
Threadt2 = new Thread(c);
t1.start();
t2.start();
}
}
停止线程:
1, stop():过时
2, run方法结束,当线程没有了要运行的代码,线程就结束了,意味着任务结束,线程消失。一般情况下,run方法中都会定义循环。
开启新的执行路径就是为了对多次的运算和其他代码进行同时的执行。
public voidrun()
{
System.out.println(“ok”);
}//这种线程任务是毫无意义的,根本就不需要多线程执行
public voidrun()
{
while(true)
{
System.out.println(“ok”);
}
}
开启多线程会同时并重复做很多运算
结束run方法,只要控制住run中的循环即可。
控制循环通常需要通过标记来完成。
中断线程
如果读不到标记,在任务线程中处于了冻结状态
释放了执行资格,无法执行标记,run方法没结束,线程也无法结束
Thread类中有一个interrupt方法,可以将线程的冻结状态清楚让线程恢复到具备执行资格
守护线程
Thread类中的setDaemon()方法可以将线程标记为守护线程或用户线程,即后台线程
必须在启动线程前调用
当正在运行的线程都是守护线程时,java虚拟机退出。
即当前台线程结束后,后台线程自动结束
join():等待该线程终止。 即该线程加入执行,其他线程释放其执行权和资格
自定义线程名称,在Thread类的构造方法中有直接默认带线程名的方法,可在子类中
super(name);
优先级:cpu执行的优先级
setPriority设置cpu优先级,取1-10,相邻近的优先级区别不大
MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY
wait()和sleep()方法的异同点:
sleep()必须指定时间,wait是可以指定时间,也可以不指定时间
sleep():会释放执行权,不会释放锁
wait():会释放执行权,会释放锁