JAVA-多线程

1.1多线程是什么


1.进程:当一个程序进入内存运行,即变成一个进程。
JAVA-多线程_第1张图片

2.线程:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
JAVA-多线程_第2张图片
3.单线程和多线程的区别
JAVA-多线程_第3张图片

1.2程序运行原理


1.分时调度:所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
2.抢占式调度:优先让优先级高的使用CPU,如果优先级相同,则随机使用
!!!多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高

1.3主线程和子线程的关系


在启动JVM后,虚拟机会从main方法开始执行代码,一直把main方法的代码执行结束,这就是主线程。如果在main方法的执行过程中遇到循环时间比较长的代码,那么在循环之后的其他代码是不会马上被执行的,这时候就要用多线程。
1.情况1(常见):主线程和子线程互不影响

public class TestThread{
     
    public static void main(String[] args) throws InterruptedException {
      //主线程
        System.out.println("主线程启动。。。。");
        Thread thread = new Thread(new ChildThread()); // 创建子线程
        thread.start(); //子线程开始运行
        System.out.println("主线程结束。。。。");
    }
}
class ChildThread implements Runnable{
       //重写子线程的run方法
    @Override
    public void run() {
     
        try {
                   //异常处理,括号内的为可能会出错的代码
            System.out.println("子线程启动。。。。");
            Thread.sleep(5000);
            System.out.println("子线程结束。。。。");
        } catch (InterruptedException e) {
      //(异常类型 异常对象引用)
            e.printStackTrace();  //用于处理异常的代码
        }
    }
}


JAVA-多线程_第4张图片

2.情况2:主线程开启了子线程,但是主线程结束,子线程也随之结束(守护线程)

public class TestThread{
     
    public static void main(String[] args) throws InterruptedException {
     
        System.out.println("主线程启动。。。。");
        Thread thread = new Thread(new ChildThread());
        //thread.setDaemon(true);
        thread.start();
        System.out.println("主线程结束。。。。");
    }
}
class ChildThread implements Runnable{
     
    @Override
    public void run() {
     
        try {
     
            System.out.println("子线程启动。。。。");
            ThirdThread thiredThread = new ThirdThread(); //创建孙子线程
            thiredThread.setDaemon(true);//将孙子线程(thiredThread)设为子线程(ChildThread)的守护线程,所以子线程结束,孙子线程无论是否运行完都会结束
            /*!!!对于运行中的线程,不能设置为守护线程。不然会抛出java.lang.IllegalThreadStateException异常
            */
            thiredThread.start();
            Thread.sleep(1000);
            System.out.println("子线程结束。。。。");
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
    }
}
class ThirdThread extends Thread{
     
    @Override
    public void run() {
     
        System.out.println("孙子线程启动。。。。");
        try {
     
            Thread.sleep(5000);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        System.out.println("孙子线程结束。。。。");
    }
}


JAVA-多线程_第5张图片

3.情况3:主线程开了一个子线程,主线程必须等子线程结束才能结束

public class TestThread{
     
    public static void main(String[] args) throws InterruptedException {
     
        System.out.println("主线程启动。。。。");
        Thread thread = new Thread(new ChildThread());
        thread.start();
        thread.join();    //线程阻塞,让主线程等子线程结束,才继续执行
        System.out.println("主线程结束。。。。");
    }
}
class ChildThread implements Runnable{
     
    @Override
    public void run() {
     
        try {
     
            System.out.println("子线程启动。。。。");
            Thread.sleep(5000);
            System.out.println("子线程结束。。。。");
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
    }
}


JAVA-多线程_第6张图片

总结:对于CPU来说,不存在主线程和子线程之分,线程是CPU调度的基本单位。
setDaemon 和 join改变了主线程和子线程的关系,实际应该是JVM接口代码做了处理干扰了线程的生命周期。
守护线程和非守护线程本质上没区别,但是虚拟机如果都是守护线程,虚拟机会退出,只要虚拟机还剩一个非守护线程,虚拟机都不会退出。

1.4多线程的内存图解


多线程执行时,在栈内存中,每一个执行线程都有一片自己的所属的栈内存空间。进行方法的压栈和弹栈
JAVA-多线程_第7张图片

2.1创建--继承Thread

package cn.itcats.thread.Test1;



//方式1:继承Thread,重写run方法
public class Demo1 extends Thread{
          //Demo1类继承Thread
	//重写的是父类Thread的run()
	public void run() {
     
		System.out.println(getName()+"is running...");
	}
	
