线程学习笔记

1.引入

程序,进程,线程

程序:静止在计算机上的代码段。就是软件,指令和数据的集合。一般存储在硬盘
进程:是一个程序的一次运行,有独立的资源分配,一般存储在内存或CPU
线程:进程中不同的执行路径,一个线程是一条独立的执行路径

线程和进程的关系:
一个进程可以有多个线程,且至少有一个。线程不会独立分配资源,一个进程中的所有线程,共享同一个进程中的资源。

并发,并行

并发:同一时间段内交替进行,达到时间段内同时运行的效果
并行:每一时刻都是同时运行(注意,并行只能在多核环境下实现)

2.线程执行的原理: cpu抢占式分时调度模式(队列,先进先出)

多线程时,在栈中有多个线程的run方法,谁抢到时间片谁运行。多核可以并行执行

3.线程的状态

在这里插入图片描述
在这里插入图片描述

4.创建线程(掌握)

在这里插入图片描述
Thread实现的Runnable接口

1、继承Thread类

了解:Thread里的start方法底层是调用了native start0(),c语言方法
普通创建形式:

class AThread extends Thread{
	@Override
	public void run() {
		super.run();
	}
}
public static void main(String[] args) {
		AThread a = new AThread();
//		a.run(); // 只是单纯的用对象调用run方法
		a.start(); // 开启线程,由jvm调用该线程的run方法。这就要考验线程抢时间片的本事了,那个线程抢到谁运行
	}
}

匿名内部类形式:

		new Thread() {
			@Override
			public void run() {
				...重写run方法...
			}
		}.start();

2、实现Runnable接口

普通创建格式:

class ARunnable implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
	}
}
public class ThreadStudy1 {
	public static void main(String[] args) {
		ARunnable a1 = new ARunnable();
		Thread t = new Thread(a1);
		t.start();
	}
}

匿名内部类格式:

new Thread(new Runnable() {
			
			@Override
			public void run() {
				...实现run方法...
			}
	}
}).start();

3、实现Callable接口,通过FutureTask包装器创建Thread

步骤:创建Callable实现类,不管是内部类还是什么类。用FutureTask进行包装,并返回FutureTask的实例,再转成Thread类调用start();
注意:FutureTask实现了Runnable,Future和RunnableFuture接口
在这里插入图片描述
使用方法:
注意:
1.用Callable的方式创建线程,相比较于Runnable,有返回值,且实现的call(),runnable是实现run()
2.两个线程(Thread对象)里传入同一个futureTask,只会执行一次
3.futureTask.isDone(),判断任务是否已完成
4.futureTask.get(),如有必要,等待计算完成,然后获取其结果。也就是最后输出。调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 用Callable的方式创建线程,相比较于Runnable,可以获取返回的值
		Callable ac = new Callable() {
		//这里的返回值是Object可以自定义,不设置泛型就是Object
			@Override
			public Object call() throws Exception {
				// TODO Auto-generated method stub
				System.out.println(Thread.currentThread().getName() + "call方法执行");
				int sum = 0;
				for (int i = 0; i < 1000; i++) {
					sum+=i;
				}
				return sum;
			}
		};
		// 需要一个FutureTask,而且在两个线程里传入同一个futureTask,只会执行一次
		FutureTask<Integer> futureTask = new FutureTask<>(ac);//线程组
//		注意:::两个线程里传入同一个futureTask,只会执行一次
		new Thread(futureTask, "task1-线程1:").start();
		new Thread(futureTask, "task1-线程2:").start();
		System.out.println(futureTask.isDone());//判断任务是否已完成,输出结果不一定
		System.out.println(futureTask.get());//如有必要,【等待计算完成】,然后获取其结果。也就是最后输出。
		
		//想要执行两次,就需要再来一个不一样的FutureTask
        FutureTask futureTask1 = new FutureTask(ac);
        new Thread(futureTask1,"task2-线程1:").start();
        new Thread(futureTask1,"task2-线程2:").start();
}

结果:顺序不固定,但是task1和task2都只有1个线程运行。且get获得的返回值一直是最后计算完的值。
在这里插入图片描述
拓展:FutureTask包装Runnable,封装了Callable对象的call()方法的返回值
使用构造方法:FutureTask(Runnable runnable, V result) 指定runnable和返回值。到时候get到的是result值

 Runnable runnable = new Runnable() {
        	@Override
        	public void run() {
        		System.out.println(Thread.currentThread().getName() + "runable执行");
        	}
        };
        FutureTask futureTask2 = new FutureTask<Integer>(runnable, 100);
        new Thread(futureTask2, "runTask-1:").start();
        System.out.println(futureTask2.get());

