java基础—多线程
1、多线程的概念
进程、线程、多线程的概念:
- 进程:正在进行中的程序。
- 线程:进程中一个负责程序执行的控制单元(执行路径)。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
2、创建线程方式一:继承Thread类
定义一个类继承Thread类。
覆盖Thread类中的run方法。
直接创建Thread的子类对象创建线程。
调用start方法开启线程并调用线程的run方法执行。
3、创建线程方式二:实现Runnable接口
定义类实现Runnable接口。
覆盖接口中的run方法,并将线程的任务代码封装到run方法中。
通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
调用线程对象的start方法开启线程。
实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
- 避免了java单继承的局限。所以,创建线程的第二种方式较为常用。
4、线程的安全问题
线程安全问题产生的原因:
- 多个线程在操作共享的数据。
- 操作共享数据的线程代码有多条。
线程安全问题的解决方案
思路:就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。(或者在函数上加synchronized修饰符即可)
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码
}
同步的特点:
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须是多个线程并使用同一把锁。
利用同步代码块解决安全问题案例:
- 需求:储户,两个,每个都到银行存钱,每次存100元,共存三次。
思路:
定义一个Cons(储户)类并实现Runnable接口;
复习Cons类中的run方法,执行Bank(银行)类中存储这个动作;
将存储这个动作上锁,一次只能让有一个客户存钱。
创建两个线程并开启线程。
- 代码:
class Bank
{
private int sum;
public void add(int num)
{
synchronized(this)//保证每次只有一个线程在调用
{
sum+=num;
System.out.println("sum="+sum);
}
}
}
//实现Runnable接口
class Cons implements Runnable
{
Bank b=new Bank();
//复写Runnable中的run方法。
public void run()
{
for(int x=0;x<3;x++)
b.add(100);
}
}
class BankDemo
{
public static void main(String[] args)
{
//把资源进行封装,传入给线程
Cons c=new Cons();
//创建两个线程
Thread t1=new Thread(c);
Thread t2=new Thread(c);
//开启线程
t1.start();
t2.start();
}
}
- 输出结果:
5、多线程下的单例模式
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式存在安全问题,可以使用同步函数解决。
- 代码:
class Single
{
private Single(){}
private static Single s=null;
public static Single getInstance()
{
//第一次判断,提高了效率,若对象已存在就不用判断锁了
if(s==null)
{
//保证每次只有一个线程在调用
synchronized(Single.class)
{
if(s==null)
s=new Single();
}
}
return s;
}
}
6、死锁
定义:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限地阻塞,因此程序不可能正常终止。
产生死锁的原因:
- 因为系统资源不足。
- 进程运行推进的顺序不合适。
- 资源分配不当等。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
示例代码:
class Test implements Runnable
{
//flag是标识
private boolean flag;
Test(Boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
while (true)
{
//锁a
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"……if lacka……");
}
//锁b
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"……if lackb……");
}
}
}
else
{
while (true)
{
//锁b
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"……else lackb……");
}
//锁a
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"……else lacka……");
}
}
}
}
}
class MyLock
{
public static final Object locka=new Object();
public static final Object lockb=new Object();
}
class DeadLockDemo
{
public static void main(String[] args)
{
Test t1=new Test(true);
Test t2=new Test(false);
//创建线程
Thread d1=new Thread(t1);
Thread d2=new Thread(t2);
//开启线程
d1.start();
d2.start();
}
}
7、线程间通信
- 等待/唤醒机制涉及的方法啊:
- wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
- notify():唤醒线程池中的一个线程(任何一个都有可能)
- notifyAll():唤醒线程池中的所有线程。
- wait和sleep的区别:
- wait可以指定时间也可以不指定,sleep必须指定时间。
- 在同步中,对CPU的执行权和锁的处理不同。
- wait:释放执行权,释放锁。
- sleep:释放执行权,不释放锁。
- 示例(生产者-消费者问题):
- 代码:
class Resource
{
private String name;
private String sex;
private boolean flag=false;
public synchronized void set(String name,String sex)
{
//flag为true时,线程t1冻结,等待唤醒
if(flag)
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
this.name=name;
this.sex=sex;
flag=true;
notify();
}
public synchronized void out()
{
//flag为false时,线程t2冻结,等待唤醒
if(!flag)
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(name+"……"+sex);
flag=false;
notify();
}
}
//输入,实现Runable接口
class Input implements Runnable
{
Resource r;
Input(Resource r)
{
this.r=r;
}
public void run()
{
int x=0;
while(true)
{
if (x==0)
{
r.set("小明","男");
}
else
{
r.set("小红","女");
}
x=(x+1)%2;
}
}
}
//输出,实现Runnable接口
class Output implements Runnable
{
Resource r;
Output(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ResourceDemo
{
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();
}
}
- 输出结果:
8、JDK1.5新特性
- JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。
- Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
- lock():获取锁。
- unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
- Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
- Condition接口中的await方法对应于Object中的wait方法。
- Condition接口中的signal方法对应于Object中的notify方法。
- Condition接口中的signalAll方法对应于Object中的notifyAll方法。
- 示例(生产者、多消费者问题:一个Lock、两个Condition)
- 代码:
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag=false;
//创建一个锁对象
Lock lock=new ReentrantLock();
//通过已有的锁获取该锁上的监视对象
Condition pro_con=lock.newCondition();
Condition con_con=lock.newCondition();
public void set(String name)
{
//获取锁
lock.lock ();
try
{
while(flag)
{
try
{
//生产者监视器冻结
pro_con.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"……生产"+this.name);
flag=true;
//唤醒消费者监视器
con_con.signal();
}
finally
{
//释放锁
lock.unlock();
}
}
public void out()
{
//获取锁
lock.lock();
try
{
while(!flag)
{
try
{
//消费者监视器冻结
con_con.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"…… 消费"+name);
flag=false;
//唤醒生产者监视器
pro_con.signal();
}
finally
{
//释放锁
lock.unlock();
}
}
}
//输入
class Producer implements Runnable
{
Resource r;
Producer(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
}
//输出
class Consumer implements Runnable
{
Resource r;
Consumer(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource r=new Resource();
Producer pro=new Producer(r);
Consumer con=new Consumer(r);
Thread t1=new Thread(pro);
Thread t2=new Thread(pro);
Thread t3=new Thread(con);
Thread t4=new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
- 输出结果: