多线程
什么是线程?
进程:可以简单理解成就是一个应用程序.
多个线程组成了一个进程.
进程与进程之间不可能进行通信,但是同一个进程中的线程可以进行通信.
我们使用多线程 程序目的就是为了提高效率.
多线程程序相当于拥有多条通路去执行。
JVM在启动时是单线程还是多线程的?
JVM在启动时是多线程。一共启动了两个线程
1.非守护线程 (其实就是我们的main方法,也叫做主线程)
2.守护线程 (垃圾回收) 它的作用用来清理堆内存中存在的没有引用指向的对象
它调用就是finalize方法
多线程是java中的一项技术
java中的多线程不是面向对象的。
java中怎样实现多线程(面试题)
首行知道:java.lang包下的所有类在使用时不需要导包.
java.lang.Thread 这个类就是java中的线程类.
阅读帮助文档
1.发现java中的线程有10个优先级,默认优行级为5.最高为10,最低为1
2.java中创建线程有两种方式
2.1 第一种是继承 Thread类,重写run方法
创建线程 创建我们自定义类的对象
启动线程 调用从Thread类中继承的start方法
步骤
1.继承Thread
2.重写run方法
通过查看帮助文档发现 Thread类中的run方法中没有任何内容,只是告诉我们子类要重写这个方法
3.创建线程对象,并启动线程
run方法作用: 要执行的子线程的内容。
start作用: 启动线程 jvm重新开辟运行通信
多线程程序的无规则性
根据这个程序的结果,发现main或run内容谁先执行,谁先执行多少个
这个是规律的,这个叫多线程程序的无序性.
为什么出现这种情况,是因为cpu在执行时,我们控制不了cpu的时间片
分配,分配给哪一个线程,哪个线程就执行。
创建线程的第二种方式
2.2 实现Runnable接口
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。
步骤
1.创建类去实现Runnable接口
2.Runnable接口中定义了一个run方法,必须重写
3.启动线程 将实现了Runnable接口类的对象做为参数传递给Thread构造方法,
这样在通过Thraed对象去调用start方法时,就知道应该执行哪个run。
例如 new Thread(实现Runnable接口对象).start();
第一种方式实现
MyThread mt=new MyThread();
mt.start();
如果我们没有在自定义类中重写run方法,那么父类中run方法没执行任何内容
这不是我们想要的,我们只有在自定义类中重写run,完成我们想要的功能
*/
/*
//第二种实现
MyRunnable mr=new MyRunnable();
new Thread(mr).start();
这时调用的是我们实现Runnable接口中的run方法的内容.
Thread类中常用的方法
1.String getName() 得到线程的名字
2.void setName() 设置线程的名称
3.static Thread currentThread() 得到当前正在运行的线程的对象的引用
4.了解 String toString() 线程对象的字符串表示形式 包括 线程名 优先级 线程组。
5.static void sleep(long millis) 让当前线程睡眠millis毫秒
关于创建线程的两种方式区别
建议使用哪一个
1.extends Thread
2.implements Runnable
建议使用第二种,为什么?
1.java中支持多实现,而不支持多继承。
如果使用了继承,那么我们程序的可扩展性下降,因为一个类只能有一个父类
使用实现,我们可以实现多个接口,不影响我们程序的扩展性。
2.关于数据共享
2.1如果使用继承Thread类来实现数据共享,那么这个数据要static,
它的生命周期太长.
2.2如果使用实现Runnable,这时候只需有一个实现Runnable对象将其传递给
Thread类的构造方法,就可以实现多个线程操作同一个数据。
多线程程序的几种状态
1.创建状态
2. start 启动 运行状态
3.冻结
4.销毁 1. 线程结束 run运行结束 2.调用stop()不建议使用
5.临时堵塞状态
一定要会画状态图
多线程程序存在安全问题
得到的结果是11 12
这种是一种理想化状态的结果.
怎样解决这种安全问题
当一个线程在执行过程中,如果没有执行完,那么其它的
线程给我等待,当前线程执行完成后,其它的线程才允许
去执行内容,这样就可以解决这个线程安全问题.
代码实现 我们可以使用synchronized关键字来操作。
让线程进行同步
怎样使用
同步代码块
格式 synchronized(任意对象){
内容
}
相当于加了锁,也就是说当前线程没有执行完内容,
这把锁不会释放,其它线程想要执行,必须得到锁才
可以执行。
也可以使用另外一种方式进行同步
同步方法 就是在执行的方法上用synchronized进行修饰
但是它降低了多线程程序的效率.
同步什么时候使用?
1.多线程时使用同步
2.在线程执行的内容中,操作了共享数,并且是多条件语句操作,这时就需要同步
同步使用时要注意的问题?
我们必须保证想要同步的这些线程所使用的锁的对象必须是
同一个。
同步方法,如果方法是实例方法,那么它默认所使用的锁是 this
如果同步方法是静态的方法,那么它默认所使用的锁的 类名.class 对象.
多线程中的单例设计模式
能出现线程安全的问题是懒汉式(延迟加载方式)
代码实现
//懒汉式
class SingleTon
{
private static SingleTon single=null;
//static Object obj=new Object();
private SingleTon(){
}
public static SingleTon getInstance(){
synchronized(SingleTon.class){
if(single==null){
single=new SingleTon(); //当多个线程操作时,第一个线程在判断single==null为true时,
//第一个线程切换到了临时堵塞状态,那么第二个线程在判断
//发现single==null还为true,这时就出问题了
}
}
return single;
}
}
/*
class SingleTon
{
private static SingleTon single=null;
private SingleTon(){
}
public synchronized static SingleTon getInstance(){
if(single==null){
single=new SingleTon(); //当多个线程操作时,第一个线程在判断single==null为true时,
//第一个线程切换到了临时堵塞状态,那么第二个线程在判断
//发现single==null还为true,这时就出问题了
}
return single;
}
}*/
线程同步缺点
1.降低效率
2.会出现死锁.
什么时候会出现死锁?
一般是同步嵌套
示例代码
class Test1
{
public static void main(String[] args)
{
DeadLockDemo dld=new DeadLockDemo();
Thread th1=new Thread(dld);
Thread th2=new Thread(dld);
th1.start();
th2.start();
}
}
class DeadLockDemo implements Runnable
{
boolean flag=true;
public void run(){
while(true){
if(flag){
method1();
}else{
method2();
}
}
}
public void method1(){
flag=false;
synchronized(Global.A){
System.out.println("if................A");
synchronized(Global.B){
System.out.println("if................B");
}
}
}
public void method2(){
flag=true;
synchronized(Global.B){
System.out.println("else..................B");
synchronized(Global.A){
System.out.println("else...............A");
}
}
}
}
class Global
{
//这两个对象相当于两把锁
public static Object A=new Object();
public static Object B=new Object();
}
线程间的通信
例如 有两个数据库 MySql Oracle
我们以前的程序的数据建立在MySql上的
1.做一个线程程序将MySql数据库中的内容读取出来,封装成java中的对象
简单说,就是通过程序对Person的属性进行了赋值操作.
2.在做一个线程程序,它将封装的java对象 其实就是我们例子中的Person对象、
进行读取,将这些信息得到,重新添加到Oracle数据库中.
这两个线程操作的资源应该是同一个。简单说,就是InputThread类与OutputThread类应该操作的是
同一个Person对象.
我们现在要完成的事情是这样?
可不可以让InputThread类在赋值完成后,如果没有读取赋值信息,不在赋值。
也就是说InputThread类赋一次,OutputThread类读取一次。
这时候我们需要用来 wait notify。
为了可以判断当前应该执行什么操作,我们可以定义一个boolean变量来控制操作.
用这个变量来标识当前的操作是 赋值 取值.
wait:线程等待 当线程wait后,它会释放执行权,并释放锁。
notify:它是唤醒线程池中随机的一个线程。但是一般情况下都是得到线程池中第一个.
为了保证线程间的数据安全
1.线程操作的源应该是同一个
2.线程同步时所使用的锁对象要是同一个
线程间的通信
例如 有两个数据库 MySql Oracle
我们以前的程序的数据建立在MySql上的
1.做一个线程程序将MySql数据库中的内容读取出来,封装成java中的对象
简单说,就是通过程序对Person的属性进行了赋值操作.
2.在做一个线程程序,它将封装的java对象 其实就是我们例子中的Person对象、
进行读取,将这些信息得到,重新添加到Oracle数据库中.
这两个线程操作的资源应该是同一个。简单说,就是InputThread类与OutputThread类应该操作的是
同一个Person对象.
我们现在要完成的事情是这样?
可不可以让InputThread类在赋值完成后,如果没有读取赋值信息,不在赋值。
也就是说InputThread类赋一次,OutputThread类读取一次。
这时候我们需要用来 wait notify。
为了可以判断当前应该执行什么操作,我们可以定义一个boolean变量来控制操作.
用这个变量来标识当前的操作是 赋值 取值.
wait:线程等待 将线程放入到线程池中 当线程wait后,它会释放执行权,并释放锁。
notify:它是唤醒线程池中随机的一个线程。但是一般情况下都是得到线程池中第一个.
notifyAll:它是将线程池中所有的线程唤醒
关于wait,notify,notifyAll这三个方法,定义在Object类中.
为什么定义在Object类中.
简单说,这三个方法都应该由监视器对象(锁对象)调用 ,
锁对象是哪个类对象,不确定,那么这些方法就应该定义在
Object类,java中所有的类都包含了这样的方法。
这三个方法一般都是在同步中应用的。
多生产者,多消费者
为什么出现这样情况
1.我们还没有生产产品,就已经消费了。
因为线程wait后,当notify这个线程,我们使用的是if条件判断,当
线程唤醒后,得到执行权执行时不在进行判断,直接执行下面的内容。
怎样修改? 让线程被notify后,得到执行权要执行时,在进行一次状态判断
将原来的if条件判断状态,改成while判断.
2.出现了相当于死锁的情况
生产者线程与消费者线程都进入了线程池。
怎样解决:我们某个线程在执行时,唤醒时将线程池中的所有线程唤醒。 notifyAll;
sleep与wait区别
1.时间
sleep必须指睡眠时间
wait可以指定时间,也可以不指定
2.
sleep释放了线程的执行权,但是不释放锁
wait释放线程的执行权,也释放了锁。
JDK1.5后解决多生产者与消费者问题
java.util.concurrent.locks 包下的
Lock:
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用
Condition中的 await signal signalAll
使用jdk1.5后这些锁对象与监视对象,最大的一个好处是操作列简单,
并且一个锁可以对应多个监视器,也就是说,我们在生产时,应该唤醒的是
消费线程,消费时应该唤醒的是生产线程。
jdk1.5后
synchronized -------->Lock代替 lock() unlock();
synchronized(对象) -----这个对象就是监视器
在1.5后被 Condition
以前所使用Object类中的的wait notify notifyAll这些方法也被
Codition中的 await signal signalAll方法替换.
以前所有的方式synchronized(对象)
这种方式可以理解成一个锁对象一个监视器。这个监视器可以执行的操作 wait notify notifyAll
这些操作,在执行notify,或notifyAll操作时,唤醒的就是监视器wait的线程。
jdk1.5后 锁是Lock 它是通过 lock()与unlock()方法来控制要同步的内容.要注意的是大部分情况下
都要将unlock放到finally中.
我们可以根据Lock对象得到多个监视器。这多个监视器都有自己的await signal signalAll操作。
它们只会操作对应监视器上的线程。
线程的其它操作:
关于停止线程操作
线程停止有两种方式
1.调用线程类中的stop方法 不建议使用,因为已经过时.
2.加标识 run方法执行完线程就自动结束了。
对于这种使用标识操作,让线程自动结束的操作,有一些情况下出问题
例如 线程 wait
stop方法可以让线程结束,但是过时了,可以使用interrupt方法来代替。
但是这个方法在使用时可以抛出异常。
因为interrupt方法将线程的状态改变了,例如下面程序,当线程由wait 冻结状态
变成消亡状态,线程状态改变了,这时产生了interruptedException
void setDaemon(boolean f) 将线程标注成守护线程(后台线程)
设置线程是守护线程,那么必须在线程启动前设置。
java中的线程分为两种,一种叫前台线程,另外一种是后台线程(守护线程)
,如果所有的前台线程都结束了,那么所有的后台线程也自动结束。
int getPriority() 得到线程的优先级
void setPriority(int i);设置线程的优先级
重点:生产者消费者问题 要求代码会写 jdk1.5后的也要会写.
java中线程的面试题
1.线程的创建方式
2.线程同步原理与多线程的安全问题
3.什么情况下会出现死锁,写一个死锁代码
4.生产者消费者问题 (要求代码)
5.jdk1.5后新产生的关与同步的操作
6.sleep与wait区别