在这里插入图片描述

4、使用线程池 例如用Executors

1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。
Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

这个帖子挺全的:https://blog.csdn.net/m0_37840000/article/details/79756932

1.Executor执行Callable步骤:

用Executors获取一个ExecutorService的实例,用ExecutorService的实例submit(callable)提交callable对象,并得到一个Future对象,用Future对象的get方法得到执行结果。
(什么都不用管,直接submit就行了,Executor框架自己控制线程的启动、执行、关闭等简化开发
注意:因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用在Executor构造器中。


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadPool3 {
	 public static void main(String[] args){   
	        ExecutorService executorService = Executors.newCachedThreadPool();   
	        List<Future<String>> resultList = new ArrayList<Future<String>>();   
	  
	        //创建10个任务并执行   
	        for (int i = 0; i < 10; i++){   
	            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
	            Future<String> future = executorService.submit(new TaskWithResult(i));   
	            //将任务执行结果存储到List中   
	            resultList.add(future);   
	        }   
	  
	        //遍历任务的结果   
	        for (Future<String> fs : resultList){   
	                try{   
	                    while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
	                    System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
	                }catch(InterruptedException e){   
	                    e.printStackTrace();   
	                }catch(ExecutionException e){   
	                    e.printStackTrace();   
	                }finally{   
	                    //启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
	                    executorService.shutdown();   
	                }   
	        }   
	    } 
}
class TaskWithResult implements Callable<String>{   
    private int id;   
  
    public TaskWithResult(int id){   
        this.id = id;   
    }   
  
    /**  
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法, 
     * 则该方法自动在一个线程上执行 
     */   
    public String call() throws Exception {  
        System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
        //该返回结果将被Future的get方法得到  
        return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
    }   
}

结果:
在这里插入图片描述
问题:为什么不在for循环中直接get呢?

可以直接get,但是get会等这个线程结束才得到值,每循环一次基本都会等到线程结束,所以基本上就一个线程在执行。结果很少两个运行的时候,不确定。

2.Executor执行Runnable步骤:

通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上。也可以用submit(runable)或submit(runable,result);


import java.util.concurrent.ExecutorService;   
import java.util.concurrent.Executors;   

public class ThreadPool4{   
  public static void main(String[] args){   
      ExecutorService executorService = Executors.newCachedThreadPool();   
//      ExecutorService executorService = Executors.newFixedThreadPool(5);  
//      ExecutorService executorService = Executors.newSingleThreadExecutor();  
      for (int i = 0; i < 5; i++){   
          executorService.execute(new TestRunnable());   
          System.out.println("************* a" + i + " *************");   
      }   
      executorService.shutdown();   
  }   
}   

class TestRunnable implements Runnable{   
  public void run(){   
      System.out.println(Thread.currentThread().getName() + "线程被调用了。");   
  }   
}  

结果:
在这里插入图片描述
一个池的线程可能被调用多次,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务。

问题,submit和execute方法的区别?

在这里插入图片描述
在这里插入图片描述
execute没有返回值,处理异常需要trycatch,与普通线程一样

submit有返回值,所以需要返回值的时候必须使用submit。
不管提交的是Runnable还是Callable类型的任务,如果不对返回值Future调用get()方法,都会吃掉异常。

线程池

https://mp.weixin.qq.com/s/ROQMBFw0DWM4sSP-sdy-IA.

线程池2

方法二:用ThreadPoolExecutor (推荐)

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo {

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) {

        //使用阿里巴巴推荐的创建线程池的方式
        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 10; i++) {
            //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
            Runnable worker = new MyRunnable("" + i);
            //执行Runnable
            executor.execute(worker);
        }
        //终止线程池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

ThreadPoolExecutor 3 个最重要的参数:

  • corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量
  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数:

  1. keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
  2. unit : keepAliveTime 参数的时间单位。
  3. threadFactory :executor 创建新线程的时候会用到。
  4. handler :饱和策略。关于饱和策略下面单独介绍一下。

javaGuide 2020最新Java并发进阶常见面试题总结: https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/multi-thread/2020最新Java并发进阶常见面试题总结.md

线程区别

继承Thread:子类直接创建对象调用start(),基于继承的特点,继承了其他类就不能继承Thread,灵活性低。重写run方法
实现Runnable:需要实现Runnable并要转为Thread实例调用start()
实现
在这里插入图片描述

线程使用例子:

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 作者: 帅的一批
 * 时间:2019年12月16日下午3:21:43
 * 学习:多线程
 * 内容: 一个线程打印1-100 
 * 		一个线程打印A-Z 50遍
 */
public class ThreadStudy2 {
	public static void main(String[] args) {
		new Thread() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 1; i <= 100; i++) {
					System.out.print("==thread==:");
					System.out.println(i);
				}
			}
		}.start();
		
		new Thread(new Runnable() {		
			@Override
			public void run() {
				// TODO Auto-generated method stub	
				for (int j = 0; j < 50; j++) {
					System.out.print("==runnable==:");
					for (int i = 65; i <= 90; i++) {
						System.out.print((char) i);
					} 
					System.out.println();
				}
			}
		}).start();
	}
}

