写在前面的部分
本周主要学习了多线程的相关知识
多线程
1.基本概念
a. 进程:
观察任务管理器,发现正在运行的程序(桌面的应用程序,客户端程序..)就是一个进程
进程来说:进程是系统分配资源调用的一个独立单位.
b. 多线程的意义:
1)现在目前计算机是一种多进程计算机,在做一件事情的同时还可以做另一件事情.
2)多进程是为了提高计算机CPU的使用率
c. 线程:
线程是依赖于进程存在,一个线程相当于进程中的某个任务,
d. 多线程的意义:
一个进程中开启了多个任务,每个一任务(线程),他们在互相抢占CPU的执行权,(提高程序的执行效率)
多线程在抢占CPU的执行权:特点:具有随机性!.
e. jvm虚拟机是不是一个多线程程序:
是,至少开启了两条线程,一条为主线程,另一条为垃圾回收器开启的垃圾回收线程
f. 多线程实现方式:
(1)自定一个类:MyThread 继承自Thread类
(2)在MyThread类中重写Thread类中的run() :为什么重写run()
(3)在主线程中,创建该类的实例对象,启动线程
2.实现方式一——继承Thread类
a. 概念:
1)自定义一个类继承Thread类,然后重写run()方法,run方法中写的是耗时的操作2)在主线程中创建该类的实例,启动线程public class MyThread extends Thread { /* * 重写Thread类中的run()方法 * */ @Override public void run() { // System.out.println("helloworld"); //耗时的操作,线程睡眠,线程等待,循环语句 for(int x = 0 ; x <100 ; x ++){ System.out.println(x); } } }
//run()方法调用相当于调用了一个普通方法,并不会出现线程随机性;而start()方法调用, MyThread my1 = new MyThread() ; MyThread my2 = new MyThread() ; //分别启动线程 my1.start() ; my2.start() ;
b. 构造方法:
(1)无参构造 Thread()
(2)有参构造 Thread(String name)
c. 线程名称:
(1)public final String getName()返回该线程的名称
(2)public final void setName(String name)改变线程名称,使之与参数 name 相同。
package org.westos_03; public class MyThread extends Thread { //无参构造 public MyThread(){ } //有参构造 public MyThread(String name){ super(name) ; } @Override //my1和my2子线程都会执行这段代码,两个子线程在互相抢占CPU的执行权 public void run() { for(int x = 0 ; x <200; x ++){ // System.out.println(x); System.out.println(getName() +":"+x); } } }
d. 线程优先级:
线程的优先级在1-10中取值,默认值为5.....
(1)Thread关于优先级的字段
1)public static final int MAX_PRIORITY 10 :最大优先级 优先级大的抢占到CPU的执行权大,并不代表就一定能抢到,因为线程的执行具有随机性!
2)public static final int MIN_PRIORITY 1 :最小优先级
3)public static final int NORM_PRIORITY 5 :默认优先级
(2)public final int getPriority():返回线程的优先级
(3)public final void setPriority(int):设置线程的优先级
e. join方法
(1)public final void join() throws InterruptedException等待该线程终止。
如果等待过程中被中断,则抛出异常InterruptedException
(2)线程1执行join方法后,会先执行线程1,等该线程执行完毕之后,再执行其他线程
jt1.start() ; //设置线程等待该线程终止该方法必须要启动线程 try { jt1.join() ; } catch (InterruptedException e) { //InterruptedException:中断异常 e.printStackTrace(); } jt2.start() ; jt3.start() ;
f. yield方法:
(1)public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
(2)暂停当前线程执行其他线程,并不保证另一个线程就一定能抢占到CPU的执行权
@Override public void run() { //yt1,yt2 for(int x =0 ; x <100 ; x ++){ System.out.println(getName()+":"+x); Thread.yield() ; } }
g. 其他方法:
(1)Thread.currentThread():获得当前运行的进程
(2)public final void setDaemon(boolean on) on指定true,就是设置守护线程...(在线程启动前调用)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
主线程数据输出完毕时,守护线程等会就会退出,无论run方法是否执行完。
h. 线程的生命周期
(1)public static void sleep(long millis) throws InterruptedException在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) 如果睡眠过程中被打断则抛出异常
(2)public final void stop():强迫线程停止执行
(3)public void interrupt()中断线程。 表示中断线程一种状态
(4)线程的生命周期:
2.实现方式二——实现Runnable接口
a. 开发步骤:
(1)自定义一个类,并实现Runnable接口(2)重写run方法(3)在主线程创建实例对象,并将其作为Thread类的参数传入(4)启动线程package org.westos_06; public class MyRunnable implements Runnable { @Override //run()中的方法也是需要一些耗时的操作 public void run() { for(int x = 0 ; x < 100 ; x ++){ //在控制台,打印每一个子线程的线程名称 // System.out.println(getName()+":"+x); //getName()是Thread类中的方法 //间接的使用Thread类的静态功能得到线程名称 System.out.println(Thread.currentThread().getName()+":"+x); } } } public class ThreadDemo { public static void main(String[] args) { //1)创建MyRunnable实例对象 MyRunnable my = new MyRunnable() ; //2)创建线程类对象 //Thread线程类中的构造方法 //public Thread(Runnable target) // Thread t1 = new Thread(my) ; // t1.setName("线程1") ; //public Thread(Runnable target,String name) Thread t1 = new Thread(my, "线程1") ; Thread t2 = new Thread(my, "线程2") ; //启动线程 t1.start() ; t2.start() ; } }
b. 线程同步
(1)模拟电影院售票
某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。(要保证三个窗口共享100张票,应把票定义在Runnable接口的子实现类中;)
(2)可能出现的问题(线程不安全)
1)一张票可能被卖多次(CPU的原子性)
2)可能出现负票(延时操作和线程随机性)
(3)检验多线程安全问题的标准
1)当前是否是一个多线程环境
2)多线程环境中是否有共享数据
3)是否有多条语句对共享数据进行操作
(4)解决方案:——synchronized
synchronized(同步锁对象){
多条语句对共享数据的操作;
}
2)
synchronized(d){//每一个线程ts1,ts2,ts3都使用的一个锁 //ts1进来,其他线程进不来 if (tickets > 0) { try { Thread.sleep(100) ; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); } }
(5)同步锁
1)同步锁对象:应该每一个线程都要使用这个锁对象(同步锁):理解为门的开和关
2)可以是Object类型,任意的Java类
(6)同步方法:
1)public synchronized void method(){} ->非静 态的同步方法的同步锁对象是this
2)静态的同步方法的同步锁对象时类名.class对象
(7)使用第二种方法实现多线程满足数据分离原则(面向接口的编程)
3.同步锁对象Lock
a. 概念:Lock是一个接口,所以它在使用的是 ReentrantLock子实现类
b. 常用方法:
(1)public void lock()获取锁。
(2)public void unlock()试图释放此锁
c. 实现代码 (同步代码块不需要synchronized块)
try{
//获取锁
lock.lock() ;
if(tickets>0){
//加入延迟操作
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}finally{
//试图释放锁对象
lock.unlock() ;}
4.死锁
a. 概念:
使用同步机制可以解决多线程的安全问题,但是自身也会有弊端:
(1)同步---->执行效率低(每一个线程在抢占到CPU的执行权,会去将(门)关闭,别的线程进不来)
(2)容易出现死锁现象
(3)死锁:两个或者两个以上的线程出现了互相等待的情况,就会出现死锁!
(4)应用:生产和消费者模式
b. 常见代码:
c. 生产者与消费者模式:if(flag){ synchronized(MyLock.objA){ System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } }//代码执行完毕,objA锁相当于才能被释放掉 }else { //dl2 synchronized (MyLock.objB) { System.out.println("else objB"); synchronized(MyLock.objA){ System.out.println("else objA"); } } }
(1)概念:
生产者:生产数据(输入数据)
消费者:输出数据
5.等待唤醒机制
a. 常用方法:(定义在Object类中)
(1)wait():等待直到调用notify()唤醒
调用之后立即释放锁(2)notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
6.线程组——ThreadGroup
a. 构造方法:
(1)public ThreadGroup(String name)构造一个新线程组(2)public Thread(ThreadGroup group,Runnable target ,String name)(使用线程组构造Thread线程)b. 方法:
(1)setDaemon:将所有线程均设为守护线程
(2)public final ThreadGroup getThreadGroup()返回该线程所属的线程组(默认线程组为main)
7.线程池
a. 概念:使用线程池节约成本(很多子线程调用完毕不会立即被回收掉,而是会回到线程池中被多次利用!)
使用工厂类来生产线程池b. 工厂类Executors中常用方法:
(1)public static ExecutorService newFixedThreadPool(int nThreads)
参数指定线程池中有多少个线程
返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
c. ExecutorService接口常用方法:
(1)Future<?> submit(Runnable task) ——>开启线程
(2)<T> Future<T> submit(Callable<T> task)
(3)shutdown():关闭线程ExecutorService pool = Executors.newFixedThreadPool(2) ; //下来使用ExecutorService(跟踪多个异步任务)一些方法 //使用submit(Runnable target):提交多个任务 pool.submit(new MyRunnable()) ; pool.submit(new MyRunnable()) ; //结束线程池 // void shutdown() pool.shutdown() ;
d. 多线程实现方式3
(1)submit方法返回Future接口表示异步运算的结果
(2)callable接口:(与Runnable接口类似)
实现该接口需要重写call方法(返回值是Object)
1)public Object call() throws Exception{}
该方法返回的类型和Callable接口中的泛型一致
(3)实现代码:public class MyCallable implements Callable<Object>{}
e. 利用多线程求异步计算的结果:
(1)案例:分别计算1-100,1-200之间的和
(2)创建线程池:
ExecutorService ThreadPool = Executors.newFixedThreadPool(2) ;
(3)提交异步任务:
Future<Integer> f1 = ThreadPool.submit(new MyCallable(100)) ;
Future<Integer> f2 = ThreadPool.submit(new MyCallable(200)) ;
(4)写Callable子实现类:
@Override public Integer call() throws Exception { //定义最终结果变量 int sum = 0 ; for(int x = 1 ; x <=number; x ++ ){ sum += x ; } return sum; }
(5)调用Future接口中get()方法,返回计算结果
Integer v1 = f1.get() ;
Integer v2 = f2.get() ;