	public static void main(String[] args) {
     
		Demo1 demo1 = new Demo1();      //创建线程
		Demo1 demo2 = new Demo1();
		demo1.start();        //线程启动
		demo2.start();
	}

}

JAVA-多线程_第8张图片

2.2实现Runnable接口,重写run

package cn.itcats.thread.Test1;

//实现Runnable接口,重写run()
//实现Runnable接口只是完成了线程任务的编写,启动线程,需要new Thread(Runnable target),再调用thread对象调用start()启动
//使用面向接口,将任务与线程分离,有利于解耦
public class Demo2 implements Runnable{
      //实现Runnable接口
	public void run() {
     
		System.out.println("implements Runnable is running");//重写run()
	}
	public static void main(String[] args) {
     
		Thread thread1 = new Thread(new Demo2());//创建线程
		Thread thread2 = new Thread(new Demo2());
		thread1.start();//开启线程
		thread2.start();
	}

}

JAVA-多线程_第9张图片

2.3匿名内部类

package cn.itcats.thread.Test1;

//方式3匿名内部类,创建启动线程次数较少的情况,书写更便捷
public class Demo3 {
     
	public static void main(String[] args) {
     
		//方式1:相当于继承了Thread类,作为子类重写run()实现,Demo1的缩减版
		new Thread() {
     
			public void run() {
     
				System.out.println("匿名内部类创建线程方式1。。。");
			}
		}.start();
		
		//方式2:实现Runnable,Runnable作为内部类,Demo2的缩减版
		new Thread(new Runnable() {
     
			public void run() {
     
			System.out.println("匿名内部类创建线程方式2...");
		}
	}).start();//我的理解是直接在类里面创建线程直接启动

}
}

JAVA-多线程_第10张图片

2.4实现Callable接口

package cn.itcats.thread.Test1;
 
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
 
/**
 * 方式4:实现Callable 接口
 * 含返回值且可抛出异常的线程创建启动方式
 */
public class Demo5 implements Callable<String>{
      //实现Callable接口
 
	public String call() throws Exception {
     
		System.out.println("正在执行新建线程任务");
		Thread.sleep(2000);
		return "新建线程睡了2s后返回执行结果"; //返回值
	}
 
	public static void main(String[] args) throws InterruptedException, ExecutionException {
     
		Demo5 d = new Demo5();
		/*	call()只是线程任务,对线程任务进行封装
			class FutureTask implements RunnableFuture
			interface RunnableFuture extends Runnable, Future
		*/
		
		//Callable接口将task传给Futuretask
		FutureTask<String> task = new FutureTask<>(d);
		//建立一个Thread对象调用start,启动
		Thread t = new Thread(task);   
		t.start();
		System.out.println("提前完成任务...");
		//获取任务执行后返回的结果,可以外部异步获得子线程的运行结果
		String result = task.get();
		System.out.println("线程执行结果为"+result);
	}
	
}

JAVA-多线程_第11张图片

2.5Timer定时任务*

package cn.itcats.thread.Test1;
import java.util.Timer;
import java.util.TimerTask;

//创建启动线程之Timer定时任务,使用方法与Demo3相似
public class Demo6 {
     
	public static void main(String[] args) {
     
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
     
			public void run() {
     
				System.out.println("定时任务延迟1,每隔1000ms执行一次");
			}
		},1,1000);
	}

}
//当任务执行完毕或想执行不同任务时,实现起来比较麻烦。框架“quartz”

JAVA-多线程_第12张图片

2.6创建线程池


#2.6.1线程池概念
JAVA-多线程_第13张图片

2.6.2实现代码1

package cn.itcats.thread.Test1;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class Demo7 {
     
	public static void main(String[] args) {
     
		//创建带有10个线程的线程池,若创建的是CachedThreadPool则不需要指定线程数量,线程数量取决于线程任务,不够用则创建线程,够用则回收
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		for(int i=0;i<10;i++) {
     
			threadPool.execute(new Runnable() {
     
				public void run() {
     
					System.out.println(Thread.currentThread().getName()+"is running");
				}
			});
		}
		threadPool.shutdown();//销毁线程池,如若不销毁,会出现运行完毕,但程序并未停止
	}

}

JAVA-多线程_第14张图片

2.6.3线程池--Runnable接口

package cn.itcats.thread.Test1;

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

public class ThreadPoolDemo {
     