结果太长,大概就是两个线程抢着运行,说不准那一次运行那个线程,且一个方法中他随时可能被抢。如,输出ABC的时候,输出到了C线程就被抢了,那么就会接着输出123.
在这里插入图片描述
在这里插入图片描述

创建线程方法的对比

1、继承Thread和实现Runnable接口两个方法对比

继承Thread::代码简单,但是灵活性低,因为继承是单继承,实现类继承了Thread就不能再继承其他类了,拓展性低。
实现Runnable代码略微复杂,但是灵活性高。实现类可以实现多个接口,还可以继承一个父类。
回忆:接口可以继承多个接口,但不能继承类

5.线程常用方法(熟悉)

1.getName() 获取线程的名称
2.setName(String name) 设置线程的名称
注意: 默认没有设置线程的名称时,线程的名称 从0开始 格式 : Thread-index 其中 index从0开始。 如:Thread-0、Thread-1…Thread-n
3.currentThread() Thread类的静态方法 获取当前正在执行的线程对象。

Thread.currentThread();  获取当前执行的线程对象

4.getPriority() 获取线程的优先级
5.setPriority(int newPriority) 设置线程的优先级
注意 :优先级 : 最大为10 ,最小为1 ,默认为5 。
6 boolean interrupted()判断线程是否中断
7 interrupt() 中断线程
8.setDaemon(boolean on) 当参数为true时,设置为守护线程,如果为false ,设置为用户线程。
9.isDaemon() 返回为true 表示守护线程。
守护线程 : 用于守护其他线程可以正常运行的线程,为其他核心线程准备良好的运行环境
该线程为用户线程提供准备工作,如果用户线程消亡,守护线程就没有意义了,不论是否执行完成,一段时间后会结束
通常我们创建一个守护线程,对于一个系统来说在功能上不是主要的。例如抓取系统资源明细和运行状态的日志线程或者监控线程。

重要方法

run();如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
注意:start()之后,线程进入就绪状态
join()等待该线程终止。join(long millis)等待该线程终止的时间最长为 millis 毫秒。
注意 : join方法相当于方法调用。(大概就是谁调用join,执行谁)
sleep(long millis) 休眠 以毫秒值为单位。
yield() 线程的让步 :暂停当前正在执行的线程对象,执行其它线程。

yield注意:yield()应该做的是 让当前运行线程回到可运行(就绪)状态以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
简单来说:当一个线程抢到执行权后,执行到yield()方法后,就会放弃执行权,其他线程就可以拿到执行权 了。

yield() 例子:http://www.verejava.com/?id=16992953009875

wait()、notify/notifyAll() 方法

方法使用例子

守护线程使用

注意:start()之后调用setDaemon()会报异常java.lang.IllegalThreadStateException(无效的线程状态异常)
源码:
在这里插入图片描述

/**
 * 学习:多线程
 * 内容:
 * 		常用方法:
 * 			getName()
 * 			setName()
 * 			getPriority() //获取线程优先级,1-10 默认是	5
 * 			setPriority()
 * 		
 * 			isDaemon() // 判断是否为守护线程
 * 			setDaemon(boolean b)
 * 		守护线程
 * 		Exception in thread "main" java.lang.IllegalThreadStateException 
 * 同一个线程对象不能重复调用两次start方法,start之后不能再设置线程对象

 */
public class ThreadStudy4 {
	public static void main(String[] args) {
		//守护线程1
		DThread dt = new DThread();
		dt.setDaemon(true);
		dt.start();
		//守护线程2
		EThread et = new EThread();
		et.setDaemon(true);
		et.start();
		//main线程
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		
	}
}
class DThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10000; i++) {
			System.out.println(Thread.currentThread().getName() +":"+ i);
		}
	}
}
class EThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

说明:其中守护线程1循环10000次,守护线程2循环1000次,main线程循环10次。不管谁那个线程先抢到时间片,只要main方法的循环执行结束,另外两个都会在一段时间后结束。
在这里插入图片描述

