java基础-多线程
2015-06-29 16:30
183 查看
一、多线程引入
1、我们在之前写的代码程序只有一个执行流程,这样的程序就是单线程程序。
2、假如一个程序有多条执行流程,那么,该程序就是多线程程序。
二、进程和线程的概念:
1、进程:应用程序在内存中运行的空间
2、线程:是进程中的一个执行单元,负责执行进程中的代码。
3、一个进程如果只有一条执行路径,则称为单线程程序。
4、一个进程如果有多条执行路径,则称为多线程程序。
三、多线程的存在解决什么问题?
1、多部分代码同时执行的问题。
2、传统的单个主线程从头执行到尾的运行安排,当遇到较多的操作次数时,效率偏低。
四、多线程的弊端:
1、开启过多会降低效率,多线程的原理其实是多个执行单元执行一个程序,CPU高速在多个线程之间进行切换,当线程个数过多时,CPU切换一个循环的时间过长,相应的降低了程序运行的效率。
五、多线程的特性:
1、随机性,因为CPU的快速切换造成的。
六、java程序运行原理
1、java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
2、思考:jvm虚拟机的启动是单线程的还是多线程的?
jvm的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
七、举例:
1、金山卫士进行电脑体检的同时,还可以清理垃圾,木马查杀等,这就是多线程。
第二节:创建线程的两种方式
一、方式一:
1、创建方式:继承Thread类。
(3).1 定义一个类继承Thread。原因:Thread类描述了线程的任务存在的位置:run方法。
(3).2 重写run方法。原因:为了定义线程需要运行的任务代码。
(3).3 创建子类对象,就是创建线程对象。目的:为了执行线程任务,而是任务都定义在run方法中。run方法结束了,线程任务就是结束了,线程也就是结束了
(线程任务:每一个线程都有自己执行的代码,主线程的代码都定义在主函数中,自定义线程的代码都定义在run方法中。)
(3).4 调用start方法,原因:开启线程并让线程执行,同时还会告诉jvm去调用run方法。
2、调用run和调用start的区别?
调用run方法仅仅是主线程执行run方法中的代码;调用start方法是开启线程,让新建的线程与主线程同时执行。
3、多线程的运行原理
(3).1 如果多部分代码同时执行,那么在栈内存方法执行是怎么完成的呢?
其实是,每一个线程在栈内存中都有自己的栈内存空间,在自己的栈空间中
进行压栈和弹栈。
(3).2 主线程结束,程序就结束吗?
主线程结束,如果其他线程还在执行,进程是不会结束了,当所有的线程都结束了,进程才会结束。
3、代码体现
[java]
view plaincopy
//创建线程的第一种方式 继承Thread
//开启两个窗口进行卖票 第一种方式
/*
思路:
1 创建一个类 继承thread类并覆盖run方法
2 创建子类对象
3 调用start方法
*/
class Ticket extends Thread
{//自定义变量并给一定的值
private int ticket=100;
//覆盖run方法
public void run()
{
//进行循环
while (true)
{
//判断票的数量
if (ticket>0)
{
//打印输出
System.out.println(Thread.currentThread().getName()+".............................."+ticket--);
}
else
{
break;
}
}
}
}
class Demo
{
public static void main(String[] args)
{
//创建Thread子类的对象
Ticket t=new Ticket();
Ticket t1=new Ticket();
//并调用start的方法
t.start();
t1.start();
}
}
二、方式二:
1、方式二步骤:
(1).1,定义类实现Runnable接口。原因:避免了继承Thread类的单继承局限性。
(1).2,覆盖接口中的run方法。原因:将线程任务代码定义到run方法中。
(1).3,创建Thread类的对象。原因:只有创建Thread类的对象才可以创建线程。
(1).4,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
原因:线程已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,
所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
(1).5,调用Thread类的start方法开启线程。
2、方式二和方式一的区别:
(2).1 第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
(2).2 实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类:线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。
实现runnable接口:将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。
Runnable接口对线程对象和线程任务进行解耦。
3、代码体现
[java]
view plaincopy
//创建两个线程去卖票,用种第二方式
/*
思路:
1 创建 一个类 去实现 Runable接口 并覆盖run方法
2 创建一个类的对象 并把这个对象作为参数传递给Thread类的构造函数中
3 调用start方法
*/
//实现Runnable接口
class Ticket implements Runnable
{
//定义一定量的票数
private int ticket=200;
//覆盖run方法
public void run()
{
//进行循环
while (true)
{
//判断票的数量
if (ticket>0)
{
//进行打印
System.out.println(Thread.currentThread().getName()+".............."+ticket--);
}
else
{
break;
}
}
}
}
class Demo1
{
public static void main(String[] args)
{
//把Ticket()的类的对象传入到Thread类对象的构造方法当中去,并调用start方法。
new Thread(new Ticket()).start();
new Thread(new Ticket()).start();
}
}
第三节:线程的运行的状态
一、线程运行是有多种状态的:
1、创建 new Thread类或者其子类对象。
2、运行 start()具备者CPU的执行资格和CPU的执行权。
3、冻结 释放了CPU的执行资格和CPU的执行权。比如执行到sleep(time) wait() 导致线程冻结。
4、临时阻塞状态 具备者CPU的执行资格,不具备CPU的执行权。
5、消亡 线程结束。run方法结束。
第四节:线程的安全问题
一、多线程的安全问题:
1、安全问题产生的原因:
(1).1线程任务中有共享的数据。
(1).2线程任务中有多条操作共享数据的代码。
当线程在操作共享数据时,其他线程参与了运算导致了数据的错误(安全问题)
2、安全问题的解决思路:
保证在线程执行共享数据代码时,其他线程无法参与运算。
3、安全问题的解决代码体现:
同步代码块。synchronized(obj){需要被同步的代码}
4、同步的好处:
同步的出现解决了多线程的安全问题。
5、同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
6、同步的前提:
必须保证多个线程在同步中使用的是同一个锁。
解决了什么问题?
当多线程安全问题发生时,加入了同步后,
问题依旧,就要通过这个同步的前提来判断同步是否写正确。
7、代码体现
[java]
view plaincopy
//出现了线程的安全问题
//解决安全方式之一同步函数 卖票问题
//定义一个类实现Runnable接口
class Ticket implements Runnable
{
//定义一个私有的变量票的数量
private int ticket=10;
//覆盖run方法
public void run()
{
//进行循环
while (true)
{ //加上一个锁
synchronized(this)
{
//判断票的数量大于零
if (ticket>0)
{
//让线程进行睡眠100毫秒,并进行异常的处理
try{Thread.sleep(100);}catch(InterruptedException ie){}
//进行打印
System.out.println(Thread.currentThread().getName()+"................"+ticket--);
}
else
{
break;
}
}
}
}
}
class Demo2
{
public static void main(String[] args)
{
//创建Ticket类的对象
Ticket t=new Ticket();
//并把对象传入到Thread类对象的构造函数当中去
Thread tt=new Thread(t);
Thread ttt=new Thread(t);
//调用start的方法。
tt.start();
ttt.start();
}
}
二、同步函数
1、同步函数的表现形式。就是让一个封装体具备了同步性。
其实就是在函数上加上同步关键字。
2、同步函数的锁用的哪个呢?
(2).1 非静态的同步函数使用的锁是: this
(2).2 静态的同步函数使用的锁时:类名.class
3、同步函数和同步代码块的区别。
(3).1同步函数使用的锁是固定的。
(3).2同步代码块使用的锁是任意对象。
(3).3如果线程任务中仅使用一个同步,可以简化成同步函数的形式。
(3).4如果使用多个同步(多个锁),必须使用同步代码块。
三、单例模式懒汉式中并发访问
1、懒汉式并并发访问,容易出现线程安全问题,而导致对象的不唯一。
2、解决的方式,加上同步关键字。如果使用同步代码块,使用的锁是 类名.class
3、但是效率会降低,所以可以使用同步代码块,加上双重对引用变量的判断。
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
四、死锁:
1、死锁:线程都为结束,都处于不执行状态,这时就称为死锁。
2、常见场景:
(2).1 同步嵌套。
(2).2 都处于冻结状态。
3、尽量避免死锁。
4、代码体现
[java]
view plaincopy
//写一个死锁
/*
思路:
1 所谓的死锁 就是嵌套循环 相互抱着不是自己的锁不放就导致了死锁
2 定义一个类 定义两个锁
3 定义一个类 实现Runnable接口 覆盖run方法 在写入一个嵌套循环 再定义一个标记
4 开启线程
*/
class Demo4
{
public static void main(String[] args)
{
//创建Ticket类的两个对象
Ticket t=new Ticket();
Ticket t1=new Ticket();
//分别传入到两个Thread类对象的构造函数当中去
Thread tt=new Thread(t);
Thread ttt=new Thread(t1);
//调用start方法
tt.start();
//把标记进行转换
t.bug=true;
//再进行调用start方法
ttt.start();
}
}
//定义了锁的类
class MyLock
{
//并定义了两个锁
public static final Object locka=new Object();
public static final Object lockb=new Object();
}
//自定义一个Ticket类并实现Runnabale接口
class Ticket implements Runnable
{
//定义一个变量,来记住票的数量
private int ticket;
//定义一个变量标记
boolean bug=false;
//覆盖run方法
public void run()
{
//进行判断标记
if (bug)
{
//进行循环
while (true)
{
//定义一同步代码块并传入MyLock.locka锁
synchronized(MyLock.locka)
{
//进行对线程sleep100毫秒,并对异常进行处理
try{Thread.sleep(100);}catch(InterruptedException ie){}
//打印票的数量
System.out.println("......................run1111111,,,,,,,,,locka................"+ticket++);
//在定义一个同步代码块这时是MyLock.lockb锁
synchronized(MyLock.lockb)
{
//进行打印
System.out.println(".......1111111lockb................");
}
}
}
}//当标记变化时,就执行下列代码
else
{
while (true)
{
//定义一同步代码块并传入MyLock.lockb锁
synchronized(MyLock.lockb)
{
//进行对线程sleep100毫秒,并对异常进行处理
try{Thread.sleep(100);}catch(InterruptedException ie){}
//打印票的数量
System.out.println("...............,,lockb22222222222222................"+ticket++);
//在定义一个同步代码块这时是MyLock.locka锁
synchronized(MyLock.locka)
{
//进行打印
System.out.println(".....................22222222222run...locka................");
}
}
}
}
}
}
第五节:线程间的通信
一、简述:
1、多线程间通信:多个线程执行任务不同,但是处理资源相同。
2、等待唤醒机制涉及的方法:
(1)、wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中。
(2)、notify():唤醒线程池中一个线程(任意)。
(3)、notifyAll():唤醒线程池中的所有线程。
3、这些方法都必须定义在同步中?
因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。
4、为什么操作线程的方法wait notify notifyAll定义在了Object类中?
因为这些方法是监视器的方法。监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。
5、一对一 代码体现
[java]
view plaincopy
//线程间的通信 例如生产者与消费者 一对一的情况
/*
思路:
1 资源是唯一的 所以创造一个类用来描述这个类
2 用一个类来描述生产者生产者生产一个
3 用一个类来描述消费者消费者就消费一个
*/
class Demo5
{
public static void main(String[] args)
{
Socure s=new Socure();
Procity p=new Procity(s);
Take t=new Take(s);
new Thread(p).start();
new Thread(t).start();
}
}
//自定义一个资源类
class Socure
{
//私有变量标记和生产和消费的数量
private boolean balg;
private int count;
//定义一个方法用来生产的
public void add()
{
//定义一个同步代码块
synchronized(this)
{
//循环标记
while(balg)
//进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException io){}
//数量进行加一
count++;
//打印所生产的数量
System.out.println(Thread.currentThread().getName()+"........生产者............."+count);
//标记进行改变
balg=true;
//进行释放
this.notify();
}
}
//定义一个方法进行消费
public void get()
{
//定义一个同步代码块
synchronized(this)
{
//循环标记
while(!balg)
//进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException ie){}
//打印出消费者消费的数量
System.out.println(Thread.currentThread().getName()+"...............消费者.........."+count);
//标记进行转变
balg=false;
//进行释放
this.notify();
}
}
}
//定义一个生产者类去实现Runnable接口
class Procity implements Runnable
{
//定义资源的变量
private Socure s;
Procity(Socure s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行生产
s.add();
}
}
}
//定义一个消费者类实现Runnavale接口
class Take implements Runnable
{
//定义资源的变量
private Socure s;
Take(Socure s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行消费
s.get();
}
}
}
6、多对多 代码体现
[java]
view plaincopy
//线程之间的通信 多对多的情况
class Demo6
{
public static void main(String[] args)
{
//创建子类类的对象
Recour r=new Recour();
//定义生产者,并把资源进行传入构造函数当中去
Procity p=new Procity(r);
//定义生消费者,并把资源进行传入构造函数当中去
Take t=new Take(r);
//把生产者传入到Thread类对象中的构造方法并调用start方法
new Thread(p).start();
new Thread(p).start();
//把消费者传入到Thread类对象中的构造方法并调用start方法
new Thread(t).start();
new Thread(t).start();
}
}
//自定义一个资源类
class Recour
{
//定义一个变量用来记住生产和消费的个数
private int count;
//定义一个标记
private boolean flag;
//定义一个方法 进行生产的
public void add()
{
//同步代码块
synchronized(this)
{
//定义一个循环
while(flag)
//如果符合进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException io){}
//生产的个数
count++;
//打印出生产的个数
System.out.println(Thread.currentThread().getName()+"......生产者......."+count);
//转换标记
flag=true;
//进行释放
this.notifyAll();
}
}
public void get()
{
//同步代码块
synchronized(this)
{
//定义一个循环
while (!flag)
//如果符合进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException ioe){}
//打印出消费的个数
System.out.println(Thread.currentThread().getName()+"..消费者...."+count);
//转换标记
flag=false;
//进行释放
this.notifyAll();
}
}
}
//自定义一个生产类去实现Runnable接口
class Procity implements Runnable
{
//定义一个资源类的变量
private Recour s;
Procity(Recour s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行生产
s.add();
}
}
}
//定义一个消费类并且去实现Runnable接口
class Take implements Runnable
{
//定义一个资源类的变量
private Recour s;
Take(Recour s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行消费
s.get();
}
}
}
二、jdk1.5中提供了多线程升级解决方案:
1、jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
2、Lock接口: 出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。同时更为灵活。可以一个锁上加上多组监视器。
3、Lock接口中的方法:
(1)、lock():获取锁。
(2)、unlock():释放锁,通常需要定义finally代码块中。
4、Condition接口:出现替代了Object中的wait notify notifyAll方法。
(1)、将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。
5、Condition接口中的方法:
(1)、await():让线程处于冻结状态
(2)、signal():唤醒线程池中一个线程(任意)。
(3)、signalAll(): 唤醒线程池中的所有线程。
6、代码体现三、停止线程:
1、stop方法:此方法以过时,通过该方法的描述得到解决办法。
2、run方法结束:任务中都会有循环结构,只要控制住循环就可以结束任务,控制循环通常就用定义标记来完成。
3、如果线程处于了冻结状态,无法读取标记。如何结束呢?
(1)、可以使用Thread类中的interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。
注:当时强制动作会发生了InterruptedException,记得要处理 。
4、sleep和wait方法的异同点:
(1)、相同点:可以让线程处于冻结状态。
(2)、不同点:
(2).1:
sleep必须指定时间。
wait可以指定时间,也可以不指定时间。
(2).2:
sleep时间到,线程处于临时阻塞或者运行。
wait如果没有时间,必须要通过notify或者notifyAll唤醒。
(2).3:
sleep不一定非要定义在同步中。
wait必须定义在同步中。
(2).4:
都定义在同步中,
线程执行到sleep,不会释放锁。
线程执行到wait,会释放锁。
四、多线程中的其他方法:
1、setDaemon: 将线程设置为守护线程,必须在开启前设置,当进程中的线程都是守护线程时,进程结束。
2、setPriority()方法用来设置优先级
(2).1 MAX_PRIORITY 最高优先级10。
(2).2 MIN_PRIORITY 最低优先级。
(2).3 NORM_PRIORITY 分配给线程的默认优先级。
3、join方法:加入一个执行线程。
当a线程执行到了b线程的.join()方法时,a线程就会等待,等b线程都执行完,a线程才会执行。(此时b和其他线程交替运行。)join可以用来临时加入线程执行。
4、yield方法:暂停线程,释放执行权。
转自:http://blog.csdn.net/bei__kou/article/details/46455597?ref=myread
1、我们在之前写的代码程序只有一个执行流程,这样的程序就是单线程程序。
2、假如一个程序有多条执行流程,那么,该程序就是多线程程序。
二、进程和线程的概念:
1、进程:应用程序在内存中运行的空间
2、线程:是进程中的一个执行单元,负责执行进程中的代码。
3、一个进程如果只有一条执行路径,则称为单线程程序。
4、一个进程如果有多条执行路径,则称为多线程程序。
三、多线程的存在解决什么问题?
1、多部分代码同时执行的问题。
2、传统的单个主线程从头执行到尾的运行安排,当遇到较多的操作次数时,效率偏低。
四、多线程的弊端:
1、开启过多会降低效率,多线程的原理其实是多个执行单元执行一个程序,CPU高速在多个线程之间进行切换,当线程个数过多时,CPU切换一个循环的时间过长,相应的降低了程序运行的效率。
五、多线程的特性:
1、随机性,因为CPU的快速切换造成的。
六、java程序运行原理
1、java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
2、思考:jvm虚拟机的启动是单线程的还是多线程的?
jvm的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
七、举例:
1、金山卫士进行电脑体检的同时,还可以清理垃圾,木马查杀等,这就是多线程。
第二节:创建线程的两种方式
一、方式一:
1、创建方式:继承Thread类。
(3).1 定义一个类继承Thread。原因:Thread类描述了线程的任务存在的位置:run方法。
(3).2 重写run方法。原因:为了定义线程需要运行的任务代码。
(3).3 创建子类对象,就是创建线程对象。目的:为了执行线程任务,而是任务都定义在run方法中。run方法结束了,线程任务就是结束了,线程也就是结束了
(线程任务:每一个线程都有自己执行的代码,主线程的代码都定义在主函数中,自定义线程的代码都定义在run方法中。)
(3).4 调用start方法,原因:开启线程并让线程执行,同时还会告诉jvm去调用run方法。
2、调用run和调用start的区别?
调用run方法仅仅是主线程执行run方法中的代码;调用start方法是开启线程,让新建的线程与主线程同时执行。
3、多线程的运行原理
(3).1 如果多部分代码同时执行,那么在栈内存方法执行是怎么完成的呢?
其实是,每一个线程在栈内存中都有自己的栈内存空间,在自己的栈空间中
进行压栈和弹栈。
(3).2 主线程结束,程序就结束吗?
主线程结束,如果其他线程还在执行,进程是不会结束了,当所有的线程都结束了,进程才会结束。
3、代码体现
[java]
view plaincopy
//创建线程的第一种方式 继承Thread
//开启两个窗口进行卖票 第一种方式
/*
思路:
1 创建一个类 继承thread类并覆盖run方法
2 创建子类对象
3 调用start方法
*/
class Ticket extends Thread
{//自定义变量并给一定的值
private int ticket=100;
//覆盖run方法
public void run()
{
//进行循环
while (true)
{
//判断票的数量
if (ticket>0)
{
//打印输出
System.out.println(Thread.currentThread().getName()+".............................."+ticket--);
}
else
{
break;
}
}
}
}
class Demo
{
public static void main(String[] args)
{
//创建Thread子类的对象
Ticket t=new Ticket();
Ticket t1=new Ticket();
//并调用start的方法
t.start();
t1.start();
}
}
二、方式二:
1、方式二步骤:
(1).1,定义类实现Runnable接口。原因:避免了继承Thread类的单继承局限性。
(1).2,覆盖接口中的run方法。原因:将线程任务代码定义到run方法中。
(1).3,创建Thread类的对象。原因:只有创建Thread类的对象才可以创建线程。
(1).4,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
原因:线程已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,
所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
(1).5,调用Thread类的start方法开启线程。
2、方式二和方式一的区别:
(2).1 第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
(2).2 实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类:线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。
实现runnable接口:将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。
Runnable接口对线程对象和线程任务进行解耦。
3、代码体现
[java]
view plaincopy
//创建两个线程去卖票,用种第二方式
/*
思路:
1 创建 一个类 去实现 Runable接口 并覆盖run方法
2 创建一个类的对象 并把这个对象作为参数传递给Thread类的构造函数中
3 调用start方法
*/
//实现Runnable接口
class Ticket implements Runnable
{
//定义一定量的票数
private int ticket=200;
//覆盖run方法
public void run()
{
//进行循环
while (true)
{
//判断票的数量
if (ticket>0)
{
//进行打印
System.out.println(Thread.currentThread().getName()+".............."+ticket--);
}
else
{
break;
}
}
}
}
class Demo1
{
public static void main(String[] args)
{
//把Ticket()的类的对象传入到Thread类对象的构造方法当中去,并调用start方法。
new Thread(new Ticket()).start();
new Thread(new Ticket()).start();
}
}
第三节:线程的运行的状态
一、线程运行是有多种状态的:
1、创建 new Thread类或者其子类对象。
2、运行 start()具备者CPU的执行资格和CPU的执行权。
3、冻结 释放了CPU的执行资格和CPU的执行权。比如执行到sleep(time) wait() 导致线程冻结。
4、临时阻塞状态 具备者CPU的执行资格,不具备CPU的执行权。
5、消亡 线程结束。run方法结束。
第四节:线程的安全问题
一、多线程的安全问题:
1、安全问题产生的原因:
(1).1线程任务中有共享的数据。
(1).2线程任务中有多条操作共享数据的代码。
当线程在操作共享数据时,其他线程参与了运算导致了数据的错误(安全问题)
2、安全问题的解决思路:
保证在线程执行共享数据代码时,其他线程无法参与运算。
3、安全问题的解决代码体现:
同步代码块。synchronized(obj){需要被同步的代码}
4、同步的好处:
同步的出现解决了多线程的安全问题。
5、同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
6、同步的前提:
必须保证多个线程在同步中使用的是同一个锁。
解决了什么问题?
当多线程安全问题发生时,加入了同步后,
问题依旧,就要通过这个同步的前提来判断同步是否写正确。
7、代码体现
[java]
view plaincopy
//出现了线程的安全问题
//解决安全方式之一同步函数 卖票问题
//定义一个类实现Runnable接口
class Ticket implements Runnable
{
//定义一个私有的变量票的数量
private int ticket=10;
//覆盖run方法
public void run()
{
//进行循环
while (true)
{ //加上一个锁
synchronized(this)
{
//判断票的数量大于零
if (ticket>0)
{
//让线程进行睡眠100毫秒,并进行异常的处理
try{Thread.sleep(100);}catch(InterruptedException ie){}
//进行打印
System.out.println(Thread.currentThread().getName()+"................"+ticket--);
}
else
{
break;
}
}
}
}
}
class Demo2
{
public static void main(String[] args)
{
//创建Ticket类的对象
Ticket t=new Ticket();
//并把对象传入到Thread类对象的构造函数当中去
Thread tt=new Thread(t);
Thread ttt=new Thread(t);
//调用start的方法。
tt.start();
ttt.start();
}
}
二、同步函数
1、同步函数的表现形式。就是让一个封装体具备了同步性。
其实就是在函数上加上同步关键字。
2、同步函数的锁用的哪个呢?
(2).1 非静态的同步函数使用的锁是: this
(2).2 静态的同步函数使用的锁时:类名.class
3、同步函数和同步代码块的区别。
(3).1同步函数使用的锁是固定的。
(3).2同步代码块使用的锁是任意对象。
(3).3如果线程任务中仅使用一个同步,可以简化成同步函数的形式。
(3).4如果使用多个同步(多个锁),必须使用同步代码块。
三、单例模式懒汉式中并发访问
1、懒汉式并并发访问,容易出现线程安全问题,而导致对象的不唯一。
2、解决的方式,加上同步关键字。如果使用同步代码块,使用的锁是 类名.class
3、但是效率会降低,所以可以使用同步代码块,加上双重对引用变量的判断。
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
四、死锁:
1、死锁:线程都为结束,都处于不执行状态,这时就称为死锁。
2、常见场景:
(2).1 同步嵌套。
(2).2 都处于冻结状态。
3、尽量避免死锁。
4、代码体现
[java]
view plaincopy
//写一个死锁
/*
思路:
1 所谓的死锁 就是嵌套循环 相互抱着不是自己的锁不放就导致了死锁
2 定义一个类 定义两个锁
3 定义一个类 实现Runnable接口 覆盖run方法 在写入一个嵌套循环 再定义一个标记
4 开启线程
*/
class Demo4
{
public static void main(String[] args)
{
//创建Ticket类的两个对象
Ticket t=new Ticket();
Ticket t1=new Ticket();
//分别传入到两个Thread类对象的构造函数当中去
Thread tt=new Thread(t);
Thread ttt=new Thread(t1);
//调用start方法
tt.start();
//把标记进行转换
t.bug=true;
//再进行调用start方法
ttt.start();
}
}
//定义了锁的类
class MyLock
{
//并定义了两个锁
public static final Object locka=new Object();
public static final Object lockb=new Object();
}
//自定义一个Ticket类并实现Runnabale接口
class Ticket implements Runnable
{
//定义一个变量,来记住票的数量
private int ticket;
//定义一个变量标记
boolean bug=false;
//覆盖run方法
public void run()
{
//进行判断标记
if (bug)
{
//进行循环
while (true)
{
//定义一同步代码块并传入MyLock.locka锁
synchronized(MyLock.locka)
{
//进行对线程sleep100毫秒,并对异常进行处理
try{Thread.sleep(100);}catch(InterruptedException ie){}
//打印票的数量
System.out.println("......................run1111111,,,,,,,,,locka................"+ticket++);
//在定义一个同步代码块这时是MyLock.lockb锁
synchronized(MyLock.lockb)
{
//进行打印
System.out.println(".......1111111lockb................");
}
}
}
}//当标记变化时,就执行下列代码
else
{
while (true)
{
//定义一同步代码块并传入MyLock.lockb锁
synchronized(MyLock.lockb)
{
//进行对线程sleep100毫秒,并对异常进行处理
try{Thread.sleep(100);}catch(InterruptedException ie){}
//打印票的数量
System.out.println("...............,,lockb22222222222222................"+ticket++);
//在定义一个同步代码块这时是MyLock.locka锁
synchronized(MyLock.locka)
{
//进行打印
System.out.println(".....................22222222222run...locka................");
}
}
}
}
}
}
第五节:线程间的通信
一、简述:
1、多线程间通信:多个线程执行任务不同,但是处理资源相同。
2、等待唤醒机制涉及的方法:
(1)、wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中。
(2)、notify():唤醒线程池中一个线程(任意)。
(3)、notifyAll():唤醒线程池中的所有线程。
3、这些方法都必须定义在同步中?
因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。
4、为什么操作线程的方法wait notify notifyAll定义在了Object类中?
因为这些方法是监视器的方法。监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。
5、一对一 代码体现
[java]
view plaincopy
//线程间的通信 例如生产者与消费者 一对一的情况
/*
思路:
1 资源是唯一的 所以创造一个类用来描述这个类
2 用一个类来描述生产者生产者生产一个
3 用一个类来描述消费者消费者就消费一个
*/
class Demo5
{
public static void main(String[] args)
{
Socure s=new Socure();
Procity p=new Procity(s);
Take t=new Take(s);
new Thread(p).start();
new Thread(t).start();
}
}
//自定义一个资源类
class Socure
{
//私有变量标记和生产和消费的数量
private boolean balg;
private int count;
//定义一个方法用来生产的
public void add()
{
//定义一个同步代码块
synchronized(this)
{
//循环标记
while(balg)
//进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException io){}
//数量进行加一
count++;
//打印所生产的数量
System.out.println(Thread.currentThread().getName()+"........生产者............."+count);
//标记进行改变
balg=true;
//进行释放
this.notify();
}
}
//定义一个方法进行消费
public void get()
{
//定义一个同步代码块
synchronized(this)
{
//循环标记
while(!balg)
//进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException ie){}
//打印出消费者消费的数量
System.out.println(Thread.currentThread().getName()+"...............消费者.........."+count);
//标记进行转变
balg=false;
//进行释放
this.notify();
}
}
}
//定义一个生产者类去实现Runnable接口
class Procity implements Runnable
{
//定义资源的变量
private Socure s;
Procity(Socure s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行生产
s.add();
}
}
}
//定义一个消费者类实现Runnavale接口
class Take implements Runnable
{
//定义资源的变量
private Socure s;
Take(Socure s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行消费
s.get();
}
}
}
6、多对多 代码体现
[java]
view plaincopy
//线程之间的通信 多对多的情况
class Demo6
{
public static void main(String[] args)
{
//创建子类类的对象
Recour r=new Recour();
//定义生产者,并把资源进行传入构造函数当中去
Procity p=new Procity(r);
//定义生消费者,并把资源进行传入构造函数当中去
Take t=new Take(r);
//把生产者传入到Thread类对象中的构造方法并调用start方法
new Thread(p).start();
new Thread(p).start();
//把消费者传入到Thread类对象中的构造方法并调用start方法
new Thread(t).start();
new Thread(t).start();
}
}
//自定义一个资源类
class Recour
{
//定义一个变量用来记住生产和消费的个数
private int count;
//定义一个标记
private boolean flag;
//定义一个方法 进行生产的
public void add()
{
//同步代码块
synchronized(this)
{
//定义一个循环
while(flag)
//如果符合进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException io){}
//生产的个数
count++;
//打印出生产的个数
System.out.println(Thread.currentThread().getName()+"......生产者......."+count);
//转换标记
flag=true;
//进行释放
this.notifyAll();
}
}
public void get()
{
//同步代码块
synchronized(this)
{
//定义一个循环
while (!flag)
//如果符合进行等待,并对异常进行处理
try{this.wait();}catch(InterruptedException ioe){}
//打印出消费的个数
System.out.println(Thread.currentThread().getName()+"..消费者...."+count);
//转换标记
flag=false;
//进行释放
this.notifyAll();
}
}
}
//自定义一个生产类去实现Runnable接口
class Procity implements Runnable
{
//定义一个资源类的变量
private Recour s;
Procity(Recour s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行生产
s.add();
}
}
}
//定义一个消费类并且去实现Runnable接口
class Take implements Runnable
{
//定义一个资源类的变量
private Recour s;
Take(Recour s)
{
this.s=s;
}
//覆盖run方法
public void run()
{
while (true)
{
//进行消费
s.get();
}
}
}
二、jdk1.5中提供了多线程升级解决方案:
1、jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
2、Lock接口: 出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。同时更为灵活。可以一个锁上加上多组监视器。
3、Lock接口中的方法:
(1)、lock():获取锁。
(2)、unlock():释放锁,通常需要定义finally代码块中。
4、Condition接口:出现替代了Object中的wait notify notifyAll方法。
(1)、将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。
5、Condition接口中的方法:
(1)、await():让线程处于冻结状态
(2)、signal():唤醒线程池中一个线程(任意)。
(3)、signalAll(): 唤醒线程池中的所有线程。
6、代码体现三、停止线程:
1、stop方法:此方法以过时,通过该方法的描述得到解决办法。
2、run方法结束:任务中都会有循环结构,只要控制住循环就可以结束任务,控制循环通常就用定义标记来完成。
3、如果线程处于了冻结状态,无法读取标记。如何结束呢?
(1)、可以使用Thread类中的interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。
注:当时强制动作会发生了InterruptedException,记得要处理 。
4、sleep和wait方法的异同点:
(1)、相同点:可以让线程处于冻结状态。
(2)、不同点:
(2).1:
sleep必须指定时间。
wait可以指定时间,也可以不指定时间。
(2).2:
sleep时间到,线程处于临时阻塞或者运行。
wait如果没有时间,必须要通过notify或者notifyAll唤醒。
(2).3:
sleep不一定非要定义在同步中。
wait必须定义在同步中。
(2).4:
都定义在同步中,
线程执行到sleep,不会释放锁。
线程执行到wait,会释放锁。
四、多线程中的其他方法:
1、setDaemon: 将线程设置为守护线程,必须在开启前设置,当进程中的线程都是守护线程时,进程结束。
2、setPriority()方法用来设置优先级
(2).1 MAX_PRIORITY 最高优先级10。
(2).2 MIN_PRIORITY 最低优先级。
(2).3 NORM_PRIORITY 分配给线程的默认优先级。
3、join方法:加入一个执行线程。
当a线程执行到了b线程的.join()方法时,a线程就会等待,等b线程都执行完,a线程才会执行。(此时b和其他线程交替运行。)join可以用来临时加入线程执行。
4、yield方法:暂停线程,释放执行权。
转自:http://blog.csdn.net/bei__kou/article/details/46455597?ref=myread
相关文章推荐
- JAVA之JNDI初步理解
- Java中的Filter
- Idea_从Eclipse转Intellij IDEA
- Spring 配置方式
- struts2.1笔记06:struts2开发环境的搭建实际操作出现的问题
- 深入浅出 Java Concurrency (一) ----原子操作
- 【Spring学习笔记-MVC-14】Spring MVC对静态资源的访问
- spring中使用 @value 简化配置文件的读取
- struts2ActionContextCleanUp的作用
- struts2 使用拦截器记录异常日志
- webService之(一)java原生态服务端
- java通过给按钮添加监听器理解接口的作用
- Java Web框架之二层模型-JSP+JavaBean
- javamail邮件发送报错解决方案
- JavaWed_Servlet_Response
- Java 树父节点递归获取树子节点
- 【转】Spring Annotation 详解
- java测试注解(@RunWith(value = SpringJUnit4ClassRunner.class) )
- java学习之旅44--面向对象_17_封装
- java maven 相关问题