Java多线程

多线程

一、线程实现方式

1.1并发与并行

并发:指两个或多个时间在同一个时间段内发生

并行:指两个或多个时间内在同一时刻发生(同时发生)

1.2线程与进程

进程:指一个内存中运行的程序(由硬盘到内存,占用内存执行)

线程:线程是进程中的执行单元,一个进程至少有一个线程

1.3多线程好处

1.效率高

2.多个线程互不影响

1.4线程调度

1.分时调度

2.抢占式调度(java是抢占式调度)

二、创建线程

2.1创建Thread类的子类

(1)步骤

  1. 创建一个Thread类的子类

  2. 写run方法

  3. 创建Thread类的子类对象

  4. 调用start方法

JVM执行main方法,找OS开辟一条main方法通向CPU的路径,这个路径叫main线程,主线程,CPU通过这个线程,这个路径可以执行main方法

执行RUN方法时,开辟一条通向CPU的新路径用来执行run方法

结果是随机性的,线程随机调用(抢占式调度)

(2)获得线程名称

Thread.currentThread().getName()

(3)设置线程名称

public MyThread(String name) {
	super(name);
}

MyThread mt = new MyThread("旺财");
MyThread mt = new MyThread();
mt.setName("分线程");

(4)sleep方法

Thread.sleep(1000);//暂停1秒(1000毫秒)

2.2继承Runnable接口

(1)步骤

  1. 创建一个Runnable接口的实现类

  2. 实现该类,然后放入Thread的构造

  3. Thread的start

(2)好处

避免了Thread继承的局限,增强扩展性(解耦),尽量使用这种方式创建线程

(3)匿名内部类

匿名:没有名字

内部类:写在其他类内部的类

匿名内部类作用:简化diamante

匿名内部类最终产物:子类/实现类对象,而这个类没有名字

/**
 * 匿名内部类 
 */
new Thread() {    
	@Override    public void run() {        
		String name = Thread.currentThread().getName();        
		for (int i = 1; i <= 20; i++) {            
			System.out.println(name + "=>" + i + "汤汤");        
		}    
	}
}.start();
/** 
 * 简化接口方式 
 */
new Thread(new Runnable() {    
 	@Override    
 	public void run() {       
    	String name = Thread.currentThread().getName();        
    	for (int i = 1; i <= 20; i++) {            
    		System.out.println(name + "=>" + i + "宝宝");        
    	}    
    }
}).start();

三、线程安全

3.1售票重复问题

(1)安全问题

多个窗口售票重复的解决(线程安全)

多线程访问了共享数据

当程序出现延时时,就会出现问题

代码:

/** 
 * 卖票案例 线程类
 */
 public class RunnableImpl implements Runnable {    
 	//定义一个多线程共享票源    
 	private int ticket = 1;    
 	//设定线程任务:卖票    
 	@Override    
 	public void run() {        
 		String name = Thread.currentThread().getName();        
 		while (ticket > 0) {            
 			try {                
 				Thread.sleep(10);            
 			}catch (InterruptedException e){                
 				e.printStackTrace();            
 			}            
 			System.out.println(name + "==>正在卖第" + ticket-- + "张票");        
 		}    
 	}
 }
/**
 * 开启三个线程卖票
 */
public class Demo01Ticket {    
	public static void main(String[] args) {        
		Runnable run = new RunnableImpl();        
		new Thread(run).start();        
		new Thread(run).start();        
		new Thread(run).start();    
	}
}

(2)出现原因

开启多个线程,多个线程一起抢夺CPU的执行权,谁抢到谁执行

当while(ticket>0)判断过后,线程失去了对ticket的实时校验(失去了CPU的执行权),就造成了安全问题

(3)解决办法

线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了CPU的执行权,让其他的线程只能等待,等待当前线程完成

1.同步代码块

2.同步方法。

3.锁机制

3.2同步代码块

synchronized(锁对象){
	可能会出现线程安全的代码
}

注意:

​ 1.同步代码块中的锁对象可以是任意对象

​ 2.但是必须保证多个线程使用的锁对象是用一个

​ 3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行

总结:

​ 同步中的线程,没有执行完不会释放锁。同步外的线程,没有锁,进不到同步

3.3.1同步方法

(1)使用步骤

  1. 把访问共享数据的代码抽取出来,放到一个方法中

  2. 给方法加synchronized

(2)实现方法

同步方法也会把方法内部的代码锁住

只让一个线程执行

