多线程
1,进程
**概述:**正在运行的程序叫做进程。进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。
多进程的意义:
单进程计算机只能做一件事情,而现代计算机都是多进程,也就是可以同时做很多事情。
**举例:**一边记笔记,一边屏幕广播,一边执行代码
所以多进程的意义:同一个时间可以进行多个任务,提高工作效率,并且提高cpu的使用率。
思考:
我电脑是单核处理器,我一边做笔记,一边屏幕广播,它们是同时进行的吗?
一个cpu在一个时间点上,只能做一件事情,它在执行多个进程时进行程序间的高速切换,这个时间非常短。所以给我们的感官是进程同时执行。
2,线程
在同一个进程中,可以执行多个任务,而每一个任务就是一个线程。
概述:
线程也就是程序执行单元。也是执行路径,线程也是程序使用cpu资源的基本单位。
单线程:
只有一条执行路径或一个执行单元
多线程:
有多条执行路径或多个执行单元。
多线程的意义:
1,线程的执行是抢占式的,它要抢占cpu的资源,一个多线程程序在执行时,如果一些线程必须等待的时候,cpu资源就可以交给其他线程去使用,这样就提高了cpu的使用率。
2,对于进程来说,如果它是多线程的,在抢占cpu资源时,就有更大的机率抢占到cpu资源。提高程序的执行率。
3,在抢占cpu资源时,是具有随机性的,我们不敢保证哪一个线程在哪一段时间可以抢到cpu资源。
**注意:**jvm是多线程,一个主线程和一个垃圾回收线程
3,并行,并发
并行:
多个处理器或多个处理器同时处理多个不同任务
某一段时间内运行多个程序
并发:
一个处理器同时处理多个任务
某个时间点运行多个程序
举例:
1,并发就相当于一个人同时吃3个馒头,并行就相当于3个人分别吃1馒头
2,我正在吃饭,突然来电话,但是我直到吃完饭,我才去接电话,就代表我不支持并发,也不支持并行
我正在吃饭,突然来电话,我停止吃饭,去接电话,接完电话,接着吃饭,代表我支持并发
我正在吃饭,突然来电话,我一边吃饭,一边打电话,这就代表我支持并行。
4,如何实现多线程
由于线程是依赖于进程存在的,所以首先我们要创建一个进程,而进程是由系统来创建的,所以我们需要调用系统的某些功能来创建进程,但是java是不能调用系统功能,但是它可以调用c/c++写好的程序来创建进程,进而实现多线程。
Java讲c/c++写好的代码封装到一个类中,然后通过这个类可以直接调用创建进程,线程的这些功能。然后间接的实现多线程。
java提供这个类叫做Thread类
通过API,我们就知道创建线程有两种方式
1,一种方法是将类声明为Thread子类,该子类应重写Thread类的run()方法
2
方式1:
1,自定义线程MyThread,继承Thread
2,MyThread类重写run()方法
3,创建MyThread类对象
4,启动线程Start
**方式2:**1,实现Runnable接口
2,重写run方法
3,创建自定义线程类对象
4,创建Thread对象并将自定义线程对象作为参数进行传递
5,start()
**注意:**直接调用线程对象的run方法相当于普通方法的调用,启动线程要用start方法,用start方法流程是,start先启动线程,然后由jvm去调用run方法
**思考:为什么要重写run方法?
一个类中,我们并不需要所有的方法都按照线程去完成,只需要将要按照线程执行的功能写进run方法就可以了
线程可以被多次启动吗?
不能,线程只能启动一次,如果写调用两次start方法,相当于调用了两次线程,既不是启动了两次线程,也不是开启了两条线程。
/* 创建多线程
* 1.自定义线程MyThread类,继承Thread类
2.MyThread类重写run()方法,重写的内容就是线程要干的工作
3.创建MyThread类对象
4.启动线程start();
*
* 线程重命名:1.setName(String name)
* 2.有参构造;在子类中声名有参构造,并显示指定访问父类有参构造
*
* 获取当前执行的线程名:Thread.currentThread().getName()
* */
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
//线程一般用来执行比较耗时间的工作
for(int i=0;i<500;i++){
System.out.println(getName()+":"+i);
}
}
}
class MyThreadD{
public static void main(String[] args) {
MyThread myThread=new MyThread("holy");
MyThread myThread1=new MyThread("shit");
// myThread.setName("holy");
// myThread1.setName("shit");
/* myThread.run();
myThread1.run();//普通方法调用*/
myThread.start();//启动线程
myThread1.start();
System.out.println(Thread.currentThread().getName());//获取主线程的名称
}
}
4,1设置线程优先级
两种线程调度模型
1,分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用CPU的时间片。
2,抢占式调度:该调度模型会优先让优先级高的线程使用cpu,如果优先级相同,则随机选择,优先级高的线程只代表他抢到cpu的概率较大
**默认优先级为5,优先级最小是1,最大是10 **
public class MyThread1 extends Thread {
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(getName()+":"+i);
}
}
}
class ThreadD2{
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1("彭于晏");
MyThread1 myThread11=new MyThread1("金城武");
myThread1.setPriority(4);//设置优先级
myThread11.setPriority(10);
//获取优先级
System.out.println(myThread1.getPriority()+" "+myThread1.getName());
System.out.println(myThread11.getPriority()+" "+myThread11.getName());
myThread1.start();
myThread11.start();
}
}
4,2 线程控制-----sleep
概述:使当前正在执行的线程休眠,以指定毫秒数暂停
/*线程控制-线程睡眠演示案例
* sleep()在哪个线程中调用,就阻塞哪个方法*/
public class SleepD {
public static void main(String[] args) {
MySleep mySleep1 = new MySleep("彭于晏");
MySleep mySleep2 = new MySleep("金城武");
mySleep1.start();
mySleep2.start();
}
}
class MySleep extends Thread {
private String name;
public MySleep(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(getName()+" "+i+""+new Date());
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4,3 join线程控制----加入线程
Public final void join(long miles)该方法是让调用该方法的线程执行完毕之后,再去执行其他线程,不设置则执行完毕之后,再执行其他线程。
/*线程控制-线程睡眠演示案例
* sleep()在哪个线程中调用,就阻塞哪个方法*/
public class SleepD {
public static void main(String[] args) {
MySleep mySleep1 = new MySleep("彭于晏");
MySleep mySleep2 = new MySleep("金城武");
MySleep mySleep3= new MySleep("林青霞");
mySleep1.start();
mySleep1.setPriority(1);
try {
mySleep1.join();//彭于晏先运行完再运行其他线程
} catch (InterruptedException e) {
e.printStackTrace();
}
mySleep2.start();
mySleep3.start();
}
}
class MySleep extends Thread {
private String name;
public MySleep(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(getName()+" "+i+""+new Date());
/* try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
4,4线程控制—线程礼让yield()
Public static void yield()线程礼让可以让线程间的抢占趋于和平,你运行一下我运行一下,但是仅仅是让抢占不激烈,并不是说一定每个线程的执行时间或执行次数相同。
/*线程控制-线程睡眠演示案例
* sleep()在哪个线程中调用,就阻塞哪个方法*/
public class SleepD {
public static void main(String[] args) {
MySleep mySleep1 = new MySleep("彭于晏");
MySleep mySleep2 = new MySleep("金城武");
MySleep mySleep3= new MySleep("林青霞");
mySleep1.start();
mySleep1.setPriority(5);
/* try {
mySleep1.join();//彭于晏先运行完再运行其他线程
} catch (InterruptedException e) {
e.printStackTrace();
}*/
mySleep2.start();
mySleep3.start();
}
}
class MySleep extends Thread {
private String name;
public MySleep(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(getName()+" "+i+""+new Date());
/* try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Thread.yield();//线程礼让
}
}
}
4,5线程守护
public final void SetDaemon(boolean on)
注意事项:
1,当线程只有守护线程,jvm会退出
2,在线程开始前设置守护线程
/*public final void SetDaemon(boolean on)
刘备跑完,张飞和关羽的线程也会结束*/
public class DaemonD extends Thread{
private String name;
public DaemonD(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+" "+i);
}
}
}
class DaemonDemo{
public static void main(String[] args) {
DaemonD daemonD1 = new DaemonD("关羽");
DaemonD daemonD2 = new DaemonD("张飞 ");
//在线程启动前添加后台线程
daemonD1.setDaemon(true);
daemonD2.setDaemon(true);
daemonD1.start();
daemonD2.start();
Thread.currentThread().setName("刘备");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
4,6线程控制—终止线程
public final void stop()
public void interrupt()
interrupt方法与stop不同,它走的是异常处理机制,如果在线程终止前还有一些代码必须执行的话,你可以把这些代码写在finally中或try-catch结构外。
注意:
public void interrupt()
//中断线程。
public static boolean interrupted()
//测试当前线程是否已经中断。
/*线程中断,stop方法本质上是不安全的,它会立马终止调用该方法的线程,让主线程后面的代码还没有执行就结束了
* interrupt只是中断调用该方法的线程,主线程后面的代码依然会执行*/
public class InterruptD extends Thread {
private String name;
public InterruptD(String name) {
super(name);
}
@Override
public void run() {
System.out.println("线程"+getName()+"开始了"+new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("线程被中断了");
}
System.out.println("线程"+getName()+"结束了"+new Date());
}
}
class MyInterrupt{
public static void main(String[] args) {
//创建线程对象
InterruptD interruptD1 = new InterruptD("李莫愁");
InterruptD interruptD2 = new InterruptD("洪凌波");
interruptD1.start();
try {
//只给线程三秒钟时间,不然就干掉该线程,执行主线程剩下的代码
Thread.sleep(3000);//让线程休眠三秒
interruptD1.interrupt();//让线程中断
System.out.println("sdfjslfjks");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4,7实现多线程的另一种方式
/*1,实现Runnable接口
* 2,重写run方法
* 3,创建自定义线程类对象
* 4,创建Thread对象并将自定义线程对象作为参数进行传递
* 5,start()*/
public class ThreadD1 implements Runnable {
@Override
public void run() {
for (int i=0;i<500;i++){
System.out.println(":"+i);
}
}
}class ThreadD1Demo{
public static void main(String[] args) {
ThreadD1 threadD1=new ThreadD1();
Thread thread=new Thread(threadD1);
}
}
案例:
/*多线程模拟电影院售票*/
public class SellTicket extends Thread{
public static int tickets=100;
/*为什么要设置成static,因为被静态修饰的成员变量可以被所有对象共享,并且只在类加载的时候进行一次初始化
* 而非是对象所有,在创建对象时被初始化*/
@Override
public void run() {
while(true){
if(tickets>-1){
System.out.println(getName()+"正在售票,剩余"+(tickets--)+"张票");
}
}
}
}
class TicketWindow{
public static void main(String[] args) {
SellTicket window1=new SellTicket();
SellTicket window2=new SellTicket();
SellTicket window3=new SellTicket();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
/*自定义三个线程模拟电影院三个窗口售票*/
public class SellTicket2 implements Runnable{
private static int tickets=100;
@Override
public void run() {
while(true){
if(tickets>-1){
System.out.println(Thread.currentThread().getName()+"正在售票,剩余"+(tickets--));
}
}
}
}
class TicketWindows{
public static void main(String[] args) {
//创建自定义类对象
SellTicket2 sellTicket2=new SellTicket2();
//将自定义类对象作为参数传入
Thread ticketWindow1=new Thread(sellTicket2,"窗口1");
Thread ticketWindow2=new Thread(sellTicket2,"窗口2");
Thread ticketWindow3=new Thread(sellTicket2,"窗口3");
ticketWindow1.start();
ticketWindow2.start();
ticketWindow3.start();
}
}
5,1解决问题–同步代码块
判断可能出现多线程安全问题的依据:
1,是否是多线程
2,是否有共享数据
3,是否有多条语句操作共享数据
思路:
考虑把这多条操作共享数据的语句进行包裹,当某一个程序在执行被包裹的语句时,其他线程不能执行这些语句。
怎么包裹?
java为我们提供了一种机制可以解决我们的需求:同步机制
格式:
synchronized(对象){
多条操作共享数据的语句;}
对象:任意对象,这个对象起到了一个锁的作用。要将对象提前创建,作为一个参数传到synchronized括号中。
同步代码块前提:
1,多线程
2,多个线程使用的是同一个锁对象
同步代码块的优缺点:
同步代码块的好处是解决了多线程的安全问题,但由于每一个线程在执行之前都要去判断一下锁对象,这样是很浪费资源的。会降低程序运行的效率。
将synchronized写在非静态方法声明上面,锁对象是谁?
this
将将synchronized写在静态方法声明上面,锁对象是谁?
是成员变量所在的class文件,类名.class,字节码文件对象(反射会讲)。
/*利用同步代码块来模拟电影院中三个窗口售票的情况*/
public class SynchronizedD implements Runnable {
private int tickets=100;
Object obj=new Object();
@Override
public void run() {
synchronized (obj) {
while(true){
if(tickets>-1){
try {
Thread.sleep(200);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售票,剩余票数" + (tickets--)+"张");
}
}
}
}
}
class TicketWindows1{
public static void main(String[] args) {
//创建自定义类对象
SynchronizedD ticketWindows=new SynchronizedD();
Thread ticketWindow1 = new Thread(ticketWindows,"窗口1");
Thread ticketWindow2 = new Thread(ticketWindows,"窗口2");
Thread ticketWindow3 = new Thread(ticketWindows,"窗口3");
ticketWindow1.start();
ticketWindow2.start();
ticketWindow3.start();
}
}
5,2 lock锁对象
概述:这是一个接口提供了比synchronized更广泛的功能
Lock的实现子类:
ReentranLock:
构造方法:
public ReentrantLock()
成员方法:
public void lock()
获取锁。
public void unlock()
试图释放此锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockD implements Runnable {
private int tickets=100;
Lock lock=new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();//上锁
if(tickets>-1){
try {
Thread.sleep(200);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售票,剩余票数" + (tickets--)+"张");
}
lock.unlock();//下锁
}
}
}
class TicketWindows2{
public static void main(String[] args) {
//创建自定义类对象
LockD ticketWindows=new LockD();
Thread ticketWindow1 = new Thread(ticketWindows,"窗口1");
Thread ticketWindow2 = new Thread(ticketWindows,"窗口2");
Thread ticketWindow3 = new Thread(ticketWindows,"窗口3");
ticketWindow1.start();
ticketWindow2.start();
ticketWindow3.start();
}
}
5.3死锁
什么是死锁?
死锁就是指两个或两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象。
/*死锁案例的演示*/
public class DeadLockD extends Thread{
private static Object lockA=new Object();
private static Object lockB=new Object();
private boolean flag;
public DeadLockD (boolean flag){
this.flag=flag;
}
@Override
public void run() {
if (flag) {
synchronized (lockA){
System.out.println("if lockA");
synchronized (lockB){
System.out.println("if lockB ");
}
}
}else {
synchronized(lockB){
System.out.println("else lockB");
synchronized(lockA){
System.out.println("else lockA");
}
}
}
}
}
class DeadLock{
public static void main(String[] args) {
DeadLockD d1=new DeadLockD(true);
DeadLockD d2=new DeadLockD(false);
d1.start();
d2.start();
}
}
问题:
1,重复数据
线程执行的随机性,cpu执行效率高
2,数据不匹配
cpu执行的原子性和线程的随机性
根本原因:
1,是否是多线程
2,是否有共享资源
3,是否有多条语句操作共享数据
解决方案:
加锁
6,线程组Thread-Group
package com.company.Thread;
/*线程组演示案例
* 默认情况下,所有线程都属于main线程组
* 设置线程组的好处:方便统一管理线程*/
public class ThreadGroupD implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class MyThreadGroup{
public static void main(String[] args) {
method1();
method2();
}
private static void method2() {
//构建一个新的线程组
ThreadGroup threadGroup = new ThreadGroup("新线程组");
ThreadGroupD t1 = new ThreadGroupD();
Thread thread1 = new Thread(threadGroup,t1, "李莫愁");
Thread thread2 = new Thread(threadGroup,t1, "公孙绿萼");
//输出两个线程组的名称
System.out.println(thread1.getThreadGroup().getName());
System.out.println(thread2.getThreadGroup().getName());
}
private static void method1() {
ThreadGroupD t1 = new ThreadGroupD();
Thread thread1 = new Thread(t1, "李莫愁");
Thread thread2 = new Thread(t1, "公孙绿萼");
//分别返回两个线程所在的线程组
ThreadGroup threadGroup1=thread1.getThreadGroup();
ThreadGroup threadGroup2=thread2.getThreadGroup();
System.out.println( threadGroup1.getName() );
System.out.println( threadGroup2.getName() );
//通过链式编程获取main方法的线程组名称
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
7,线程池
**概述:**程序启动一个新线程成本是比较高的,因为要一直与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要大量创建生命周期较短的线程时,就更应该使用线程池。
**好处:**线程池里面的每一个代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象继续使用。
package com.company.Thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*线程池对象演示*/
public class ThreadPoolD implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class MyTP{
public static void main(String[] args) {
//创建一个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);//放两个线程的线程池
//创建两个线程
threadPool.submit(new ThreadPoolD());
threadPool.submit(new ThreadPoolD());
//停止先前的线程并不会接受新的任务
threadPool.shutdown();
}
}
8,Callable泛型接口
/*线程实现的第三种方式
* Callable
* 好处:
* 1,可以有返回值
* 2,可以抛出
* 弊端:代码比较复杂,所以一般不用*/
public class CallableD implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return null;
}
}
class MyCallable{
public static void main(String[] args) {
//创建两个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
threadPool.submit(new CallableD());
threadPool.submit(new CallableD());
//关闭线程池
threadPool.shutdown();
}
}
/*带泛型的Callable接口*/
class CallableD2 implements Callable<Integer>{
private int num;
public CallableD2(int num){
this.num=num;
}
@Override
public Integer call() throws Exception {
int result=0;
for (int i = 0; i <= num; i++) {
result +=i;
}
return result;
}
}
class MyCallable2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建两个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Future<Integer> result1 = threadPool.submit(new CallableD2(5));
Future<Integer> result2 = threadPool.submit(new CallableD2(100));
System.out.println(result1.get());
System.out.println(result2.get());
//关闭线程池
threadPool.shutdown();
}
}
9,定时器
概述:可以指定程序在某个规定的时间做规定的工作(可以是重复的工作),是一个线程工具,可用来调度多个定时任务,以后台的方式执行。
TImerTask:可以由定时器指定一次或重复执行的任务
注意:cancel方法要在run方法中调用
import java.util.Timer;
import java.util.TimerTask;
public class TimerTaskD extends TimerTask {
Timer time;
//创建一个Timer成员变量作为参数,才可以正确使用cancel方法
public TimerTaskD(Timer time) {
this.time = time;
}
public TimerTaskD() {
}
@Override
public void run() {
System.out.println("有内鬼,终止交易!");
time.cancel();
}
}
class TaskDemo{
public static void main(String[] args) {
//创建定时器对象
Timer timer=new Timer();
//执行Task任务
timer.schedule(new TimerTaskD(timer),3000,1000);
}
}
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*定时删除文件及文件夹*/
public class DeleteFolder extends TimerTask {
private Timer timer;
public DeleteFolder(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
File file =new File("/Users/zhoukun/Desktop/java/testDoc");
deleteFolder(file);
timer.cancel();
}
private void deleteFolder(File srcFolder) {
//获取指定文件夹下所有的File对象
File [] files =srcFolder.listFiles();
if(files!=null){
for(File file:files){
if(file.isDirectory()){//文件夹
deleteFolder(file);
}else{//文件
System.out.println(srcFolder.getName()+"------"+file.delete());
}
}
}
}
}
class DeleteFolderDemo{
public static void main(String[] args) {
//创建Timer对象
Timer timer = new Timer();
//设置执行时间
String time = "2020-12-13 09:59:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = simpleDateFormat.parse(time);
} catch (ParseException e) {
e.printStackTrace();
}
timer.schedule(new DeleteFolder(timer),date);
}
}
10,匿名内部类实现多线程
package com.company.Thread;
/*匿名内部类实现线程*/
public class Anonymity {
public static void main(String[] args) {
//方式1:继承Thread类
new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}.start();
//方式2:实现runnable接口
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}){
}.start();
//方式3:面试可能会考,会走Thread的run方法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}){
@Override
public void run() {
System.out.println("world");
}
}.start();
}
}
总结
sleep方法和wait方法的区别?
sleep 必须指定休眠时间,并且不释放锁
wait可以不用指定时间,并且可以释放锁
为什么notify,wait,notyfyAll等方法都定义在object类里面?
因为这些方法的调用都是依赖于锁对象存在的,而同步代码块的锁对象是任意锁。
而object代码的任意对象都是锁对象,所以就将这些方法定义在object类里面。
线程的生命周期:
理想情况:创建-------就绪------执行------死亡
一般情况:创建-------就绪------阻塞------就绪-------执行------死亡