多线程
程序,线程,进程的关系
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锁