同步方法的锁对象是谁?

就是实现类对象 new RunnableImpl()

也就是this

public synchronized void payTicket(String name){    
    if (ticket > 0) {        
        try {            
            Thread.sleep(10);        
        } catch (InterruptedException e) {            
            e.printStackTrace();        
        }        
        System.out.println(name + "==>正在卖第" + ticket-- + "张票");    
    }
}

3.3.2静态同步方法

锁对象是谁?

​ 不能是this,因为this是创建对象后产生的,静态方法优先于对象

​ 静态方法的所对象是本类的class属性==》class文件对象(反射)

3.4 Lock方法

java.util.concurrent.locks.look接口

java.util.concurrent.locks.ReentrantLock 实现类

使用步骤:

​ 1.在成员位置创建一个ReentrantLockd 对象

​ 2.在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁

​ 3.在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁

public class RunnableImpl implements Runnable {
    //定义一个多线程共享票源
    private int ticket = 100;

    Lock l = new ReentrantLock();

    //设定线程任务:卖票
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        //创建同步代码块
        while (ticket > 0) {
            l.lock();
            if (ticket > 0) {
                try {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name + "==>正在卖第" + ticket-- + "张票");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    l.unlock();
                }
            }

        }
    }
}

四、线程状态

4.1 状态

新建状态:new

运行状态:start()

阻塞状态:

死亡状态:stop()

休眠(睡眠)状态:sleep(long) wait(long)

无限(永久)等待状态Object.wait(); 唤醒:Object.notify()

4.2 等待唤醒案例

/**
 * 等待唤醒案例:线程之间的通信
 *  创建一个顾客线程(消费者):告知老板要的包子数量和种类,调用wite()方法,放弃CPU的执行,进入Waiting状态
 *  创建一个老板线程(生产者):话了五秒做包子,做好包子之后,调用notify方法唤醒顾客拿包子
 * 注意:
 *  顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个正在执行
 *  只有所对象才能调用wait和notify
 *
 * Object类中的方法
 * wait()
 * 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
 * notify()
 * 唤醒正在等待对象监视器的单个线程。
 */
public class WaitAndNotify {

    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(){
            @Override
            public void run() {

                synchronized (obj){
                    //进入waiting状态
                    try {
                        System.out.println("客户要了包子");
                        obj.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("客户拿到包子回家");
                }

            }
        }.start();

        new Thread(){
            @Override
            public void run() {

                synchronized (obj){
                    try {
                        System.out.println("老板开始做包子");
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        System.out.println("老板做好了包子");
                        obj.notify();
                    }

                }

            }
        }.start();

    }

}

五、等待唤醒机制

5.1线程间通信

多个线程在锤同一个资源,但是用的作案工具各不相同

为何要线程通信?

​ 我们希望多个线程锤同一个资源时,能够讲道理守规矩,让他们通信,知道这个资源有人锤了

如何保证线程间通信有效利用资源?

5.2等待唤醒机制

重点:

​ 有效利用资源

通信方法:

​ wait:等待

​ notify:随机唤醒一个

​ notifyAll:唤醒所有

注意:

​ 就算被唤醒了,如果CPU被占用,一样不能马上执行,会进入阻塞状态,抢夺CPU资源

细节:

​ 1、wait和notify必须由同一个锁调用。

​ 2、wait和notify是属于Object类的方法。

​ 3、wait和notify方法必须要在同步代码块或同步线程中使用。

六、线程池

我们使用线程的时候就去创建一个线程,这样实现起来非常简单,但是存在问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束,这样频繁差UN个点线程就会大大降低系统的效率,因为频繁常见线程和销毁线程需要时间。

线程池就是为了解决这个问题而诞生的

6.1底层原理

线程池:容器–>集合(ArrayList, Hashset, LinkedList, HashMap)

当程序第一次启动的时候,创建多个线程,保存到一个集合中

当我们想要使用线程的时候,就可以从集合中取出来线程使用

Thread t = list.remove(0);
Thread t = linked.removeFirst();

当我们使用完毕线程,需要把线程归还给线程池

list.add(t);
linked.addLast(t);

JDK1.5已经内置类线程池

6.2用法

java.util.concurrent.Executors java.util.concurrent.ExecutorService

public static void main(String[] args) {

        ExecutorService es = Executors.newFixedThreadPool(2);

        es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });

        //线程池会一直开启,使用完了后归还给线程池
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());

        es.shutdown();
}

public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值