Java多线程编程(基础篇)

 

一.进程和线程的区别:

   进程:当前计算机正在运行的程序,进程是cpu分配资源的基本单位,一个进程至少有一个线程。

   线程: 计算机中任务调度和最小的执行单元,一个线程也被称为轻量级进程。

   Java多线程:在单个程序运作的过程中同时运作多个线程,完成不同的工作,称为多线程。

   引入线程的好处:Java虚拟机允许应用程序并发的运行多个线程,引入线程可以减少程序并发时的cpu的开销。

二.Java的运行状态图:

    

      

初始状态(被创建):创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。

运行状态:获得CPU执行权,正在执行的线程。

等待状态:进入等待态的线程会暂时释放CPU执行权,并释放资源。

阻塞状态:处于等待状态的线程,会不断地请求资源,直到请求到资源,才从阻塞状态转换到运行状态。

销亡状态:线程执行结束。

三.如何自定义一个线程?

   1.继承一个线程类。

     

public class ThreadDemo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println("main...i=" + i);
        }
        Tred t = new Tred(); //创建一个线程
        t.start();   //start()开启线程
    }
}
class Tred extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("thread...i=" + i);
        }
    }
}

 

   2.实现一个接口。

  

public class ShowTicket {
public static void main(String[] args) {
    
    Tree t=new Tree();
    Thread t1=new Thread(t);
    Thread t2=new Thread(t);
    Thread t3=new Thread(t);
    
    t1.start();
    t2.start();
    t3.start();    
}    

}
class Tree implements Runnable{  //继承一个接口
    static int ticket=100;
    @Override
    public void run() {    
            while(true) {
                try {
                    Thread.sleep(30);
                }catch(Exception e) {}
                if(ticket>0) {
                    System.out.println(Thread.currentThread().getName()+
                            "正在出售第"+ticket--+"张票");
                        
    }    
}
}
}

总结:

        无论是继承Thread还是实现接口,都要重写run()方法,因为run方法中定义了线程的执行内,而且此方法由JVM自动调用。

顺便说一下Java多线程的执行路径:

         程序的执行流程将不会按照原有单线程的执行流程执行,有可能会出现多个线程之间互相打断的情况。

四.synchronized关键字

   synchronized:同步

   语法:synchronized(对象锁){ 要锁的代码 }

   作用:保证线程执行的原子性,也就是说,当前线程执行完毕后,其他线程方可执行。

   注意:此处对象锁锁的不是代码块,锁的是对象。当对象发生改变时,同步会失效。

    synchronized保证程序原子性的代码实例:

  

public class ShowTicket1 {
public static void main(String[] args) {
    Windows w=new Windows();
    Thread t1=new Thread(w);
    Thread t2=new Thread(w);
    Thread t3=new Thread(w);
    
    t1.start();
    t2.start();
    t3.start();
}
}
class Windows implements Runnable{
     private int ticket =100;
     Object obj=new Object();
     @Override
     public void run() {    
        while(true) {
            synchronized(obj) {//原子性  互斥锁,不可再分,代码块必须执行完,保证了代码的完整性。
            if(ticket>0) {
                //try{TimeUnit.SECONDS.sleep(0);}catch(Exception e){}   睡眠的另一种写法
                try{Thread.sleep(50);}catch(Exception e) {}
                System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket--+"张票");
            }
        }
    }
  }
}

知识点:

    1. 同步和非同步方法能否同时调用?

     可以
     在同步方法在执行过程中,允许其他线程调用非同步方法。

    2.验证同步方法的对象锁是this

      思路:开启两个线程,让一个进入同步代码块,另一个进入同步方法,设置一个中间变量boolean flag

      实例代码:

    

public class SynchronizedDemo {
    public static void main(String[] args) {
        Tt t = new Tt();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t1.start();
        try {
            Thread.sleep(50);
        } catch (Exception e) {
        }
        t.flag=false;
        t2.start();
    }
}

class Tt implements Runnable {
    int count = 10;
    Object obj = new Object();
    boolean flag=true;

    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (this) {
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {
                    }
                    if (count > 0)
                        System.out.println(Thread.currentThread().getName() + 
                                "count=" + count--);
                }
            }
        } else {
            while(true)
            method();
        }
    }
    public synchronized void method() {
        while (true) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {
            }    
        if (count > 0)
            System.out.println(Thread.currentThread().getName() + 
                    "count=" + count--);
    }
  }

 

    3.如果同步代码的对象锁是this,那么这个方法可以写为同步方法。

    4.线程死锁:线程死锁:
     假设有A,B两个死锁,a线程的执行需要获取b线程的对象锁,b线程的执行需要获取a线程的对象锁

     实例代码:

   