	public static void main(String[] args) {
     
		ExecutorService service = Executors.newFixedThreadPool(2);  //创建线程池对象,包含2个线程对象
		MyRunnable r = new MyRunnable(); //创建Runnable实例对象,需要另外创建一个MyRunnable实现类
		Thread t = new Thread(r); //创建线程对象
		t.start(); //调用MyRunnable中的run()
		service.submit(r);//从线程池中获取线程对象,然后调用MyRunnable中的run()
		service.submit(r);//再获取个线程对象,调用MyRunnable中的run()
		service.submit(r);
		for(int i=0;i<10;i++) {
      //从线程池获取10个线程对象,然后调用MyRunnable中的run()
			service.submit(r);
			//注意!!!sumbit()方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用的线程又归还到线程池中
		}
		service.shutdown();  //关闭线程池
	}

}


package cn.itcats.thread.Test1;
public class MyRunnable implements Runnable {
     
@Override
//开始写接口实现类的run()方法
public void run() {
     

System.out.println("1111111");

 

try {
     

Thread.sleep(2000);

} catch (InterruptedException e) {
     

e.printStackTrace();

}

System.out.println("222222222: " +Thread.currentThread().getName());

System.out.println("333333333333333");

}

}

JAVA-多线程_第15张图片

2.6.4线程池--Callable

package cn.itcats.thread.Test1;

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

public class ThreadPoolDemo1 {
     
	public static void main(String[] args) throws InterruptedException,ExecutionException{
     
		ExecutorService threadPool = Executors.newFixedThreadPool(2); //创建线程池,包含两个线程对象
		MyCallable c = new MyCallable(100,200);// 创建MyCallable接口子类对象
		MyCallable c2 = new MyCallable(10,20);
		Future<Integer> result = threadPool.submit(c); //Future结果对象
		Integer sum = result.get(); //Future的get方法所返回的结果类型
		System.out.println("sum="+sum);
		result = threadPool.submit(c2);
		sum = result.get();
		System.out.println("sum="+sum);
		
	}

}

package cn.itcats.thread.Test1;

import java.util.concurrent.Callable;

public  class MyCallable implements Callable<Integer> {
     
	//成员变量
	int x = 5;
	int y = 3;
	//构造方法
	public MyCallable() {
     
		
	}
	public MyCallable(int x,int y) {
     
		this.x=x;
		this.y=y;
	}
	@Override
	public Integer call() throws Exception{
     
		return x+y;
	}
}

JAVA-多线程_第16张图片

2.7创建线程总结


1.继承Thread线程类:
1.自定义类继承Thread;
2.在自定义类中重写Thread类的run方法
3.创建线程对象
4.调用start方法,启动线程
2.实现Runnable接口:
1.创建线程任务类,实现Runnable接口
2.在线程任务类中,重写接口中的run方法
3.创建线程任务类对象
4.把线程任务类对象作为Thread类构造方法的参数使用来创建线程对象
5.调用start方法

3.1线程安全问题


例子:模拟电影院的卖票,实现多个窗口同时卖100张票
窗口:用线程对象模拟
票:Runable接口子类来模拟

public class ThreadDemo {
     

public static void main(String[] args) {
     

//创建票对象

Ticket ticket = new Ticket();

 

//创建3个窗口

Thread t1  = new Thread(ticket, "窗口1");

Thread t2  = new Thread(ticket, "窗口2");

Thread t3  = new Thread(ticket, "窗口3");

 
//同时进行卖票操作
t1.start();

t2.start();

t3.start();

}

}
public class Ticket implements Runnable {
     

//共100票

int ticket = 100;

 

@Override

public void run() {
     

//模拟卖票

while(true){
     

if (ticket > 0) {
     

//模拟选坐的操作

try {
     

Thread.sleep(1);

} catch (InterruptedException e) {
     

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);

}

}

}

}

JAVA-多线程_第17张图片
出现了结果为

3.2.1同步代码块

同步代码块:在代码块声明上 加上synchronized
synchronized(锁对象){
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能保证线程安全

package cn.itcats.thread.Test1;

public class Ticket implements Runnable{
     
	int ticket = 100;
	Object lock = new Object();//定义锁对象
	@Override
	public void run() {
     
		while(true) {
     
			/*synchronized(锁对象){
			 * 可能会产生问题的代码
			}
			同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能保证线程安全
			*/
			synchronized(lock) {
      //同步代码块,
			if(ticket>0) {
     
				try {
     
					Thread.sleep(1);
				}catch(InterruptedException e) {
     
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket--);
			}
		}
	}
	}	

}

package cn.itcats.thread.Test1;

public class ThreadDemo {
     
	public static void main(String[] args) {
     
		Ticket1 ticket = new Ticket();
		Thread t1 = new Thread(tickets,"窗口1");
		Thread t2 = new Thread(tickets,"窗口2");
		Thread t3 = new Thread(tickets,"窗口3");
		t1.start();
		t2.start();
		t3.start();
	}

}

3.2.1同步方法

同步方法:在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}

package cn.itcats.thread.Test1;

public class Tickets implements Runnable {
     
