一.线程的基本属性有哪些?
二.讲讲实现多线程方式有哪些?
在java代码中实现多线程的方式常用的有两种
1.类A继承Thread类,重写Thread类的run方法,通过start()方法来启动
2.类B实现Runnable接口及其run方法。new一个Thread类,将B对象实例作为参数传进去,执行Thread类的start()方法
第二种方法之所以可以实现多线程,是因为在Thread类源码里边,在执行start方法的时候,会先判断有无实现runnable接口的,如果有则直接执行runnable的run方法。
三.线程有哪些状态?
3.1.阻塞和等待的区别:
阻塞是被动的,但是等待是主动的。
四.如何终止一个线程?
4.1.interrupt中止线程
用法1:线程二在while/for等循环的时候,线程一发出中断信号,此时需要循环条件再进行判断
public class StopThreadWithSleep implements Runnable{
private static int a=0;
@Override
public void run() {
while(a<100000000&&!Thread.currentThread().isInterrupted()){
if(a%100==0){
System.out.println(a+"是100的倍数");
}
a++;
}
}
public static void main(String[] args) {
Thread thread1=new Thread(new StopThreadWithSleep());
thread1.start();
try{
Thread.sleep(5);
System.out.println("主线程休眠结束---------------------------------------------");
}catch (InterruptedException e){
e.printStackTrace();
}
thread1.interrupt();
}
}
用法2:线程二在sleep/wait/join等阻塞的过程中,线程一发出中断信号,此时线程二将抛出异常(抛出异常后中断标志会重新复位,即在调用interrupt方法时,中断标志是true,在抛出异常后,中断标志为false)
public class StopThreadWithOutSleep implements Runnable{
private static int a=0;
@Override
public void run() {
try {
Thread.sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
//在捕获异常后,中断标志已经为false,此时需要重新将标志变为true
Thread.currentThread().interrupt();
while(a<100000000&&!Thread.currentThread().isInterrupted()){
if(a%1000==0){
System.out.println(a+"是1000的倍数");
}
a++;
}
}
public static void main(String[] args) {
Thread thread1=new Thread(new StopThreadWithOutSleep());
thread1.start();
try{
Thread.sleep(2000);
System.out.println("主线程休眠结束---------------------------------------------");
}catch (InterruptedException e){
e.printStackTrace();
}
thread1.interrupt();
}
}
4.2.其他方法(设置Boolean类型的中止变量)中止线程
用此方法可以实现部分条件下的线程中止,比如类似于interrupt用法一的情况。但是不适用于interrupt用法二的情况,比如当sleep的时候,不能进行中断,当sleep结束的时候才能进行中断,不建议使用
4.3.中止线程的实际应用
1.当方法包含sleep等会使线程阻塞的方法时,应该将异常throw,方便处理。
public class StopThreadWithOutSleep implements Runnable{
private static int a=0;
@Override
public void run() {
while(true){
try{
printNum();
}catch (InterruptedException e){
System.out.println("已经出现异常");
break;
}
}
}
//在类似情况下,当printNum在阻塞时被中断是不影响while(true)继续进行的,假如printNum是一个公共调用类
//则调用者可能会出现接收不到日志的情况,因此最好将异常抛出,这样会强制让调用者进行处理
private static void printNum() throws InterruptedException{
while(a<100000000){
if(a%1000==0){
System.out.println(a+"是1000的倍数");
}
a++;
Thread.sleep(5000);
}
}
public static void main(String[] args) {
Thread thread1=new Thread(new StopThreadWithOutSleep());
thread1.start();
try{
Thread.sleep(2000);
System.out.println("主线程休眠结束---------------------------------------------");
}catch (InterruptedException e){
e.printStackTrace();
}
thread1.interrupt();
}
}
五.关于线程常用方法。
消费者生产者模式
1.必须要有一个类似于队列的东西当缓存
2.生产者线程安全且满足在仓库满的时候进行等待,在未满的时候,进行生产且通知消费者继续消费
3.消费者线程安全且在仓库空的时候进行等待,在未空的时候,进消费且通知生产者继续生产
实现1:通过BlockingQueue(阻塞队列来实现) 其put和take方法都是线程安全,且put方法会在仓库满的时候进行等待,在未满的时候,进行生产且通知消费者继续消费。take方法会在仓库空的时候进行等待,在未空的时候,进消费且通知生产者继续生产
/**
* @author xiaqi
* @date 2019/12/11
*
* 1.定义共享队列LinkedBlockingQueue
* 2.定义原子类(用来确定货物编号)
* 3.定义消费者实现类
* while(true){
* linkedBlockingQueue.take()
* }
* 4.定时生产者实现类
* while(true){
* linkedBlockingQueue.put()
* }
*
* 说明:linkedBlockingQueue在put的时候会通过Lock加锁,
* linkedBlockingQueue在take的时候会通过take加锁,
* 在内部通过Lock通过Condition await和signal来防止多线程下出现问题
*
*/
public class BlockingQueueModel implements Model {
private final BlockingQueue<Task> queue;
private final AtomicInteger increTaskNo = new AtomicInteger(0);
public BlockingQueueModel(int cap) {
// LinkedBlockingQueue 的队列不 init,入队时检查容量;ArrayBlockingQueue 在创建时 init
this.queue = new LinkedBlockingQueue<>(cap);
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl implements Runnable {
@Override
public void run() {
while (true) {
try {
Task task = queue.take();
// 固定时间范围的消费,模拟相对稳定的服务器处理过程
Thread.sleep(500 + (long) (Math.random() * 500));
System.out.println("consume: "+Thread.currentThread().getName()+"-------" + task.no);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
private class ProducerImpl implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep((long) (Math.random() * 1000));
Task task = new Task(increTaskNo.getAndIncrement());
System.out.println("produce: " +Thread.currentThread().getName()+"++++++++" + task.no);
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public static void main(String[] args) {
Model model = new BlockingQueueModel(3);
for (int i = 0; i < 2; i++) {
new Thread(model.newRunnableConsumer()).start();
}
for (int i = 0; i < 5; i++) {
new Thread(model.newRunnableProducer()).start();
}
}
}
实现二:利用wait、notify来实现。注意:消费者与生产者采用同一个锁,且每次采用notifyAll方法进行唤醒。
/**
* @author xiaqi
* @date 2019/12/11
* 错误地方,为了加快处理使用了两个锁:PRODUCE_LOCK、CONSUME_LOCK会导致一个问题,
* 当消费者获取一次商品之后,不能唤醒生产者,只能唤醒CONSUME_LOCK.notifyAll()其他消费者,正常来说应该唤醒生产者继续生产的。
* 代码之所以不出错是因为有while,一直在生产,所以显得正常,但是实际上并没有做到通知。因为对象notify的限制,所以一般不可能
* 改正后采用同一个锁,可以保证正确,但是会造成效率不高,正确的解决方法是采用两个Lock变量PRODUCE_LOCK与CONSUME_LOCK。
* 同时增加两个变量对应的PRODUCE_Condition,CONSUME_Condition,当消费者消费之后,可以唤醒生产者线程去生产。
* 参考:https://monkeysayhi.github.io/2017/10/08/Java%E5%AE%9E%E7%8E%B0%E7%94%9F%E4%BA%A7%E8%80%85-%E6%B6%88%E8%B4%B9%E8%80%85%E6%A8%A1%E5%9E%8B/
*/
public class WrongWaitNotifyModel implements Model{
/**
* 定义队列
*/
private final Queue<Task> linkedList=new LinkedList<Task>();
/**
* 定义队列长度
*/
private int max=0;
/**
* 定义生产者锁
*/
private final Object PRODUCE_LOCK=new Object();
/**
* 定义消费者锁
*/
//改正后采用同一个锁,可以保证正确,但是会造成效率不高,解决方法是采用两个Lock变量与两个变量对应的Condition
// private final Object CONSUME_LOCK=new Object();
/**
* 定义原子类
*/
private final AtomicInteger atomicInteger=new AtomicInteger(0);
public WrongWaitNotifyModel(int length){
max=length;
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumeImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProduceImpl();
}
private class ConsumeImpl implements Runnable{
@Override
public void run() {
try{
while(true){
synchronized (PRODUCE_LOCK){
Thread.sleep(500);
while(linkedList.size()==0) {
System.out.println(Thread.currentThread().getName()+"已售罄");
PRODUCE_LOCK.wait();
}
Task task = linkedList.poll();
System.out.println(Thread.currentThread().getName()+" -- "+task.no);
PRODUCE_LOCK.notifyAll();
}
}
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
}
private class ProduceImpl implements Runnable{
@Override
public void run() {
try{
while(true){
synchronized (PRODUCE_LOCK){
Thread.sleep(200);
while(linkedList.size()>max){
System.out.println(Thread.currentThread().getName()+"当前仓库已满---");
PRODUCE_LOCK.wait();
}
Task task=new Task(atomicInteger.getAndIncrement());
linkedList.offer(task);
System.out.println(Thread.currentThread().getName()+" +++ :"+task.no);
PRODUCE_LOCK.notifyAll();
}
}
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
}
public static void main(String[] args) {
Model model = new WrongWaitNotifyModel(3);
for (int i = 0; i < 2; i++) {
new Thread(model.newRunnableProducer()).start();
}
for (int i = 0; i < 2; i++) {
new Thread(model.newRunnableConsumer()).start();
}
}
}
实现3:在实现2的基础上,利用reentrantLock与condition来实现。wait、notify用的是一个锁,但此时可以使用两个reentrantLock来将消费者锁和生产者锁分开、提高效率。同时采用两个condition来分别进行wait和notify操作。
----------------------------------------更新-------------------------------------------
1.ReentrantLock的实现是
pro.lock-- pro.unlock-- cus.lock-- cus.signal--cus.unlock实现
当使用sync的时候,我们可以再释放生产者锁之后,再获取消费者锁,唤醒消费者,达到跟reentrantLock类似的情况
2.在判断仓库是否为空、是否已满的时候,要用while循环,而不是if循环,原因是可能存在虚假唤醒。可参考:java多线程中虚假唤醒的原因都有什么? - 齐柏林的回答 - 知乎 https://www.zhihu.com/question/50892224/answer/280667072
死锁
是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
死锁的代码:
while(true){
Synchorized(objectA){
Synchorized(objectB){
//操作
}
}
}
while(true){
Synchorized(objectB){
Synchorized(objectA){
//操作
}
}
}
死锁的必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(3) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。