多线程详解


多线程


程序,线程,进程的关系

1.1:程序:是为了完成特定的任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
1.2:进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。------生命周期
1.3:线程:进程可进一步细化为线程,是一个程序内部的一条执行路劲。

详细:进程可以细化为多个线程,每个线程,拥有自己独立的:栈,程序计数器:多个进程,共享同一个进程中的结构:方法区、堆
在这里插入图片描述

2.IDEA中的Project 和Module的理解
在IDEA中创建的顶级工程是Project,module为项目的子模块,project相当于eclipse的workspace,module相当于eclipse的项目。
3. 并发与并行
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
4.Thread类和Runnbale接口启动线程的两种方式的对比:
4.1 开发中:优先选择,实现Runble接口的方式
原因:1.实现的方式没有累的单继承性的局限性
2.实现的方式更适合来处理多线程共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run();将线程要案处理的逻辑声明在run()中。
目前两种方式,想要启动线程,都是调用Thread类中的start()方法。

eg:举例说明线程遇到的问题(售票线程。利用实现Runble接口的方式)

/**
 * 例子:创建三个窗口买票,总票数为100,使用实现Runnbale接口的方式
 * 
 * 1.问题:卖票过程中出现了重票和错票----》出现了线程安全问题
 * 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时。其他线程参与进来,也操作车票
 * 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,知道线程a操作完时,
         其他线程才可以开始操作ticket。
 * 这种情况即使线程a出现了阻塞,也不能被改变。
 * 4.在java中,我们通过同步机制,来解决线程的安全问题
 * 方式1:同步代码块
 * synchronized(同步监视器){
 *      //需要被同步的代码
 * }
 * 说明:1.操作共享数据的代码,即为需要被同步的代码
 *     2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
 *     3.同步监视器:俗称:锁。任何一个类的对象,都可以充当锁;	
 * 			要求:多个线程必须要共同用一把锁;
			补充:在实现Runnable接口创建多线程的方式中,我肯可以考虑使用this充当同步监视器
 * 方式2:同步方式
 * 
 * 5.同步的方式,解决了线程的安全问题。
 *    操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
 *  关于同步方法的总结:
 *	 1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
 *	 2.非静态的同步方法,同步监视器是:this
 *	   静态的同步方法,同步监视器是:当前类本身
 *
 */
 -------------------------------------------------------------------------------------------------
 同步代码块解决线程问题
 -------------------------------------------------------------------------------------------------
public class windwos implements Runnable{
private	int ticket=100;
Object obj=new Object();
	@Override
	public void run() {
		while(true) {
			/*	synchronized (this) {//可使用当前对象。因其是唯一的
			*/
			synchronized (obj) {
			if(ticket>0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
                 System.out.println(Thread.currentThread().getName()+"售票处:"+"票号"+ticket);
                    ticket--;
			}else {
				break;
			}
			}
		}
	}
}
实现Runble接口的方式的测试类:
	public static void main(String[] args) {
		windwos sd=new windwos();
		Thread thread1=new Thread(sd);
		Thread thread2=new Thread(sd);
		Thread thread3=new Thread(sd);
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		thread3.setName("窗口3");
		thread1.start();
		thread2.start();
		thread3.start();
	}

eg:同步代码块解决继承Thread类的线程安全问题

/**
 * 使用同步代码块解决继承Thread类的线程安全问题
 * 例子:创建三个窗口买票,总票数为100,使用继承Thread类的方式
 * 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类充当同步监视器
 * 对于继承Thread类的窗口线程(三个窗口调用时),票的总数和线程锁都是是确定唯一的,用static方法修饰、
 */
public class windows extends Thread{
private	static int ticket=100;
private static Object obj=new Object();
	@Override
	public void run() {
		while(true) {
		//	synchronized(obj) {//正确的
		 synchronized(windows.class) {//Class class=windows.class,windows.class类只会加载一次
		//错误的方式:this代表这thread1,thread2,thread3三个对象
		//synchronized(this) {
			if(ticket>0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"售票处:"+"票号"+ticket);
				ticket--;
			}else {
				break;
			}
			}
		}
	}
}

继承Thread方式的测试类:

public static void main(String[] args) {
	windows thread1=new windows();
	windows thread2=new windows();
	windows thread3=new windows();
	thread1.setName("窗口1");
	thread2.setName("窗口2");
	thread3.setName("窗口3");
	thread1.start();
	thread2.start();
	thread3.start();
}

使用同步方式解决线程问题

启动线程测试类

public static void main(String[] args) {
		windows sd=new windows();
		Thread thread1=new Thread(sd);
		Thread thread2=new Thread(sd);
		Thread thread3=new Thread(sd);
		thread1.setName("窗口1");
		thread2.setName("窗口2");
		thread3.setName("窗口3");
		thread1.start();
		thread2.start();
		thread3.start();
	}	

