Lock锁
Lock锁是一种可以自己开关的锁,与synchronized关键字的锁不一样的是,Lock锁能认为的开锁和关锁,而synchronized关键字在他修饰的代码块内开头自动关锁,结束自动开锁,这样不灵活。
Lock是一个接口,所以不能创建他的实例化对象,只能创建他的实现类对象,且在继承Thread方法开启多线程的情况下,因为会创建多个线程对象,所以需要将方法里的锁设置为静态变量,这样的话,才能控制多个线程的锁共用,起到锁的效果,Lock的开关主要时通过lock和unlock方法实现,实例代码如下:
下列代码实现锁的功能时,在第100次循环时,如果不加unlock的方法,则会直接break跳出循环,但是unlock解锁方法是写在循环里的,所以就会导致锁一直处于上锁的情况,所以需要在while循环中break之前添加一个unlock也就是解锁的方法。
package com.itazhang.Demo2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread2 extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
public MyThread2() {
}
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
while (true){
lock.lock();
if(ticket == 100){
lock.unlock();
break;
}else{
ticket++;
System.out.println(Thread.currentThread().getName()+"这是卖的第"+ticket+"张票");
}
lock.unlock();
}
}
}
(在写锁的时候不要让锁嵌套起来,不然可能造成死锁的情况)
等待唤醒机制
等待唤醒机制会有生产者和消费者这两个概念,正常情况是生产者生产数据,消费者消费数据。但是还要生产者等待的情况,也就是生产者之前生产的数据消费者还没进行消费,生产者又抢占到了cpu结果就会生产者因为之前生产的数据没被消费所以无法继续生产,消费者因为没有抢到cpu资源所以没有办法进行消费数据。还要一种就是消费者等待情况,在消费者消费了之前的数据后,又抢到了cpu资源,因为没有数据给消费者消费,而生产者又没抢到cpu资源,所以也会导致无法正常进行相关线程操作。针对上述问题,就有了等待唤醒机制。
如果出现生产者等待情况,生产者就会进入等待,cpu资源就会被消费者抢占,消费者在消费完数据之后就会唤醒生产者,然后继续进行生产消费。
如果出现消费者等待情况,消费者就会进入等待,cpu资源就会被生产者抢占,生产者在生产完之后就会唤醒消费者,从而继续消费生产。
下面是一个基础的生产者消费者的等待唤醒机制的代码实现,他需要一个生产者类,一个消费者类,以及一个控制数量,状态,和提供锁对象的控制类,如下:
生产者:
package com.itazhang.Demo3;
public class BUser extends Thread{
@Override
public void run() {
while(true){
synchronized (Controller.lcok){
if(Controller.count==10){
break;
}else{
if(Controller.flag==1){
try {
Controller.lcok.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Controller.flag = 1;
System.out.println("生产者正在制造数据");
Controller.lcok.notifyAll();
}
}
}
}
}
}
消费者:
package com.itazhang.Demo3;
//消费者的实现
public class AUser extends Thread{
@Override
public void run() {
while(true){
synchronized (Controller.lcok){
if(Controller.count == 10){
break;
}else{
//表示现在有数据
if(Controller.flag == 1){
Controller.count++;
System.out.println("消费者正在消费"+Controller.count+"个数据");
Controller.flag = 0;
//唤醒生产者进行生产
Controller.lcok.notifyAll();
}else{//如果没有数据,消费者直接wait
try {
Controller.lcok.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
控制者:
package com.itazhang.Demo3;
public class Controller {
static int count = 0;//表示数据的个数,如果数据个数为10,就停止
static int flag = 0;//0表示现在没有数据,1表示现在有数据
static Object lcok = new Object();//锁对象
}
测试类:
package com.itazhang.Demo3;
public class Test3 {
public static void main(String[] args) {
AUser a = new AUser();
BUser b = new BUser();
b.start();
a.start();
}
}
上述代码运行如下:
等待唤醒机制 (通过阻塞队列实现)
阻塞队列其实是一种单列集合,相当于在生产者和消费者直接提供一个队列,如果该队列满了,生产者就暂时不再生产,如果队列空了,消费者暂时不再消费。
在创建生产者类时,创建出阻塞队列(不赋值)并给出带有阻塞队列的构造方法,这样在测试类当中创建线程对象的时候就能将阻塞队列传递给该对象,然后生产者类中利用put方法进行添加数据
生产者的创建如下:
package com.itazhang.Demo4;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
queue.put("数据");
System.out.println("生产者生产了数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在创建消费者类时,创建出阻塞队列(不赋值)并给出带阻塞队列的构造方法,这样在测试类当中创建线程对象的时候就能将阻塞队列传递给该对象,然后在消费者类中利用阻塞队列带有的take方法取出数据。
消费者的创建如下:
package com.itazhang.Demo4;
import java.util.concurrent.ArrayBlockingQueue;
public class Eat extends Thread{
ArrayBlockingQueue<String>queue;
public Eat(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
String s = queue.take();
System.out.println("消费者正在消费"+s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类的创建如下:
package com.itazhang.Demo4;
import java.util.concurrent.ArrayBlockingQueue;
public class Test4 {
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
Cook cook =new Cook(queue);
Eat eat = new Eat(queue);
cook.start();
eat.start();
}
}
这样通过传递阻塞队列的方法就能实现生产者和消费者的关系
线程的状态
在学习完阻塞和等待唤醒机制后,线程的状态可以用如下图表示,即图下7个状态
但是线程实际在Java中的状态时只有如下6个状态
线程池
线程池的基本原理:线程池相当于是一个容器,在线程创建完使用了之后不去将线程销毁,而是放入到线程池中,如果下次还需要线程的话,直接将线程池里面的线程拿出来用,如果线程池里面 的线程没有空闲的,那么就会排队等待获取空闲的线程。
创建线程池
一、获取没有上限的线程池对象主要是采用newCachedThreadPool方法来进行获取线程池对象,如下:
ExecutorService pool1 = Executors.newCachedThreadPool();
在获取线程池对象以后,我们需要将任务传递给该对象,例如实现了runnable接口的类,将该类的实例化对象也就是任务通过submit方法传递给该线程池对象,这样就可以实现通过线程池实现相关的任务。如下:
pool1.submit(mr);
二、获取有上限的线程池对象主要采用newFixerThreadPool方法来创建,在创建的时候传递一个int类型的整数,该整数就是开启线程池里最大的线程个数。如下:
ExecutorService pool1 = Executors.newFixedThreadPool(3);
在创建完了该含上限的线程池之后,后面的步骤与无上限的线程池操作一样,将任务通过submit方法交给线程池对象,线程池能自动分配任务给里面的线程。