多线程
### 多线程:
- 程序:一个可执行文件
- 进程:一个正在运行的程序,也可以理解成为内存中的开辟的存储空间
- 线程:负责程序的运行,可以看作是一条执行的通道或执行单元,所以我们通常将进程的工作理解成线程的工作。
进程中可不可以没有线程?必须有线程,至少有一个,当有一个线程的时候我们称为单线程(唯一的线程就是主线程)当有一个以上的线程同时存在的时候我们称为多线程
- 任务区:就是run()方法,我们将线程工作的地方称为任务区。每一个线程都有一个任务区,任务区通过对应的方法产出作用。
* JVM默认是多线程吗?
* 至少要有两个线程:
* 主线程:任务区:main函数
* 垃圾回收线程:任务区:finalize函数
线程实现的意义就是干活
- 当线程没有结束的时候,进程也不能结束。线程之间都是在抢CPU因为CPU有随机性。
创建线程
默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成自己的功能----创建自己的线程对象,ava将线程面向对象了,形成的类就是Thread,在Thread类内部执行任务的方法叫run().
注意1:如果想让run作为任务区,必须让他去被自动调用.我们通过执行start()方法,来开启线程,继而实现run方法的自动调用.
主线程的名字:main 子线程的名字:从Thread-0开始命名
1. 直接使用Thread();创建对象,创建线程,然后.start(),开启线程。
2. 使用子类对象创建对象,重写 run()方法,实现我们的功能,run就是我们的任务区。
1.Thread.currentThread():获取的是当前的线程
2.Thread.currentThread().getName():线程的名字
注意2:run()方法本来是空的,它里面的存放的是线程要做的工作,所以这里必须继承Thread,重写run()方法
注意3:run()方法不能手动创建?因为如果我们手动调用run()方法的时候,它失去了任务区的功能,变成了一个普通的方法,其方法还是运行在主线程中,代码在程序中是顺序执行的,所以不会有解决耗时操作的问题。所以不能直接调用线程的run()方法,只有子线程开始了,才会有异步的效果,当thread.start()方法执行了以后,子线程才会执行run()方法,这样的效果和在主线程中直接调用run()方法的效果是截然不同的。
实现多线程的方式两种:
- 第一种方法:通过创建Thread子类的方式实现功能–线程和任务绑定在一起,操作不方便。当多个线程要执行一个任务时,创建多个线程,这是就会有多个任务,虽然能定义一个static变量来实现你需要的功能,但是不符合逻辑。
public class Demo5 {
public static void main(String[] args) {
SubThread t0 = new SubThread();
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
SubThread t3 = new SubThread();
//开启线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
class SubThread extends Thread{
static int sum = 20;//大家共用这个变量
@Override
public void run() {
for (int i = 0; i < 5; i++) { //是为了把票都买完
System.out.println("剩余 票数:"+ --sum);
}
}
}
- 第二种:将任务从线程中分离出来,哪个线程需要工作,就将任务交给谁,操作方便.创建一个类实现Runnable,作为任务类,实现run方法。然后,创建任务对象,创建线程将任务和线程绑定。
这样做的好处是让多个线程调用一个任务对象,但是也又出现了线程安全问题,下面会引出锁的概念
注意4:这里Thread内部默认有一个run,又通过ticket传入一个run,为什么优先调用的是传入的run.因为如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
public class Demo3 {
public static void main(String[] args) {
//任务对象
Ticket ticket = new Ticket();
//将任务与线程绑定
Thread t0 = new Thread(ticket);
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
//开启线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
//任务类
class Ticket implements Runnable{
int sum = 20;
boolean flag = true;
public void run() {
while (flag) {
//让当前的线程睡100毫秒
//作用:让他暂时让出cpu
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (sum > 0) {
System.out.println("剩余 票数:"+ --sum);
}else {
flag = ! flag;
}
}
}
}
注意5:当任务和线程分离之后,多个线程对应一个run(),又会出现“线程安全问题”
1. Thread.correntTread();获取当前的线程。Thread.correntTread().
线程安全
解决:在代码中使用同步代码块
1. 可以保证线程的安全
2. 由于每次都要进行判断处理,所以降低了执行效率
同步代码块儿的构成:
synchronized(锁(对象)){
同步的代码
}
比较同步代码块和同步函数(同步函数的锁:this):
同步代码块儿使用更加的灵活,只给需要同步的部分代码同步即可,而同步函数是给这个函数内的所有代码同步.由于处于同步的代码越少越好,所以最好使用同步代码块儿
1. 当在一个类中同时存在多个synchronized修饰的代码块儿或函数时,要想安全,就必须让他们后面的对象一致。因为只有同一把锁才能安全。
2. 静态同步函数在进内存的时候不会创建对象,但是存在其所属类的字节码文件对象,属于class类型的对象,所以静态同步函数的锁是其所属类的字节码文件对象
理解synchronized关键字
1.synchronized关键字的作用域有二中:
- (1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
- (2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2.除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问,用法是:synchronized(this){//},它的作用域是当前对象。
3.synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}
在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
注意6:锁:1.对象,2.被所有线程共享----this、一个普通的对象(object对象)、字节码文件(不建议使用,使用范围大)当锁用的多的时候会使CPU的效率降低
注意7:synchronized里面代码越少,效率越高。左{获取锁,右}释放锁
总结:什么时候使用同步代码块
1.多个线程共享一个数据
2.至少有两个线程
两个进程通信
多个线程并发时,CPU默认随机切换线程,如果希望他们有规律,就可以通信,每个线程交替执行
如何进行通信:使用唤醒等待机制,notify()/notifyAll()/wait()
1. wait():让当前的线程变成等待的状态,放入一个池子(线程容器),失去了抢cpu的能力,.等待唤醒(锁相当于给当前的线程做了一个标记)
2. notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)
3. notifyAll():唤醒的是同一把锁下的所有线程。
注意8:wait,必须在同步环境中的锁调用
当面对两个生产者,两个消费者的时候,1.在wait()必须得循环判断,不然会出现两个生成着连续生成或者两个消费者连续消费。2.必须换成notifyAll();不然会出现死锁,因为当第一个生产者抢到CPU的执行完生成,第二个生成者抢到CPU,在wait()等待,第一个生产者又抢到cpu。接下来只能消费者的线程抢,重复上面的过程,正好在,第三次消费者抢到Cpu时,进入死循环。
package com.qf.test;
/* 生产者消费者:
* 单生产者单消费者-----会
* 多生产者多消费者-----了解
*
* 学习多生产者多消费者
* 需要的线程:四个---两个生产线程两个消费线程
* 需要的任务:两个---一个生产任务一个消费任务
* 需要数据:一份---产品
*
* 生产任务与消费任务共用一个数据--产品类
*
* 要求:最终也要实现一次生产一次消费
*错误描述:当有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
*原因:当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码
*
*解决办法:将标记处的if改成while
*
*问题描述:继续运行程序,会出现死锁的情况(4个线程同时处于等待状态)
*原因:唤醒的是本方的线程,最后导致所有的线程都处于等待状态.
*
*解决办法:将notify改成notifyAll.保证将对方的线程唤醒
*
*死锁:出现的情况有两种
*1.所有的线程处于等待状态
*2.锁之间进行嵌套调用
*
*/
public class Demo10 {
public static void main(String[] args) {
//准备数据
Product1 product = new Product1();
//准备任务
Producer1 producer = new Producer1(product);
Consumer1 consumer = new Consumer1(product);
//准备线程
Thread proThread1 = new Thread(producer);
Thread proThread2 = new Thread(producer);
Thread conThread1 = new Thread(consumer);
Thread conThread2 = new Thread(consumer);
//开启线程
proThread1.start();
conThread1.start();
proThread2.start();
conThread2.start();
}
}
//创建产品
class Product1{
String name;//产品的名字
double price;//产品的价格
int count;//生产的产品数量
//标识
boolean flag = false;
//准备生产
public synchronized void setProduce(String name,double price){
while (flag == true) {
try {
wait();//让生产线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.price = price;
System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count++;
flag = ! flag;
//notify();//唤醒消费线程
notifyAll();
}
//准备消费
public synchronized void getConsume() {
while (flag == false) {
try {
wait();//让消费线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
//唤醒生产线程
flag = ! flag;
//notify();
notifyAll();
}
}
//创建生产任务
class Producer1 implements Runnable{
Product1 product;
public Producer1(Product1 product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.setProduce("bingbing", 10);
}
}
}
//创建消费任务
class Consumer1 implements Runnable{
Product1 product;
public Consumer1(Product1 product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.getConsume();
}
}
}
研究Lock
1.比较synchronized和lock
1.synchronized:从jdk1.0就开始使用的同步方法-称为隐式同步.ynchronized(锁对象){//获取锁 }//释放锁 ----- 我们将锁还可以称为锁旗舰或者监听器
2.Lock:从jdk1.5开始使用的同步方法-称为显示同步
1.原理:Lock本身是接口,要通过他的子类创建对象干活儿
2.使用过程:–首先调用lock()方法获取锁–进行同步的代码块儿–使用unlock()方法释放锁.
2.适用场景:当进行多生成者多消费者的功能时,使用Lock,其他都使用synchronized
3.使用效率上:lock高于synchronized
JAVA是把同步方法面向对象:Lock
1.创建锁对象:Lock lock = new ReentrantLock();
2.用于生产任务的Condition:Condition因素出Object监视器方法( wait , notify和notifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。
Condition proCon = lock.newCondition();
3.获取锁:lock.lock();释放锁:lock.unlock();
4.让线程等待:proCon.await(); 唤醒线程:conCon.signal();
Condition类似于迭代器,为生成者和消费者创建自己的对象,在自己的同步代码块中唤醒对方的,提高效率相对于notifyALL()
Lock和synchronized有以下几点不同:
1.)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3.Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4.通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5.Lock可以提高多个线程进行读操作的效率。