1 基本概念:程序、进程、线程
JDK5.0之后 创建线程有 4 种方式
---->程序:为完成特定任务、用某种语言编写的一组指令的集合。静态对象
---->进程:是程序的一次执行过程,动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
---->线程:进程可进一步细化为线程,是一个程序内部的一条执行路径
一个程序可以同时并行多个线程,就是多线程的
线程作为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器(pc)
多个线程要共享一个进程的方法区和堆
---->并行和并发
并行:多个CPU同时执行多个任务
并发:一个CPU(采用时间片)同时处理多个任务
2多线程创建方式一:继承Thread
*1.创建一个继承Thread类的子类
*2.重写Thread类的run()
*3.创建Thread子类的对象
*4.调用start()方法:作用:启动当前线程 / 调用当前线程的run()方法
遍历100以内的所有偶数
---->例如:下面的语句中有两个线程:一个主线程 一个thread线程
public static void main(String[]args){
Thread1 thread = new Thread1();
thread.start();
System.out.println("hello");
}
---->Thread.currentThread().getName() 查看是什么线程
---->不能用 对象.run()方法启动线程 ,那样只是掉了个方法
---->一个线程只能Start一次,如果再次start 就会出现IllegalThreadStateException的异常
---->创建Thread匿名子类的方式,
new Thread1().start();
new Thread(){
@Override
public void run(){
for(int i = 0; i < 100; i++){
if((i % 2) == 1){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
3 Thread类的常用方法
测试Thread类的常用方法
*1 start()
*2 run()一般需要重写
*3 currentThread()静态方法返回当前代码的线程
*4 getName()
*5 setName(Stringname)设置当前线程的名字
*6 yield()写在run()中,表示释放当前cpu的执行权,有可能在下一刻又抢回来了
*7 join()在一个线程A中调用另外一个线程B的join()方法,A进入阻塞状态执行另一个线程B,直到B执行完成,A才结束阻塞
*8 sleep(longmillitime)
4 线程优先级的设计
*线程的优先级
1.MAX_PRIORITY:10
*MIN_PRIORITY:1
*NORM_PRIORITY:5
*2.如何获取和设置当前线程的优先级:
*获取:Thread.currentThread().getPriority()
*设置:对象.setPriority(Thread.MAX_PRIORITY)
*说明:高优先级的线程要抢占低优先级的线程cpu的执行权,但是从概率上讲,高优先级线程高概率的情况下被执行,
*并不意味着只有搞优先级的线程执行后,低优先级才执行
5 实现 Runnable接口
*实现Runnable接口
*1创建一个实现了Runnable接口的类
*2实现该接口中的run()抽象方法
*3创建该类的实例对象
*4将此对象作为参数,传递到Thread类的构造器中,创建Thread类的对象
*5调用thread对象的start()方法:启动线程 / 调用当前线程的run()方法
Mthread mThread = new MThread();
Thread thread = newThread(mThread);
//创建第二个 线程
Thread thread2 = newThread(mThread);
说明:这个调用的run()方法 其实就是调用的 Runnable 接口的方法
底层:Thread构造器 public Thread(Runnable target){ }
6 两种方式的对比
实际应用中 实现的方式好一点,
实现的方式没有类的但继承性的局限
实现的方更适合有共享数据的
---->Thread类中也实现了Runnable接口
7 线程的声明周期
阻塞不是一个线程的最终状态!!!!
Thread.State / Thread中的内部类(枚举类) State
8 线程的安全问题
买票中出现重票
---->线程安全问题出现的原因:多个线程操作共享数据
当某个线程操作车票的过程中,还没有操作完,其他线程参与进来
---->如何解决:当一个线程操作共享数据时,,其他线程不能参与进来,即使这个线程进入了阻塞状态
---->通过同步机制。
---->同步的方式,解决了线程的安全问题 ----好处
操作同步代码时,只能有一个线程参与 ,其他线程等待,相当于一个单线程的过程,效率低
*线程同步问题:concurrent
*
方式一、同步代码块
*//实现的方式
*private Object obj=new Object()
*synchronized(同步监视器/obj){
*//需要被同步的代码,即操作共享数据的代码
*}
*说明:1.同步监视器,俗称:锁。任何一个类的对象都可以成为锁
*要求:多个线程必须共用同一把锁!!!!
*注意实现Runnable接口的方式可以用this,但是继承Thread的方式要慎用this
*//继承的方式
*private static Objectobj=newObject()!!!!!!!!!!1
*synchronized(同步监视器/obj/或者用类.class){
**//需要被同步的代码,即操作共享数据的代码
**}
*说明:类.class只会加载一次
方式二、同步方法
*如果操作共享数据的代码完整的声明在一个方法总,可以把方法声明为同步的
实现的方式Runnable
private synchronized void show(){//同步监视器this
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets+"张票");
tickets--;
}
}
继承的方式
private static synchronized void show(){//同步监视器当前的类.class
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets+"张票");
tickets--;
}
}
总结:同步方法仍然涉及到同步监视器,
非静态的锁是this
静态的锁是当前类.class
例题,单例模式,懒汉式(线程安全版)
class Bank(){
private Bank(){
}
private static Bank bank = null;
public static synchronized Bank getBank(){ // 同步监视器:bank.class // 方式二
synchronized(Bank.class){ //方式一:效率较差
if(bank == null){ //这个地方会造成线程安全问题
bank = new Bank();
}
return bank;
}
//方式三,效率较高
if(bank == null){
synchronized(Bank.class){
if(bank == null){ //这个地方会造成线程安全问题
bank = new Bank();
}
return bank;
}
}
}
9 死锁的问题
不同的线程 分别占用对方需要的同步资源不放弃,都在等待对方放弃,这时候,不会出现异常,不出现提示,所有线程都处于阻塞状态
----> 要避免死锁
10 Lock锁方式解决线程安全问题
JDK5.0
解决线程安全问题的方式三:Lock锁方式JDK5.0新增
*1.面试题synchronized/Lock的异同
同:都可以解决线程安全问题
异:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
Lock是显示锁,synchronized是隐式锁
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间调度线程,性能更好
2.优先使用顺序:
Lock->同步代码块->同步方法
11 线程的通信
线程的通信:涉及到三个方法
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步锁
notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤醒优先级搞得
notifyAll():所有wait的线程都被唤醒
三个方法必须使用在同步代码块或者同步方法中
这三个方法的调用者必须是同步代码块或者同步方法的同步监视器
这三个方法定义在Object中
12 sleep()和wait()异同?
---->相同点:
一旦执行,都使得当前线程进入阻塞状态
---->不同点:
(1)声明的位置不同,sleep声明在Thread中,wait声明在Object中,
(2)调用要求不同,sleep可以在任何需要的地方调用,wait只能在同步代码块或者同步方法中调用
(3)关于是否释放同步监视器:如果两个方法都用在同步代码块或者同步方法中,sleep不会释放锁,wait会
13 生产者/消费者问题:
public class ProductTest{
public static void main(String[] args){
Product produnct = new Product(5);
Producer pro = new Producer(produnct);
pro.setName("生产者");
Consumer con = new Consumer(produnct);
con.setName("消费者");
pro.start();
con.start();
}
}
classProduct{
privateint num;
publicProduct(int num){
this.num=num;
}
//要考虑线程同步问题用同步方法 同步监视器是this
public synchronized void produce Product(){//生产者生产产品
//如果超过20个就停止上产
if(num<20){
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
num++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+num+"个产品");
//生产者生产了一个产品就可以唤醒消费者线程了
notify();
}else{
//否则就等待
try{
wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}
//消费者消费产品
public synchronized void consumeProduct(){
//如果有就开始消费
if(num>0){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":开始消费第"+num+"个产品");
num--;
//消费者消费了一个产品就可以唤醒生产者线程了
notify();
}else{
//否则就等待
try{
wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}
}
//生产者
class Producer extends Thread{
private Product product;
public Producer(Product product){
this.product = product;
}
@Override
public void run(){
System.out.println("生产者生成中。。。");
while(true){
product.produceProduct();
}
}
}
//消费者
class Consumer extends Thread{
private Product product;
public Consumer(Product product){
this.product = product;
}
@Override
public void run(){
System.out.println("消费者消费中。。。");
while(true){
product.consumeProduct();
}
}
}
14 创建多线程的方式三:实现Callable接口
重写call()方法
创建多线程的方式三:实现callable接口
//1.实现callable接口重写call方法
class Order implements Callable
//2.实例化
Order order = new Order();
//3.借用FutureTask类
FutureTask futureTask = new FutureTask(order);
futureTask.get();
//get()方法返回值即FutureTask构造器参数Callable实现类重写的call()方法的返回值
//4.
newThread(futureTask).start();
如何理解Callable接口方式创建线程比Runnable接口方式强大?
call()方法有返回值
call()方法可以抛出异常,被外面的操作捕获
Callable是支持泛型的
15 方式四:使用线程池
创建线程池的方法
1.提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
2.执行指定的线程的操作,提供一个实现Runnable接口或者实现Callable接口的对象
executorService.execute(newNumberThread());//适用于Runnable
//executorService.submit(newCallable());//适合使用Callable
3.关闭
executorService.shutdown();
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止