多线程
程序(静态)
一段静态的代码
进程(动态)
正在运行的一个程序
自身的产生、存在和消亡–生命周期
进程作为资源分配的单位
线程
进程可细化为线程–一条执行路径
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器
内存:一个进程占用独立的方法区和堆,一个线程占用独立的虚拟机栈和程序计数器,多个线程共享同一进程的方法区和堆的内存
单核CPU与多核CPU:前者时间单元短,后者发挥多线程效率高
并行与并发
并行:多个CPU同时执行多个任务
并发:一个CPU采用时间片同时执行多个任务
线程的创建(继承Thread类)
/**
1.创建一个继承于Thread类的子类
2.重写Thread类的run()
3.创建Thread类的子类对象
4.通过该对象调用start()
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(i);
}
}
}
}
start()的作用:启动当前线程;调用当前线程的run()方法,而且每个线程的start()只能使用一次
线程的常用方法
void start();
run();一般需要重写
String getName();获取线程名字
void setName(String name);设置线程名字
static Thread currentThread();获取当前线程
static void yield();让步,释放当前CPU的执行
join();在线程A中调用线程B的join方法,此时线程A进入阻塞状态,直到线程B完成
static void sleep(long millistime);让当前线程在指定的时间内进入阻塞状态
isAlive();判断当前线程是否存活
线程的调度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcFgfzPt-1600600699144)(C:\Users\wade\Desktop\后端开发\线程调度.jpg)]
1.同优先级线程组成先进先出队列,使用时间片策略
2.对高优先级,使用优先调度的抢占式策略
优先级1-10,默认为5
getPriority();获取当前线程优先级
setPriority();设置当前线程优先级
线程创建时继承父类线程优先级
低优先级只是获得调度的概率低,并不是在高优先级完成后执行
线程的创建(实现Runnable接口)
/**
* 实现Runnable接口
* 1.创建一个实现Runnable接口的类
* 2.实现类去实现Runnable接口的抽象方法run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.调用Thread类的对象的start方法
*/
public class RunnableTest {
public static void main(String[] args) {
RunnableDemo r1=new RunnableDemo();
new Thread(r1).start();
}
}
class RunnableDemo implements Runnable{
private static int nums=100;
@Override
public void run() {
while(nums>0){
System.out.println(nums);
nums--;
}
}
}
两种方式的比较
类已有Thread以外的父类,应使用Runnable接口(开发中优先选择)
Runnable接口:没有单继承的局限性;可共享资源
线程的安全问题
当一个线程在操作共享数据时,其他线程不能参与进来,直到线程a操作万其他线程才能获取权限,即使线程a出现了阻塞也不能改变。
通过同步机制解决线程的安全问题
同步机制
共享数据:多个线程共同操作的变量
1.同步代码块
synchronized(同步监视器){
//需要被同步的代码,用于操作共享数据的代码
}
同步监视器:俗称锁,任何一个类的对象(多个线程必须共用一把锁)都可以作为锁
//使用同步代码块解决了实现Runnable接口的类的线程安全问题
class RunnableDemo implements Runnable{
private int nums=100;
private Object obj=new Object();
@Override
public void run() {
while(true) {
synchronized (obj) {//可以用this
if (nums > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + nums);
nums--;
} else break;
}
}
}
}
//使用同步代码块解决了继承Thread类的类的线程安全问题
class window extends Thread{
private static int nums=100;
private static Object obj=new Object();
public window(String name) {
super(name);
}
@Override
public void run() {
while(true) {
synchronized(obj){
if(nums>0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + nums);
nums--;
}
else break;
}
}
}
}
2.同步方法
如果操作共享数据的代码完整的声明在一个方法中,将此方法声明为同步的
非静态的同步方法的同步监视器是this
静态的同步方法的同步监视器是该方法所在的类class
//使用同步方法解决了实现Runnable接口的类的线程安全问题
class RunnableDemo implements Runnable{
private int nums=100;
private Object obj=new Object();
@Override
public void run() {
while(show()) {
}
}
private synchronized boolean show(){//监视器是this
if (nums > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + nums);
nums--;
return true;
}
else return false;
}
}
//使用同步方法解决了继承Thread类的类的线程安全问题
class window extends Thread{
private static int nums=100;
private static Object obj=new Object();
public window(String name) {
super(name);
}
@Override
public void run() {
while(true) {
show();
}
}
private static synchronized void show(){//必须是静态
synchronized(obj){
if(nums>0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + nums);
nums--;
}
}
}
}
3.线程的死锁问题
不同的线程分别占用对方需要的同步资源不放手
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
1)**互斥条件:**指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)**请求和保持条件:**指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)**不剥夺条件:**指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)**环路等待条件:**指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
同步锁(LOCK)
使用ReentrantLock类实现Lock
public class ReentrantLockTest {
public static void main(String[] args) {
window w1=new window();
Thread t1=new Thread(w1);
Thread t2=new Thread(w1);
Thread t3=new Thread(w1);
t1.setName("一");
t2.setName("二");
t3.setName("三");
t1.start();
t2.start();
t3.start();
}
}
class window implements Runnable{
private int ticks=100;
//第一步实例化
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用lock方法
lock.lock();
if(ticks>0){
System.out.println(Thread.currentThread().getName()+ticks);
ticks--;
}
else break;
}
finally {
//3.调用unlock方法
lock.unlock();
}
}
}
}
异同
Lock是手动的,使用lock方法(上锁)和unlock方法(解锁),相对灵活;
synchronized是自动的,使用到同步监视器,自动地释放同步监视器
线程的通信
使用wait()方法使得调用该方法的线程进入阻塞状态且会释放锁
使用notify()|notifyall()唤醒阻塞的线程
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1=new Thread(number);
Thread t2=new Thread(number);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int number=1;
@Override
public void run() {
while(true){
synchronized (this) {
notify();
if(number<=100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else break;
}
}
}
}
wait(): 当前线程进入阻塞状态,并释放同步监视器
notify(): 唤醒被wait()的一个线程,优先级高先被唤醒
notifyAll(): 唤醒所有被wait()的线程
以上三个方法必须使用在同步代码块或同步方法中,且从同步监视器中调用,他们定义在Object类中
sleep()和wait()的异同
相同:让线程进入阻塞状态
不同:1.两个方法声明位置不同,前者是在Thread类中,后者在Object类中
2.前者不释放锁,后者释放锁
3.前者可以在任何需要的地方使用,后者必须在同步代码块或同步方法中
生产者与消费者例题
线程的创建(实现Callable接口)
/**
* 1.创建实现Callable接口的实现类
* 2.将call()方法实现,可有返回值和抛出异常
* 3.创建Callable接口实现类的对象
* 4.将此对象传递到FutureTask构造器中,创建FutureTask对象
* 5.将FutureTask对象传递到Thread类的构造器中,创建Thread类并调用start方法
* 6.如需返回值,可使用FutureTask的get函数
*/
public class ThreadNew {
public static void main(String[] args) {
NumThread numThread=new NumThread();
FutureTask futureTask = new FutureTask(numThread);
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class NumThread implements Callable{
@Override
public Object call() throws Exception {
int sum=0;
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
Callable的优势之处:有返回值、可抛出异常、支持泛型
线程的创建(使用线程池)
class NumberThread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService Service = Executors.newFixedThreadPool(10);
//2.执行指定线程的操作。需要提供实现Runnable接口或Callable接口的类的对象
Service.execute(new NumberThread());//适合使用于Runnable
Service.execute(new NumberThread1());//适合使用于Runnable
//3.关闭线程池
Service.shutdown();
//Service.submit();//适合使用于Callable
}
}
优点:提高响应速度(减少了创建线程的时间)、降低资源的消耗(重复利用线程池线程)、便于线程管理(corePoolSize池大小、maximumPoolSize最大线程数、keepAliveTime线程空置最大时长)
创建多线程有四种方式
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
ExecutorService Service = Executors.newFixedThreadPool(10);
//2.执行指定线程的操作。需要提供实现Runnable接口或Callable接口的类的对象
Service.execute(new NumberThread());//适合使用于Runnable
Service.execute(new NumberThread1());//适合使用于Runnable
//3.关闭线程池
Service.shutdown();
//Service.submit();//适合使用于Callable
}
}
优点:提高响应速度(减少了创建线程的时间)、降低资源的消耗(重复利用线程池线程)、**便于线程管理(corePoolSize池大小、maximumPoolSize最大线程数、keepAliveTime线程空置最大时长)**
### 创建多线程有四种方式
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
4.使用线程池