	int tickets = 100;
	Object lock = new Object();
	public void run() {
     
		while(true) {
     
			method();
		}
	}
	public synchronized void method() {
     
		if(tickets>0) {
     
			try {
     
				Thread.sleep(10);
			}catch(InterruptedException e) {
     
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"正在卖票:"+tickets--);
		}
	}
	

}


package cn.itcats.thread.Test1;

public class ThreadDemo {
     
	public static void main(String[] args) {
     
		Ticket1 tickets = new Ticket1();
		Thread t1 = new Thread(tickets,"窗口1");
		Thread t2 = new Thread(tickets,"窗口2");
		Thread t3 = new Thread(tickets,"窗口3");
		t1.start();
		t2.start();
		t3.start();
	}

}

3.3死锁


当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步,这时容易引发现象:程序出现无线等待,即死锁

synchronized(A锁){
synchronized(B锁){

}
}

package cn.itcats.thread.Test1;
//定义锁对象类
public class MyLock {
     
	public static final Object lockA = new Object();
	public static final Object lockB = new Object();

}

package cn.itcats.thread.Test1;

import java.util.Random;

public  class ThreadTask implements Runnable{
     
	int x = new Random().nextInt(1);//随机取0-1的数值
	//指定线程要执行的任务代码
	@Override
	public void run() {
     
		System.out.println("x= "+x);
		while(true) {
     
			if(x%2==0) {
     
				synchronized(MyLock.lockA) {
     
					System.out.println("if-LockA");
					synchronized(MyLock.lockB) {
     
						System.out.println("if-LockB");
						System.out.println("123231");
					}
				}
			}else {
     
				synchronized(MyLock.lockB) {
     
					System.out.println("else-LockB");
					synchronized(MyLock.lockA) {
     
						System.out.println("else-LockA");
						System.out.println("6666666");
					}
				}
			}
			x++;
		}
	}

}

package cn.itcats.thread.Test1;
//测试类
public class ThreadDemo2 {
     
	public static void main(String[] args) {
     
		ThreadTask task = new ThreadTask();
		Thread t1 = new Thread(task);
		Thread t2 = new Thread(task);
		t1.start();
		t2.start();
	}

}

3.4Lock接口


查阅API,查阅Lock接口描述,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
在这里插入图片描述

package cn.itcats.thread.Test1;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public  class Ticket1 implements Runnable{
     
	int ticket1 = 100;
	Lock ck = new ReentrantLock();//创建Lock锁对象
	@Override
	public void run() {
     
		//模拟卖票
		while(true) {
     
			ck.lock();//获取锁
			if(ticket1>0) {
     
				//模拟选座的操作
				try {
     
					Thread.sleep(10); //等待10s。可能会出错的代码
				}catch(InterruptedException e) {
     //(异常类型 异常对象引用)
					e.printStackTrace(); //用于处理异常的代码
				}
				System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket1--);
			}
			ck.unlock();//释放锁
		}
	}
	

}
package cn.itcats.thread.Test1;