join使用
public class ThreadJoinStudy {
	public static void main(String[] args) throws InterruptedException {
		FThread fThread = new FThread();
		fThread.start();
		fThread.join();//下边结果的第一个图是没有这句时的执行结果。
		//fThread.join(1);// 等待这个线程1毫秒,然后接续正常的执行,如果这1毫秒这个线程没有执行完,就开始跟其他线程继续抢时间片
//		Thread.currentThread().join(); //等待当前线程结束,当前线程是main,main结束不了了
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==:"+i);
		}	
	}
}
class FThread extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			System.out.println("join==:"+i);
		}
	}
}

没有join之前,交替执行
在这里插入图片描述
调用join()之后,等join结束才走main方法
在这里插入图片描述
//fThread.join(1);// 等待这个线程1毫秒,然后接续正常的执行,如果这1毫秒这个线程没有执行完,就开始跟其他线程继续抢时间片

sleep()使用
很简单,就是 Thread.sleep(long 毫秒数);  要进行异常处理,InterruptedException,或抛异常
yield方法使用

yield就是让步,让路。顾名思义,就是暂停当前线程执行其他线程.但是就像上边说的,yield不过就是让当前线程进入准备状态,继续跟其他线程同时抢,做不到完全让步。
在这里插入图片描述

public class ThreadYieldStudy {
	public static void main(String[] args) {
		YieldThread yt = new YieldThread();
		yt.start();
		
		for (int i = 0; i < 1000; i++) {
			System.out.println("main:"+i);
		}
	}
}
class YieldThread extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 1000; i++) {
			//如果是2的倍数,就让步让其他线程走
			if(i%10 == 0) {
				yield();
			}
			System.out.println("yield:"+i);
		}
	}
}

运行结果并看不出来。。。。可以证明,他不能保证做到让步

6.安全问题及解决方案(掌握)

多线程的安全问题:

当多个线程共同操作共享数据时,由于在该线程操作共享数据的同时,其它的线程可能也操作共享数据,导致程序的结果与预期结果不一致的现象。称为多线程的安全问题。
看例子:

/**
 * 作者: 帅的一批
 * 时间:2019年12月16日下午8:29:34
 * 学习:线程
 * 内容: 账户有1000块钱,两个人抢1000块,只要余额还大于0 就可以扣1000
 * 
 * 		在run方法中sleep之后,大概会出现3中情况
 * 
 * 		就是在睡得这100毫秒内,两个人都进去了。
 * 		0,-1000:A拿了1000 余额变为0,B也拿了1000余额变为-1000
 * 		-1000,-1000:A拿了1000余额变成了0,但是没输出,被B线程抢了,B拿了1000余额变成了-1000,然后他俩在输出,这是余额都是-1000
 * 		0,0:A拿了1000余额变成了0,但是这是还没赋值给balance,这个时候balance还是1000,此时B抢到线程由拿了1000,此时balance也是0。然后他们又输出
 * 
 * 		0,余额不足,这就是正常的情况,但是一睡就很难正常了,看电脑
 */
public class ThreadStudyAccount {
	public static void main(String[] args) {
		//创建一个账户,让他俩抢,如果每个人都新建一个账户对象,就不是抢同一个了
		Account account = new Account();
		//创建两个线程,即两个人,分别取抢这1000
		Thread jack = new Thread(account);
		jack.setName("Jack::::");
		Thread rose = new Thread(account);
		rose.setName("Rose===");
		
		jack.start();
		rose.start();
		
		
	}
}

class Account implements Runnable{
	
	public int balance = 1000;
	//没有出错,因为时间间隔太短
	/*@Override
	public void run() {
		// TODO Auto-generated method stub
		//如果余额大于0,就可以操作
		if (balance > 0) {
			balance -= 1000;
			System.out.println(Thread.currentThread().getName()+"取走了1000,剩余"+balance+"元。");
		}else {
			System.out.println("余额不足");
		}
	}*/
	@Override
	public void run() {
		// TODO Auto-generated method stub
		//如果余额大于0,就可以操作
		if (balance > 0) {
			//先不改balance的值,等等 第二个线程,让他也进if里
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			balance -= 1000;
			System.out.println(Thread.currentThread().getName()+"取走了1000,剩余"+balance+"元。");
		}else {
			System.out.println("余额不足");
		}
	}
}

解决方法:加锁!!同步方法,同步代码块

加锁(同步)的方式:

1.同步方法:权限修饰符 synchronized 返回值类型 方法名称(参数){}
注意:同步非静态方法的锁对象是this,静态方法的锁对象是当前类的.class,相当于类锁
2.同步代码块:synchronized(锁对象){}
同步锁,存在共同操作共享数据才有线程安全问题,并且加的锁对象必须是也是共享的。

锁对象范围: 
	① 任意实例对象
    ② this锁
    ③ 静态锁--》静态对象 
    ④ 类锁---》 类  ---》格式  类名.class

上述取钱问题的解决方式(及 继承情况的效果重现):
继承Thread方式创建线程,为了让数据共享要加static修饰符,变成静态变量

public class SychronizedStudy1 {
	public static void main(String[] args) {
		/*Account2 a1 = new Account2();
		Thread t1 = new Thread(a1);
		Thread t2 = new Thread(a1);
		t1.setName("Jack");
		t2.setName("Rose");
		t1.start();
		t2.start();*/
		
		Account a1 = new Account();
		Account a2 = new Account();
		a1.start();
		a2.start();
	}
}
class Account extends Thread{
	static int balance = 1000;
	static Object o = new Object();
	@Override
	public void run() {
//		synchronized (o) { // 静态锁
//		synchronized(this.getClass()) { // 类锁
		synchronized (Account.class) { // 类锁
			if (balance > 0) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				balance -= 1000;
			
				System.out.println(Thread.currentThread().getName() + "取走1000,剩余" + balance + "元");
			} else {
				System.out.println(Thread.currentThread().getName() + "余额不足");
			}
		}
	}
}

class Account2 implements Runnable{
	int balance = 1000;
	Object o = new Object();
	@Override
	public /*synchronized*/ void run() { //同步方法
//		synchronized (this) { //this锁
		synchronized (o) { //任意实例 锁
			
			if (balance > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				balance -= 1000;
				System.out.println(Thread.currentThread().getName() + "取走1000,剩余" + balance + "元");
			} else {
				System.out.println(Thread.currentThread().getName() + "余额不足");
			}
		}
	}
}

释放锁

在锁中内容执行完了,
执行途中遇见break,return
遇见错或异常

死锁(加锁引起的)

死锁的帖子:https://blog.csdn.net/hd12370/article/details/82814348

多个线程或进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
如:A线程有甲锁对象可是需要乙锁对象,B线程有乙锁对象可是需要甲锁对象,就导致了谁都不能走。
代码体现,同步的嵌套:

public class DeadLockStudy1 {
	public static void main(String[] args) {
		Thread t1 = new Thread(new AThread());
		Thread t2 = new Thread(new BThread());
		t1.start();
		t2.start();
	}
}
class AThread implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized (BThread.class) {
			System.out.println("AThread --1");
			synchronized (AThread.class) {
				System.out.println("AThread --2");
			}
		}
	}
}
class BThread implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized (AThread.class) {
			System.out.println("BThread --1");
			synchronized (BThread.class) {
				System.out.println("BThread --2");
			}
		}
	}
}

产生死锁时的运行结果
在这里插入图片描述
没有产生死锁的运行结果:
在这里插入图片描述

synchronize和lock?

两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

在这里插入图片描述

lock和synchronize帖子: https://www.cnblogs.com/iyyy/p/7993788.html
更详细的帖子:https://www.cnblogs.com/handsomeye/p/5999362.html
什么是线程的可重入:https://www.cnblogs.com/steakliu/p/10657547.html

重入锁

https://www.cnblogs.com/gxyandwmm/p/9387833.html

  • 重入锁:就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。
  • 不可重入锁:就是一个线程获取了锁之后,再获取这个锁还要排队或争夺锁。这就死锁了
  • 公平锁:多个线程是一个双向队列,先进先出。按队列顺序依次执行。先到先得。
  • 非公平锁:一个线程释放锁后,其他线程都可以抢锁。例:当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的,如果C获取到了锁,B就只能继续乖乖休眠了。

可重入锁(转)

https://www.jianshu.com/p/f47250702ee7

voliate关键字

https://blog.csdn.net/yinbucheng/article/details/71305951/

7.线程池使用步骤

线程池跟数据库类似,就是不用频繁的创建并销毁,可以反复用。

1、获取线程池对象

工具类:Executors生成线程池
在这里插入图片描述
返回一个ExecutorService

2、创建任务类对象

创建线程,继承Thread,实现Runnable或Callable

3、将任务类对象提交到线程池中

调用ExecutorService的submit得到Future类型返回值,如果有返回值可以用Future的get方法获取

使用方法上边有

没看完的帖子: https://www.cnblogs.com/wxd0108/p/5479442.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值