public class DeathLock {
    public static void main(String[] args) {
        new Thread(new DL(true)).start();
        new Thread(new DL(false)).start();
    }
}

class Lock {
    static final Object LOCKA = new Object();
    static final Object LOCKB = new Object();

}

class DL implements Runnable {
    boolean flag;
    public DL(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (Lock.LOCKA) {
                System.out.println("if locka run");
                synchronized (Lock.LOCKB) {
                    System.out.println("if locka run");
                }
            }
        } else {
            synchronized (Lock.LOCKB) {
                System.out.println("else lockb run");
                synchronized (Lock.LOCKA) {
                    System.out.println("else locka run");
                }
            }
        }
    }
}

结果分析:程序出现死锁,彼此都在等待对方释放资源。

5.如果程序运行过程中,出现了异常,则该对象锁会被释放。

实例代码如下:

public class Demo4 {
    public static void main(String[] args) {
        Demo4 d=new Demo4();
        new Thread(()->d.method(),"d1").start();
        try {
            Thread.sleep(30);
        }catch(Exception e) {}
        new Thread(()->d.method(),"d2").start();        
    }    
    int num=10;
    synchronized void method() {
        System.out.println(Thread.currentThread().getName()+"start");
        while(true) {
            num++;            
            System.out.println(Thread.currentThread().getName()+"num="+ num);
            try {
                Thread.sleep(30);
            }catch(Exception e) {}    
            
            if(num==15) {
                //try {
                    int num=1/0;
                //}catch(Exception e) {}
                
            }
         }
    }
    
}
    

结果分析:

        当发生异常时,当前的对象锁会被释放。所以,在并发过程中出现异常,需要特别小心,否则会出现执行结果不一致的情况

       如何解决?

        捕获该异常。

 6.最好不要用字符串常量作为对象锁。

 

volatile关键字:可保证线程透明,不具有原子性,但是效率比较高。

可保证线程透明的原因,在此简单的说一下:由于Java的内存模型可知,多个线程之间共享的资源被存放在内存中,并且每个线程都有自己独立的工作区,Java默认每个线程都将获取到内存中的副本,并且拿着这个副本进行操。当有线程对当前变量进行修改时,其他线程将无法感知,所以不会停止运行。所以,使用volatile关键字,会通知所有的线程变量发生了改变,让所有线程都获取到变量的修改值。

注意:volatile不能保证程序运行的原子性,所以,不能代替synchronized。

实例代码如下:

public class Demo6 {
    public static void main(String[] args) {

        Demo6 d = new Demo6();

        List<Thread> list = new ArrayList<Thread>();
        for (int i = 0; i < 10; i++) {
            list.add(new Thread(d::method, "list-" + i));
        }

        list.forEach((o) -> o.start());//让集合中的10个线程都启动
        list.forEach((o) -> {
            try {
                o.join();//让集合中的10个线程先执行完,在执行main线程
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
                });

        System.out.println(d.num);
    }

volatile int num = 0;
synchronized void method() {
        for (int i = 0; i < 10000; i++) {
            num++;
        }
    }

}

五: 

AtomicXXX类本身就是原子性的,且有很多原子性的方法,但是不能保证多个原子性的方法连续使用还是原子性的。

由于++  --是不具有原子性的,所以针对这一问题,显得十分高效。

AtomicXXX类:执行效率比synchronized votalie高

实例代码:

public class Demo8 {
public static void main(String[] args) {
    Demo8 d=new Demo8();
    List<Thread> list=new ArrayList<Thread> ();
    for(int i=0;i<10;i++) {
        list.add(new Thread(d::method,"list-"+i));    
    }
    
    list.forEach((o)->o.start());
    list.forEach((o)->{
        try {
            o.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    });
    
    System.out.println(d.num);
    
}
AtomicInteger num=new AtomicInteger(0);
    void method() {
    for(int i=0;i<20;i++) {
        num.incrementAndGet();//count++
    }
}
}

 

posted @ 2018-10-15 22:50  米兰达儿  阅读(668)  评论(0编辑  收藏  举报