public class ThreadDemo {
     
	public static void main(String[] args) {
     
		Ticket1 tickets = new Ticket1();
		Thread t1 = new Thread(tickets,"窗口1");
		Thread t2 = new Thread(tickets,"窗口2");
		Thread t3 = new Thread(tickets,"窗口3");
		t1.start();
		t2.start();
		t3.start();
	}

}

@[3.5等待唤醒机制]

等待唤醒机制所涉及到的方法:

l wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

l notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

l notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

仔细查看JavaAPI之后,发现这些方法 并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?

因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。
JAVA-多线程_第18张图片
JAVA-多线程_第19张图片
如上图说示,输入线程向Resource中输入name ,sex , 输出线程从资源中输出,先要完成的任务是:

l 1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();

l 2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。

package cn.itcats.thread.Test1;

//模拟资源类
public class Resource {
     
	private String name;
	private String sex;
	private boolean flag = false; //定义标记位
	 public synchronized void set(String name,String sex) {
      //同步{}内的为可能不安全的代码
		 if(flag) //当标记为false时
			 try {
     
				 wait();
			 }catch(InterruptedException e) {
     
				 e.printStackTrace();
			 }
		 this.name = name;//设置成员变量
		 this.sex = sex;
		 flag = true;//设置后Resource中有值,将标记改为true
		 this.notify();
	 }
	 public synchronized void out() {
     
		 if(!flag)//标记为true
			 try {
     
				 wait();
			 }catch(InterruptedException e) {
     
				 e.printStackTrace();
			 }
		 System.out.println("姓名:"+ name +",性别: "+ sex);
		 flag = false;//将标记改为false
		 this.notify();//唤醒output
	 }

}

package cn.itcats.thread.Test1;

//输入线程任务类
public  class Input implements Runnable {
     
private Resource r;

public Input(Resource r) {
     
	this.r = r; //设置成员变量
}
@Override
public void run() {
     
	int count = 0;
	while(true) {
     
		if(count == 0) {
     
			r.set("小明", "男生");
		}else {
     
			r.set("小红","女生");
		}
		count = (count+1)%2;//在两个数据之间进行切换
	}
}

}
package cn.itcats.thread.Test1;

//输出线程任务类
public  class Output implements Runnable {
     
	private Resource r;
	public Output(Resource r) {
     
		this.r = r;
	}
	@Override
	public void run() {
     
		while (true) {
     
			r.out();
		}
	}

}

package cn.itcats.thread.Test1;
//测试类
public class ResourceDemoTest {
     
	public static void main(String[] args) {
     
		Resource r = new Resource();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
		
		
	}

}

3.5总结


l 同步锁

多个线程想保证线程安全,必须要使用同一个锁对象

l 同步代码块

     synchronized (锁对象){

可能产生线程安全问题的代码

}

同步代码块的锁对象可以是任意的对象

l 同步方法

     public synchronized void method()

          可能产生线程安全问题的代码

}

同步方法中的锁对象是 this

l 静态同步方法

public synchronized void method()

          可能产生线程安全问题的代码

}

静态同步方法中的锁对象是 类名.class

l 多线程有几种实现方案,分别是哪几种?

a, 继承Thread类

b, 实现Runnable接口

c, 通过线程池,实现Callable接口

l 同步有几种方式,分别是什么?

a,同步代码块

b,同步方法

静态同步方法

l 启动一个线程是run()还是start()?它们的区别?

启动一个线程是start()

区别:

start: 启动线程,并调用线程中的run()方法

run : 执行该线程对象要执行的任务

l sleep()和wait()方法的区别

sleep: 不释放锁对象, 释放CPU使用权

在休眠的时间内,不能唤醒

wait(): 释放锁对象, 释放CPU使用权

在等待的时间内,能唤醒

l 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

锁对象可以是任意类型的对象

此文参考于https://www.cnblogs.com/jmsjh/p/7762167.html

你可能感兴趣的:(JAVA,多线程,java,thread)