<span style="font-family: SimSun; background-color: rgb(255, 255, 255);"> ------</span><a target=_blank href="http://www.itheima.com" target="blank" style="font-family: SimSun; background-color: rgb(255, 255, 255);">Java培训、Android培训、iOS培训、.Net培训</a><span style="font-family: SimSun; background-color: rgb(255, 255, 255);">、期待与您交流!</span>
1,线程的由来:
1.1:进程与线程的理解:
进程是一个执行中的程序,
每一个进程执行都有一个执行的顺序,该顺序是一个执行路径,或者叫一个控制单元;
进程:
应用程序只要是启动,都会在内存中分配一片空间(地址),进程就是用来标识这个空间的,
用于封装里面代码的控制单元。
因此:线程才是进程中的真正执行的部分;
也就是说一个进程至少有一个线程(控制单元)。
如果这个线程运行的代码存在于main方法中,那么这个线程叫主线程。
主线程不是单线程,当JVM启动时, 就是一个多线程,
原因:
在执行主函数是, 执行的是主线程, 但是在内存中, 栈堆中会建立很多的对象, 如果对象不被使用, 会垃圾回收,主线程还在执行下面的操作, 但是被垃圾回收的对象就被干掉了,这就是同时在进行,
主线程在继续执行的同时 , 这个垃圾被垃圾回收了,所以JVM这时至少有两个线程
一个是主线程,
一个是负责垃圾回收的线程
1.2 多线程存在的意义
多个线程的出现,可以让一个程序中的多个代码同时执行, 这样就可以提高程序运行的效率;
比如:
如果就是一个主线程,
那内存中产生的垃圾没有回收,那么就有很多的垃圾,直到内存放不下了, 就会出问题,
这时JVM就会停下来, 即主线程先停在这个位置,先要把垃圾处理,然后在执行,这样就不合理了,
但是如果这个主线程一直逐条执行,而另一个线程在处理垃圾,有两个控制单元(执行路径),这样就会效率更快;
2 线程的创建:
多线程的创建就是为了让某些代码能够同时执行,
那如何才能在自定义的代码中自定义控制单元呢?
首先, 创建这个控制单元, 就是执行路径,他本身也是一类事物,因此就可以把它描述成一个类了;
然后创建这个控制单元的类的对象, 这样就可以调用这个类的方法(功能),来创建多条执行路径,实现代码的同步执行。
这执行路径是在进程中的, 进程是系统Windows所创建的, 因此进程中的线程也是windows帮忙创建的,而jvm依赖于系统,只需要调用系统中的内容,既可以完成动作。
而java提供了对线程对象的体现,被java虚拟机封装成了对象,因此可以直接调用。
就需要找对象, 在API中查找,java就已经提供了对线程这类事物的描述,就是Thread类。
java虚拟机允许应用程序并发地允许多个执行线程。
2.1 创建线程方法一:继承Thread类
这种方法是将类声明为Thread的子类, 该子类应重写Thread类的run()方法。
通过对API的查找, java已经提供了对线程这类事物的描述,就是Thread类。
代码如下:
class Demo extends Thread//继承Thread对象,让他成为Thread类的子类,这样就可以使用Thread类中的方法了
{
public void run(){//将需要执行的代码放在这里
for(int i=0;i<68;i++){
System.out.println("Demo run.."+i);
}
}
}
class ThreadDemo_1
{
public static void main(String[] args)
{
Demo d=new Demo();//创建了一个线程
//d.run();//如果这样的话,就是调用demo的run()方法, 只有一个主线程
d.start();//调用这个方法, 就是创建这个线程, 并启动了这个线程,让线程开始执行,JVM就会调用这个线程的run()方法了
for(int i=0;i<68;i++){
System.out.println("main..."+i);
}
}
}
。
2.1.1 继承thread类创建线程——分析
Jvm创建了主线程,
到了d=newDemo();,就创建了新的控制单元,线程被创建了,被主线程所开启的 那么这个程序就多了一个执行路径。
2,复写Thread类中的run()方法,
目的:将自定义的代码存储在run()方法中, 让线程运行。
3,主函数中创建Thread子类的对象, 然后调用这个线程的start()方法,
该方法的作用:a: 启动线程
b:调用run()方法
如果一个程序中有多个线程同时需要共享数据,
共享数据可以把他定义成静态,但是这样,他的生命周期会很长,不建议;
因此可以定义一个线程,并让这个线程都去开启执行start(),然而这样做是没有意义的,
就会提示线程状态出错,因为已经运行的程序不需要再次开启的,所以继承Extends类就不太靠谱了,
Runnable接口应该由那些打算通过某一线程执行这个实现Runnable接口的类的实例来实现。
他为非Thread子类的类(执行这个实现Runnable接口的)提供了一种激活方式。
Runnable接口中的抽象方法只有一个run(),
这个run()的作用:将需要执行另一条路径的代码(明确要运行什么代码)存储在这里,
然后实现Runnable接口的类的对象,创建一个线程,启动该线程将导致在独立执行的线程中执行这个对象的run方法,
1,定义类实现Runnable接口;
2,覆盖Runnable接口中的run()方法
将线程要运行的代码存放在该run方法中,
3,通过Thread类建立线程对象;
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中,
为什么要将Runnable接口的子类对象传递给Thread的构造方法。
因为,自定义的run()方法所属的对象是Runnable接口的子类对象, 所以要让线程去指定对象的run()方法,
就必须明确该run()方法所属的对象
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
个人理解:
继承Thread类, 比较方便, 但是有局限,
他的子类只能继承单一的线程,有局限性,
所以在定义线程时, 建议使用实现的方式,
因为一个类可以多实现, 可以作为实际参数传递到Thread类的构造方法中,
区别:
继承Thread:线程代码存放在Thread子类run()中;
实现Runnable:线程代码存放在接口的子类的run()中;
安全问题的由来:
当一个程序中有多个线程同时运行的时候, 并且是共享同一数据资源,
那么多个线程会争取抢夺CPU的执行权,当其中的一个线程使用了这个共享数据,但是只是执行了一部分, 还有一部分的
代码没有执行完, 另一个线程就争抢到了CPU的执行权, 同样执行那这一部分,从而导致共享数据的错误。
2,必须是多个线程使用同一个锁。
必须保证同步中只有一个线程在运行,
有些代码需要同步, 有些代码不需要同步, (看是否在共享数据,因为安全问题是由于共享数据所产生的)
同步的好处:
解决了多线程的安全问题
弊端:
多个线程需要判断锁, 较为消耗资源。
原因:
同步代码块用来封装需要同步的共享数据的代码。
函数:也是用来封装代码的, 如果这个函数中的代码都是共享的数据时, 那么就可以将这个函数同步,
1,明确哪些代码是多线程运行的代码;
2,明确共享数据
3,明确多线程运行代码中哪些语句是操作共享数据的。
同步函数的锁是this,
静态函数的锁是class字节码对象。
终身指向newSingle(),所以不管线程多少, 都不会产生安全问题。
单例设计模式下懒汉式的作用:延迟加载,
问题的产生:
当有多个线程执行到if语句时,
这时被其他的线程抢夺了CPU, 那么这样的话, 就会产生多个对象, 这样就会产生安全隐患。
因为单例设计中,只能有一个该类对象被创建。
那如何解决呢?
这里就需要同步, 前提: 多个线程在操作共享资源。
这样就可以解决,
但是这样的话, 每次都要先判断这个锁,这样会比较低效,
因此:在锁的上面,再加一个判断,
这样双重判断,减少了锁的判断次数, 就会提高效率了,
2.6 线程——死锁问题:
在同步中, 有时会产生死锁,这样我们需要尽量避免死锁的发生,
什么是死锁呢?——同步里面嵌套同步;
class Test implements Runnable
{
private boolean flag;
Test(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
while(true)
{
synchronized(Mylock.locka){//同步中嵌套同步
System.out.println("a锁说:这是我的锁");
synchronized(Mylock.lockb){
System.out.println("a锁说:谢谢你的b锁");
}
}
}
}
else{
while(true)
{
synchronized(Mylock.lockb){
System.out.println("b锁说:这是我的锁");
synchronized(Mylock.locka){
System.out.println("b锁说:谢谢你的a锁");
}
}
}
}
}
}
//为了方便
class Mylock
{
static Object locka=new Object();//静态的目的为了方便调用
static Object lockb=new Object();
}
class ThreadDeadTest
{
public static void main(String[] args)
{
Thread t1= new Thread(new Test(true));
Thread t2= new Thread(new Test(false));
t1.start();
t2.start();
}
}
从代码分析, 开始线程二抢到CPU的执行权, b锁向a锁要到了锁, 所以就
就是多个线程在操作同一个资源,但是操作的动作不同。
如果不同步, 那么线程之间在抢夺CPU的执行权的时候, 就会出现数据的错误。
所以前提是在需要数据共享的代码中同步。
由于线程之间的功能不同, 所以即使写了synchronized, 也无法保证是值是正确的, 因此需要考虑同步中的另一个
前提,是否是同一个锁,而锁是一个任意的对象,由于他们是共享同一个资源, 所以这个锁可以是这个资源描述的类的对象;
为什么会执行一大片?
一个线程可能一直拥有CPU的执行权;
如何解决?(参考代码)
这里可以用一个标记来标识(flag),当存一个完了之后, 就改变这个标识,当下次再执行时, 就不会再存了,
此时可以将这个线程1给冻结(sleep(时间(毫秒)),wait()),如果用sleep(),时间不确定, 所以就用wait(),当执行完了之后,
标识发生改变,再次执行时, 就处于冻结状态了,失去执行资格。
那什么时候恢复执行资格呢?
当另一个线程2进来将这个存的值,取走了, 就恢复执行资格,如何恢复?
当取走后, 标识再次发生改变,然后用notify(),唤醒线程1,此时如果这个线程2再次执行时, 由于没有了值, 那么就将线程2冻结,wait()。
这个过程就是线程间的等待唤醒机制。
<pre name="code" class="java">class Res //共享资源的类
{
String name;
String sex;
boolean flag=false;
}
/*
等待的线程在哪里呢?
线程运行的时候, 内存中会建立一个叫做线程池的,等待线程都存放在线程池中,
当notify的时候, 都是唤醒的是线程池中的线程,当线程池中有许多的线程都在等待,
通常是唤醒第一被等待的线程, 按照顺序来叫醒
*/
class Input implements Runnable//需要用到一个资源,
{
private Res r;
Input(Res r){//传递过来一个资源类的对象
this.r=r;
}
public void run(){
int x=0;
while(true)
{
synchronized(r)//这里需要同步,
{
if(r.flag){
try{r.wait();}catch(Exception e){}//当为真时, 就等待, 不能再传值了,因为已经有值
}
if(x==0){
r.name="mike";
r.sex="man";
}
else{
r.name="小王";
r.sex="女女女女女女";
}
x=(x+1)%2;
r.flag=true;
r.notify();//在线程池中叫醒Output线程
}
}
}
}
class Output implements Runnable
{
private Res r;
Output(Res r){
this.r=r;
}
public void run(){
while(true)
{
synchronized(r){//这里也需要同步,并且锁需要是同一个锁。
if(!r.flag){
try{r.wait();}catch(Exception e){}
}
//Wait(), notify(), notifyAll(), 全部用在同步中,因为需要锁必须标识wait()线程所处的锁
System.out.println(r.name+"....."+r.sex);
r.flag=false;
r.notify();
}
}
}
}
class ThreadInputOutput2
{
public static void main(String[] args)
{
Res r=new Res();
Input in=new Input(r);
Output out=new Output(r);
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
2.7.2 多线程——线程池
当线程进入冻结状态是, 这个等待的线程存放在哪里呢?
生产一个消费一个,
代码如下:
<pre name="code" class="java">class Resource
{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name){
if(flag){
try{this.wait();}
catch(Exception e){}
}
this.name=name+"....."+count++;
System.out.println(Thread.currentThread().getName()+"....生产...."+this.name);
flag=true;
this.notify();
}
public synchronized void out(){
if(!flag){
try{this.wait();}
catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+"........消费........"+this.name);
flag=false;
this.notify();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res){
this.res=res;
}
public void run()
{
while(true){
res.set("+商品+");
}
}
}
class Customer implements Runnable
{
private Resource res;
Customer(Resource res){
this.res=res;
}
public void run(){
while(true){
res.out();
}
}
}
class ThreadProCus
{
public static void main(String[] args)
{
Resource res=new Resource();
Producer pro=new Producer(res);
Customer cus=new Customer(res);
Thread t1=new Thread(pro);
Thread t2=new Thread(cus);
t1.start();
t2.start();
}
}
结果如下:
这里有两个线程, 但是实际开发有多个线程来生产消费;
但是一般来说, 实际开发是有多个线程来生产消费的,
运行的话,就会有问题,
。这样就不对了, 为啥?
原因:线程可能唤醒的是本方的线程, 而将前一个给覆盖了, 这时就需要将这个线程再次判断一下, 所以需要把if变成while循环。
而这样, 可能会将这些线程都给冻结了, 因此需要将notify(),改成notifyAll().
代码如下;
class Resource
{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name){
while(flag){//用if判断, 数据错误, while循环,全部等待, 不是活着的, 冻结了
try{this.wait();}
catch(Exception e){}
}
this.name=name+"....."+count++;
System.out.println(Thread.currentThread().getName()+"....生产...."+this.name);
flag=true;
this.notifyAll();
}
public synchronized void out(){
while(!flag){
try{this.wait();}
catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+"........消费........"+this.name);
flag=false;
this.notifyAll();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res){
this.res=res;
}
public void run()
{
while(true){
res.set("+商品+");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res){
this.res=res;
}
public void run(){
while(true){
res.out();
}
}
}
class ThreadProCus1
{
public static void main(String[] args)
{
Resource res=new Resource();
Producer pro=new Producer(res);
Consumer con=new Consumer(res);
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();
}
}
总结:
1,对于多个生产和消费:
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
2,为什么定义notifyAll().
因为需要将对方的线程唤醒。
因为只用notify(),容易出现只唤醒本方的线程的情况,导致程序中的所有的线程都在等待。
2.7.4 多线程——生产者和消费者的改进-Lock接口
在上一个代码中,存在一个bug, 就是notifyAll(), 可能会将本方的线程也给唤醒, 如何才能让他不换醒呢?
接口中Lock,特点是替代了synchronized,比使用synchronized方法和语句可获得更广泛的锁定操作。
首先:synchronized,开锁解锁都是隐式的, 而lock就可以一目了然的看到,他是显示的,
并且可以支持多个相关的Condition对象。
而Condition把wait,notify,notifyAll,替代,因此可以将代码改进:
/*
用synchronized,开锁解锁是隐式的, 但是用lock , 就可以一目了然了,
显式的,
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag=false;
private Lock lock=new ReentrantLock();//多态, 创建一个锁对象,
private Condition condition_pro=lock.newCondition();//创建condition的对象
private Condition condition_con=lock.newCondition();
public void set(String name)throws InterruptedException{
//将同步的语句变成了两个方法lock(),unlock().
lock.lock();//调用lock()方法,获取了一个锁
//用if判断, 数据错误, while循环,全部等待, 不是活着的, 冻结了
try
{
while(flag){
condition_pro.await();
}
this.name=name+"....."+count++;
System.out.println(Thread.currentThread().getName()+"....生产...."+this.name);
flag=true;
condition_con.signal();
}
finally{
lock.unlock();//解锁
}
}
public void out()throws InterruptedException{
lock.lock();
try
{
while(!flag){
condition_con.await();
}
System.out.println(Thread.currentThread().getName()+"........消费........"+this.name);
flag=false;
condition_pro.signal();
}
finally{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res){
this.res=res;
}
public void run()
{
while(true){
try
{
res.set("+商品+");
}
catch (InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res){
this.res=res;
}
public void run(){
while(true){
try
{
res.out();
}
catch (InterruptedException e)
{
}
}
}
}
class ThreadProCus2
{
public static void main(String[] args)
{
Resource res=new Resource();
Producer pro=new Producer(res);
Consumer con=new Consumer(res);
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();
}
}
2.8 多线程——停止线程
首先开启多个线程运行, 一般运行的代码通常是循环结构。如果需要让线程停止,
只要控制住循环, 就可以让run方法结束了。
让线程停止,这里用到的是intterupt(),以前用的是stop(),但是目前已经停用了。
intterupt()表示强制清除冻结状态,让线程回复到运行状态中来,不是结束线程。
相当于:这时把他给催眠(wait())了, 但是这时催眠的人出国了, 我来了, 一砖头(intterupt())下去,
class StopThread implements Runnable
{
private boolean flag=true;
public synchronized void run(){
while(flag){
try
{
wait();//这样线程处于冻结状态,主函数结束,但是程序没有结束
//本来冻结了,但是一砖头(interrupt()),打醒了,就抛出异常
}
catch (InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+".....Exception");
flag=false;//一砖头打醒后,会执行,那么while就不会执行了,
}
System.out.println(Thread.currentThread().getName()+".....run");
}
}
public void changeFlag(){
flag=false;
}
}
class Threadstop
{
public static void main(String[] args)
{
StopThread st=new StopThread();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();
t2.start();
int num=0;
while(true){
if(num==60){
t1.interrupt();
t2.interrupt();
//st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num++);
}
System.out.println("over");
}
}
总结:
前台线程:所看到的线程都是前台的线程。
然后在开始执行线程。
在运行的时候, 会共同抢夺CPU的执行权运行, 开启运行时没有区别的;
但是在结束程序上有区别:
当前台的线程都结束后, 后台的线程就会自动结束。
join方法:等待该线程终止。
//线程0, 要申请加入进来, 线程0要CPU的执行权,join的意思就是, 抢夺CPU的执行权。
//这时主线程就把CPU的执行权给了线程0,主线程处于冻结状态
//数据全部打印完, 结束后,主线程才恢复到运行状态,这个就是join 的用法,
//主线程让出了执行权,这时主线程会只等到t1线程执行完,才活过来, 和t2结不结束, 没有关系,
//join可以临时加入线程执行
class Demo implements Runnable
{
public void run(){
for(int x=0;x<70;x++){
System.out.println(Thread.currentThread().toString()+"...."+x);
Thread.yield();//暂停当前执行的线程程序
}
}
}
class ThreadJoin
{
public static void main(String[] args)throws InterruptedException
{
Demo d=new Demo();
Thread t1=new Thread(d);
Thread t2=new Thread(d);
t1.start();
//t1.setPriority(Thread.MAX_PRIORITY);//设置优先级
t2.start();
//t1.join();
/*
for(int x=0;x<80;x++){
System.out.println(Thread.currentThread().getName()+"...."+x);
}
*/
}
}
2.10
多线程——优先级,yield
把优先级设高点, 但是对于CPU的抢夺, 抢夺的概率会大一点。
yield()方法:
表示暂停当前正在执行的线程对象,并执行其他的线程。
线程释放了CPU的执行权, 就停下了, 另一个线程进来,又停下,这样交替,
-------------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------