采用同步方式进行,解决实现Runnable接口方式的线程问题

public class windows implements Runnable{
	private	int ticket=100;
		@Override
		public void run() {
			while(true) {
				show();
			}
		}
		private synchronized void show() {//这里的锁相当于this
			if(ticket>0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"售票处:"+"票号"+ticket);
				ticket--;
			}
		}
}

eg:同步方式解决继承Thread类的线程问题

/**
 * 线程通信的例子:使用两个线程打印1-100.线程1,线程2交替打印
 *
 *线程通信的三个方法:
 *wiat():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器;
 *notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
 *notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
 *说明:
 *1.wiat(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
 *2.wiat(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中的同步监视器;
 *	     否则会抛异常:java.lang.IllegalMonitorStateException
 *3.wiat(),notify(),notifyAll()三个方法是定义在java.lang.Object类中
 *sleep()和wait()的异同?
 *1.相同点:一旦执行此方发,都可以使当前线程进入阻塞状态。
 *2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait();
 *		2)调用要求不同:sleep()可以在任何需要的场景下调用。wait必须使用再同步代码块中。
 *		3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,wait()会释放。
 */
public class Number implements Runnable{
	private int number=1;
	//private Object obj=new Object();//同步代码块
	@Override
	public void run() {
		while (true) {
			synchronized (this) {//obj:同步代码块,或this代表
			//一个线程唤醒另一个线程
				this.notify();	
			if(number<=100) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+":打印:"+number);
				number++;
				try {
					//使得调用如下wait()方法的线程进入阻塞状态
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else {
				break;
			}
			}
		}
	}
}

启动线程测试类

public static void main(String[] args) {
	eamilTest t1=new eamilTest();
		eamilTest t2=new eamilTest();
		eamilTest t3=new eamilTest();
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t1.start();
		t2.start();
		t3.start();
}	
	
public class eamilTest extends Thread{
private	static int ticket=100;
	@Override
	public void run() {
		while(true) {
			show();
			}
		}
	private static synchronized void show () {//同步监视器:windows.class
		//	private synchronized void show () {//同步监视器:t1,t2,t3;此种解决方式是错误的
		if(ticket>0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}   
			System.out.println(Thread.currentThread().getName()+"售票处:"+"票号"+ticket);
			ticket--;
		}
	}
}

线程死锁的问题

/**
 *1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方
 *              放弃自己需要的同步资源,就形成了线程的死锁。
 *2.说明:
 *(1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
 *(2)我们使用同步时,要避免出现死锁。
 */
public class ThreadLock {
	
	public static void main(String[] args) {
		StringBuffer s1=new StringBuffer();
		StringBuffer s2=new StringBuffer();
		new Thread() {
		@Override
		public void run() {
			synchronized (s1) {
				s1.append("1");
				s2.append("a");
				/*try {//出现线程阻塞
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}*/
				synchronized (s2) {
					s1.append("2");
					s2.append("b");
					System.out.println(s1);
					System.out.println(s2);
				}
			}
		}
	}.start();
	new Thread(new Runnable() {
		@Override
		public void run() {
			synchronized (s2) {
			s1.append("3");	
			s2.append("c");
			/*	try {//出现线程阻塞
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}*/
			 synchronized (s1) {
				s1.append("4");
				s2.append("d");
				System.out.println(s1);
				System.out.println(s2);
			}
			}
		}
	}).start();
	}
}

第三种方式:Lock锁解决线程安全问题

package com.zhsh.thread.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全的方式三:Lock锁
 * synchronized与lock的区别:
 * 1.相同点:都可以解决线程安全问题
 * 2.不同点:synchronized机制:在执行完相应的同步代码之后,自动的释放同步监视器;
 *    lock:需要手动的启动同步(lock();),结束之后也需要手动的结束同步(unlock();)
 * 优先级:Lock》同步代码块》同步方式
 * 3.如何解决线程的安全问题,有几种方式:
 * 
 */
public class Lockwindows implements Runnable {
	private int ticket=100;
	private ReentrantLock lock=new ReentrantLock();
	@Override
	public void run() {
	while(true) {
		try {
			//调用锁方法:lock();
			lock.lock();
			if(ticket>0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"售票处,售票号:"+ticket);
				ticket--;
			}else {
				break;
			}
		} finally {
			//调用解锁方法:unclock();
			lock.unlock();
		}
		}
	}
}

Lock的多线程的测试类
	public static void main(String[] args) {
		Lockwindows lwd=new Lockwindows();
		Thread t1=new Thread(lwd);
		Thread t2=new Thread(lwd);
		Thread t3=new Thread(lwd);
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t1.start();
		t2.start();
		t3.start();
	}

线程通信

线程通信测试类

public class CommunicationTest {
	public static void main(String[] args) {
		Number nb=new Number();
		Thread t1=new Thread(nb);
		Thread t2=new Thread(nb);
		t1.setName("线程1");
		t2.setName("线程2");
		t1.start();
		t2.start();
	}
}

--------------线程通信的第2个例子-------------------------------------------

/**
 * 生产者和消者,生产者生产产品,消费者消费产品,店员负责销售产品给消费者;生产者每次生产20个产品
 * 分析问题:
 * 线程:生产者和消费者,至少两个线程以上。
 * 共享数据:产品(或是店员)
 * 是否存在线程安全问题	 
 */
public class ProductTest {
	public static void main(String[] args) {
		Clerk clerk=new Clerk();
		Producer p= new Producer(clerk);
		p.setName("生产者1");
		Consumer c=new Consumer(clerk);
		c.setName("消费者1");
		Consumer c1=new Consumer(clerk);
		c1.setName("消费者2");
		p.start();
		c.start();
		c1.start();
	}
}
public class Clerk {//店员
	private int proucesNumber=0;
	//生产者生产产品
	public synchronized void producer() {
		while (true) {
			if(proucesNumber<20) {
				proucesNumber++;
				System.out.println(Thread.currentThread().getName()+":生产产品第"+proucesNumber+"个");
				notify();
			}else {
				try {//等待
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	//消费者消费产品
		public synchronized void consumer() {
			while (true) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if(proucesNumber>0) {
					System.out.println(Thread.currentThread().getName()+":消费产品第"+proucesNumber+"个");
					proucesNumber--;
					notify();
				}else {
					try {//等待
						wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
}
public class Producer extends Thread{//生产者
	private Clerk clerk;
	public Producer(Clerk clerk) {
		super();
		this.clerk = clerk;
	}

	@Override
	public void run() {
		clerk.producer();
	}

}
public class Consumer extends Thread{//消费者
	private Clerk clerk;
	public Consumer(Clerk clerk) {
		super();
		this.clerk = clerk;
	}
	@Override
	public void run() {
		clerk.consumer();
	}
}

创建线程的方式三:实现Callable和使用线程池

1.实现Callable接口
1.1使用Runnable相比,Callable功能更加强大些
1)相比run()方法,可以有返回值
2)方法可以抛异常
3)支持泛型的返回值
4)需要借助FutureTask类,比如获取返回结果;

	
/**
 * 创建线程的方式三、实现Callable接口
 *0.如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
 *  1).call方法可有返回值
 *  2).call()可以抛出异常。被外面的操作捕获,获取异常的信息;
 *  3)Callable是支持泛型的
 * 1.创建一个实现Callable的实现类
 */
public class NumThread implements Callable{
	//2.实现call方法,将此线程需要执行的操作声明在call()中
	@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;
	}
}
/**
 *计算0-100的偶数和
 */
public class ThreadNew {
	public static void main(String[] args) {
		//3.创建Callable接口实现类的对象
		NumThread n=new NumThread();
		//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
		FutureTask futureTask = new FutureTask(n);
		//5.将FutureTask的对象作为参数传递到Thread类构造器中,创建Thread对象,并调用start();
		new Thread(futureTask).start();
		try {
			//6.获取Callable中call方法的返回值
			//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回桌子
			Object object = futureTask.get();
			System.out.println("总和为:"+object);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}		
}

2.新增方式二、使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创佳好多线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。类似公共交通工具。
好处:提高响应速度(减少创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次创建)
便于线程管理

/**
 *创建线程的方式四:使用线程池
 *1.好处:提高响应速度(减少创建新线程的时间)
 *     	 降低资源消耗(重复利用线程池中线程,不需要每次创建)
 *	  	 便于线程管理
 */
public class ThreadPool {	
 public static void main(String[] args) {
	//1.提供指定线程数量的线程池
	ExecutorService service=Executors.newFixedThreadPool(10);
	//设置线程池的属性
	System.out.println(service.getClass());//获取对象service是由哪个类所造
	ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
//	service1.setCorePoolSize(15); //....
	//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
	 service.execute(new NumberThread());//适合使用Runable
	//3.关闭连接池
	 service.shutdown();
	//service.submit();//适合使用Callable(同理:将Runnable实现类中run方法替换为call方法。设置返回值)
}
}
public class NumberThread implements Runnable{
	@Override
	public void run() {
		int sum=0;
		for (int i = 0; i < 100; i++) {
			if(i%2==0) {
				System.out.println(Thread.currentThread().getName()+",偶数:"+i);
				sum+=i;
			}
	  }
	}
}

总结:创建线程有四种方式:1.继承Tread类;2.实现Runnable接口,3.实现Callable接口,4.使用线程池
解决线程安全问题有三种方式:1.同步代码块,2.同步方式,3.Lock锁


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七秒~车

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值